Google 試算表:取得儲存格範圍 getRange()

 


如果你常用 Google 試算表做報表、匯整名單或自動化,getRange() 幾乎每天都會碰到。它就像是「先把資料框起來」的那一步:你想讀值、改格式、丟公式、搬資料,都得先抓對區塊。

本篇文章會盡可能說明內容,說明A1 表示法怎麼寫、列欄為什麼從 1 開始、整塊讀寫為什麼快很多;也會示範常見情境,例如只取到最後一列的有效資料、一次處理多個不連續區域,還彙整常見地雷(例如 setValues 維度不合、範圍越界、被保護欄位寫不進去)。先穩穩抓到對的 Range,剩下的事就好辦了。希望本篇文章能夠幫助到需要的您。


目錄

{tocify} $title={目錄} 


為什麼是 getRange()?

在 GAS 操作 Google 試算表時,你做的 8 成動作都繞不開它:讀取儲存格值、整塊資料的批次存取、格式調整、公式填入、保護範圍、甚至資料清洗前的切片,第一步通常都是「先抓到一塊 Range」。抓得又快又準,寫起來就順、效能也會好。

快速上手:四種常用呼叫方式

getRange() 會因為你是對哪個物件呼叫而有些差異,但最常見的是對 Sheet 物件呼叫(某一個工作表)。以下為四個超高頻寫法:

1.    getRange(a1Notation: string)

    範例:sheet.getRange('B2')、sheet.getRange('A2:D100')、

                sheet.getRange('A:A')、sheet.getRange('2:2')

    特色:人類可讀性高、搭配命名範圍與工作表名稱方便('工作表 1'!A1:C5)

2.    getRange(row: number, column: number)

    範例:sheet.getRange(2, 2) 代表 B2

    特色:索引從 1 開始(不是 0),適合計算後定位單一格

3.    getRange(row: number, column: number, numRows: number)

    範例:sheet.getRange(2, 1, 10) 代表自 A2 起往下 10 列(單欄)

    特色:易用於動態列數

4.    getRange(row: number, column: number, numRows: number, numColumns: number)

    範例:sheet.getRange(2, 1, 100, 4) 代表 A2:D101(100 列 × 4 欄)

    特色:最通用,後續 getValues()/setValues() 批次處理超順手

延伸:Spreadsheet 物件還有 getRangeByName(name) 可抓「命名範圍」,以及 getRangeList([a1, a1, ...]) 一次抓多塊不連續區域。


介面到程式:操作流程

步驟 1|建立試算表與資料

    1.    在 Google 試算表建立一個活頁簿,新增工作表「資料」與「報表」。

    2.    在「資料」貼入範例資料(A1:D 若干列),第一列當標題。

步驟 2|開啟 GAS 專案

    試算表上方選單:

        擴充功能 → App Script 進入編輯器(新介面叫 Apps Script Editor)。

步驟 3|取得工作表物件與 Range

function demoGetRangeBasics() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('資料');

  // A1 表示法
  const r1 = sheet.getRange('B2');              // 單一儲存格
  const r2 = sheet.getRange('A2:D100');         // 連續區域
  const r3 = sheet.getRange('A:A');             // 整欄
  const r4 = sheet.getRange('2:2');             // 整列

  // 數字索引(1-based)
  const r5 = sheet.getRange(2, 2);              // B2
  const r6 = sheet.getRange(2, 1, 10);          // A2 開始往下 10 列
  const r7 = sheet.getRange(2, 1, 100, 4);      // A2:D101
}


步驟 4|讀寫資料的正確姿勢(批次)

function readWriteBatch() {
  const ss = SpreadsheetApp.getActive();
  const sheet = ss.getSheetByName('資料');

  // 找到有效資料區(不含空白尾端)
  const dataRange = sheet.getDataRange();
  const values = dataRange.getValues();  // 2D 陣列:rows × columns

  // 例:在最後一欄新增「合計 = 價格 × 數量」
  const headers = values[0];
  const priceIdx = headers.indexOf('價格');
  const qtyIdx   = headers.indexOf('數量');

  if (priceIdx === -1 || qtyIdx === -1) {
    throw new Error('找不到價格或數量欄位');
  }

  // 產出新矩陣(避免逐格 setValue,效能差)
  const out = values.map((row, i) => {
    if (i === 0) return row.concat(['合計']);
    const price = Number(row[priceIdx]) || 0;
    const qty   = Number(row[qtyIdx])   || 0;
    return row.concat([price * qty]);
  });

  // 把 out 一次寫回「報表」工作表
  const outSheet = ss.getSheetByName('報表') || ss.insertSheet('報表');
  outSheet.clearContents();
  outSheet.getRange(1, 1, out.length, out[0].length).setValues(out);
}

重點:避免在迴圈中逐格 getValue()/setValue()。用一次抓/一次寫的 2D 陣列,延遲授權與 IO 次數,速度會快非常多。


A1 表示法與命名範圍:寫得準、維護也輕鬆

A1 表示法訣竅

    工作表名稱含空白或特殊字元,要加單引號:sheet.getRange("'2025 報表'!A1:C5")

    整欄:A:A,整列:1:1

    不要混用逗號與冒號:範圍區間一律 起點:終點,例如 A1:D10

命名範圍

    在試算表:資料 → 命名範圍 新增,例如命名 DataArea

    GAS 讀取:


function readNamedRange() {
  const ss = SpreadsheetApp.getActive();
  const range = ss.getRangeByName('DataArea'); // 回傳 Range
  const values = range.getValues();
  Logger.log(values.length);
}

命名範圍的好處是:移動或擴充資料時,程式碼不用改 A1,維護省心。


動態範圍選取:最後一列/欄、去頭去尾、避開空列

最後一列與最後一欄

const lastRow = sheet.getLastRow();
const lastCol = sheet.getLastColumn();
const body = sheet.getRange(2, 1, Math.max(lastRow - 1, 0), lastCol); // 排除標題列

注意:若表中只有標題,lastRow 可能是 1,記得 Math.max() 避免負數列數。

以關鍵欄判斷資料長度

function getBodyByAnchorColumn(sheet, anchorCol = 1) {
  const colVals = sheet.getRange(2, anchorCol, sheet.getMaxRows() - 1, 1).getValues();
  const filled = colVals.findIndex(row => row[0] === '' || row[0] === null);
  const rows = (filled === -1) ? colVals.length : filled;
  return sheet.getRange(2, 1, rows, sheet.getLastColumn());
}

這種做法適合「中間可能有空白,但以第一欄為主索引」的表。

Offset 與交集

const base = sheet.getRange('A1:D20');
const sub  = base.offset(1, 0, base.getNumRows() - 1, base.getNumColumns()); // 去掉標題

效能與架構:避免慢到爆的 4 個原則

1.    減少呼叫次數

    把讀寫合成批次:同一塊 Range 一次 getValues(),處理成新 2D 陣列後一次 setValues()。

2.    少用逐格迴圈

    不要在 for 迴圈內每格 getValue();把資料帶回本地記憶體處理。

3.    用 flush() 做必要的落盤

    在需要立即反映(例如立刻讀到剛寫入的公式結果)時再 SpreadsheetApp.flush(),不要濫用。

4.    運用觸發器與分段

    超大表可拆批或改為時間驅動觸發器定時處理,避免單次腳本超時(6 分鐘~30 分鐘上限視執行環境而定)。


情境實戰

實戰一:依條件複製指定列到報表

function filterAndCopy() {
  const ss = SpreadsheetApp.getActive();
  const src = ss.getSheetByName('資料');
  const dst = ss.getSheetByName('報表') || ss.insertSheet('報表');

  const srcData = src.getDataRange().getValues();
  const header = srcData[0];
  const statusIdx = header.indexOf('狀態');
  if (statusIdx === -1) throw new Error('缺少「狀態」欄');

  const rows = srcData.filter((r, i) => i === 0 || r[statusIdx] === '核准'); // 含標題
  dst.clearContents();
  dst.getRange(1,1,rows.length,rows[0].length).setValues(rows);
}


實戰二:整欄自動填入遞增代碼

function fillIncrementalCode() {
  const sheet = SpreadsheetApp.getActive().getSheetByName('資料');
  const body = sheet.getRange(2, 1, Math.max(sheet.getLastRow()-1, 0), 1);
  const n = body.getNumRows();
  if (n <= 0) return;

  const base = 1000; // 起始碼
  const codes = Array.from({length: n}, (_, i) => [`DOC-${base + i + 1}`]);
  body.setValues(codes);
}


實戰三:多區塊一次抓(getRangeList)

function multiRanges() {
  const sh = SpreadsheetApp.getActiveSheet();
  const ranges = sh.getRangeList(['A1:A10', 'C1:C10', 'E1:E10']).getRanges();
  ranges.forEach(r => r.setBackground('#FFFDE7'));
}


除錯與開發流程建議

Logger / Execution log:

         Logger.log(obj),或用 console.log()(新執行記錄也支援),觀察輸入輸出長相。

試資料夠小:先用 10 筆驗證流程再擴大跑全表。

保留檔案備份:

        大動作前複製活頁簿,或用版本歷史記錄可逆。

權限宣告:

        第一次操作寫入/保護等動作會跳權限同意,務必在正式前跑一次綁定。


常見錯誤與雷點

1.「Range not found / 找不到範圍」

情境 : 

    getRange("'月報 2025'!A1:C5"),但工作表其實叫「月報2025」(少空格)

    A1 表示法打成 A1, C5(逗號)

修法 :

    確認工作表名稱並以單引號包起來(含空白或特殊字元)。

    範圍用冒號 A1:C5,多區塊要用 getRangeList()。


2.「The coordinates of the range are outside the dimensions of the sheet」

情境 :

    getRange(2,1,1000,10) 但工作表只有 200 列或 5 欄

修法 :

    先以 getMaxRows()/getMaxColumns() 或 getLastRow()/getLastColumn() 動態計算;必要時 insertRows/insertColumns 補齊。


3.「The number of columns in the data does not match the number of columns in the range」(setValues 維度不符)

情境 :

    range.setValues(out) 時,out[0].length !== range.getNumColumns() 或 out.length !== range.getNumRows()

修法 :

    先 Logger.log(out.length, out[0].length) 與 range.getNumRows()/getNumColumns() 比對。

    若要動態寫入,先依 out 的大小建立對應 Range:

    sheet.getRange(startRow, startCol, out.length, out[0].length).setValues(out)


4. 以為索引從 0 開始(其實從 1)

情境 : 

    getRange(0, 0) 直接炸

修法 :

    永遠記住:GAS 的列、欄皆為 1-based。


5. getValue()/setValue() 在大表中用迴圈造成超時

情境 :

    幾千幾萬格逐一讀寫,執行時間暴增

修法 :

    改一次 getValues() → 計算 → 一次 setValues()。

    真要逐格,多用陣列操作在本地組好再一次寫回。


6. 合併儲存格導致資料對齊錯位

情境 :

    合併後 getValues() 回傳的 2D 陣列不是你想的矩形分布

修法 : 

    先 range.breakApart() 取消合併,處理完再視需要重合併。


7. 篩選檢視(Filter Views)/ 隱藏列影響寫入位置

情境 :

    視覺上第 5 列是你看到的第 3 筆,但實際列號仍是 5

修法 :

    getRange() 永遠以真實列號為準;若要「看得到的」範圍,先以條件把資料抽出後寫入新表。


8. 保護範圍寫不進去

情境 :

    指定的 Range 被保護或你沒有權限

修法 :

    取消保護、或確認腳本執行身分(安裝觸發器可指定作者身分執行);或改寫入不受保護的區塊。


9.  getLastRow() 被「格式」誤判

情境 :

    尾端有殘留格式(背景色/框線),getLastRow() 以為有資料

修法 :

    尾端清除格式 clearFormat(),或以關鍵欄內容判斷(讀取該欄 getValues() 檢查非空)。


10. 日期/數字型別錯亂

情境 :

    getDisplayValues() 拿到字串再計算失真;或 getValues() 的 Date 物件被當字串處理

修法 :

    計算用 getValues();顯示或輸出 CSV 才用 getDisplayValues()。需要時明確 Number()/String()/Utilities.formatDate() 轉型。


張貼留言 (0)
較新的 較舊