你可能已經用過 getValues(),也可能只是在別人範例裡看過它一眼。無論如何,想把 Google 試算表玩到順手,這個方法值得完整認識。它返還的是「二維陣列」:每一列就是一筆,每一欄就是一個欄位。懂了這個結構,你能更有效率地做資料清洗、彙整、比對,再配合 setValues() 一次寫回,腳本速度會明顯提升。
本篇文章用實務視角切入:從如何選對 Range,到如何將資料轉成物件陣列、如何處理日期與空白,再到什麼情況應改用 getDisplayValues() 或 getFormulas()。中途會把幾個經典坑點開。希望本篇文章能夠幫助到需要的您。
目錄
{tocify} $title={目錄}
為什麼要學 getValues()
在 Apps Script 操作 Google 試算表時,所有「把資料抓進程式」的工作,幾乎都繞不開 getValues()。它會把一個 Range 的內容,以二維陣列一次抓回來。你要做報表、比對、清洗、轉 JSON、打 API、寫回表單資料,全靠它打底。懂它,就能讓程式跑得快、錯得少。
getValues() 是什麼?回傳結構與特性
來源:
SpreadsheetApp.getActive().getSheetByName('工作表名').getRange(起始列, 起始欄, 高度, 寬度).getValues()
回傳型態:any[][](二維陣列),每一列是一個陣列,每一欄是一個元素。
空白儲存格:回傳空字串 ""(不是 null 也不是 undefined)。
日期:以 Date 物件回傳(非字串)。
數字:以 number 回傳(格式化與顯示樣式不影響原值)。
公式:回傳「公式計算後的值」。若要拿公式文字,用 getFormulas()。
不可變? 回傳陣列你可以在記憶體中自由處理,但不會自動回寫到表格;要寫回請用 setValues()。
實務讀取 10 式(從新手到進階)
讀一個固定區塊
// A1:D10
const data = sh.getRange(1, 1, 10, 4).getValues();
讀到最後一列/欄(動態範圍)
const lastRow = sh.getLastRow();
const lastCol = sh.getLastColumn();
const data = sh.getRange(1, 1, lastRow, lastCol).getValues();
只抓「有效資料區」:以標題列為基準
const headerRow = 1;
const lastRow = sh.getLastRow();
const lastCol = sh.getLastColumn();
const data = sh.getRange(headerRow + 1, 1, Math.max(lastRow - headerRow, 0), lastCol).getValues();
只抓某一欄(例如 A 欄)
const colA = sh.getRange(1, 1, sh.getLastRow(), 1).getValues().flat();
flat() 把 [[a],[b],[c]] 壓平成 [a,b,c],更好處理。
按條件過濾(例如狀態=“完成”)
const [header, ...rows] = sh.getDataRange().getValues();
const statusIdx = header.indexOf('狀態');
const done = rows.filter(r => r[statusIdx] === '完成');
轉換為物件陣列(以標題列為鍵)
function toObjects(values){
if (values.length === 0) return [];
const [header, ...rows] = values;
return rows.map(r => {
const obj = {};
header.forEach((k, i) => obj[k] = r[i]);
return obj;
});
}
// 用法
const objs = toObjects(sh.getDataRange().getValues());
轉 JSON 並外送(報表 API)
const payload = JSON.stringify(toObjects(sh.getDataRange().getValues()));
// UrlFetchApp.fetch('https://api.example.com', {method:'post', payload});
讀日期並格式化(避免時區誤差)
const tz = Session.getScriptTimeZone(); // e.g. Asia/Taipei
const fmt = (d) => Utilities.formatDate(d, tz, 'yyyy-MM-dd');
const dates = sh.getRange(2, 1, sh.getLastRow()-1, 1).getValues()
.map(r => r[0] instanceof Date ? fmt(r[0]) : '');
取值+取顯示值:做對帳或匯出 CSV
const r = sh.getDataRange();
const raw = r.getValues(); // 原值(數字/日期型別)
const shown = r.getDisplayValues(); // 看到的字串(含格式)
搭配 setValues() 一次寫回(批次處理)
const data = sh.getRange(2, 1, sh.getLastRow()-1, 3).getValues(); // 抓 A:C
const out = data.map((row, i) => [row[0], row[1], row[2] * 1.05]); // 第三欄加 5%
sh.getRange(2, 1, out.length, out[0].length).setValues(out);
方法比一比:什麼時候不用 getValues()
| 方法 | 回傳 | 何時用 |
|---|---|---|
getValues() |
原始值(二維陣列) | 要做計算、比對、統計、轉物件 |
getDisplayValues() |
顯示字串(二維陣列) | 匯出人看得懂的文本、CSV、免管格式 |
getFormulas() |
公式字串(二維陣列) | 稽核或複製公式 |
getValue() |
單一儲存格 | 少用(效能差),只有單格需求時可用 |
getBackgrounds() 等 |
樣式相關 | 做格式稽核、品質控管 |
原則:大範圍請一次抓(getValues());避免回圈內多次呼叫 API。
效能最佳化:讓腳本跑 10 倍快
1. 批次思維:
一次 getValues() + 記憶體運算 + 一次 setValues(),勝過 N 次 getValue()/setValue()。
2. 減少範圍:只抓需要的列、欄,不要整張表一把抓。
3. 避免 for 迴圈裡反覆呼叫 Range:把資料先存變數,回圈處理陣列即可。
4. Cache(選用):重複運算的結果可 CacheService.put() 暫存。
5. 用 flush() 時機:只在需要立即看到寫回結果時用;平常不用反而更快。
6. 觸發器負載:表單提交觸發器 onFormSubmit 中,務必控制範圍與計算量。
常見錯誤訊息與排錯流程
錯誤 1:The number of rows in the data does not match the number of rows in the range.
情境:setValues() 時,資料陣列尺寸與 Range 尺寸不符。
排錯:Logger.log(out.length, out[0].length),對照 getRange(row, col, numRows, numCols)。
錯誤 2:Cannot read properties of undefined (reading 'length')
情境:空資料或變數沒值就操作。
排錯:先判斷 values.length、values[0] 是否存在。
錯誤 3:日期亂碼或字串化
情境:資料來自 CSV/外部貼上,日期其實是字串。
解法:new Date(row[idx]) 試轉;或改抓 getDisplayValues(),再做字串處理。
錯誤 4:Exception: Service Spreadsheets timed out
情境:抓太大範圍或在觸發器執行超時。
解法:縮小範圍、分批處理、加 Cache、改用時間驅動觸發器分段跑。
錯誤 5:權限不足
情境:第一次執行未授權、或嘗試存取他人檔案。
解法:確認檔案權限、重新授權、必要時用 DriveApp 找檔後 openById()。
10 大雷點清單(含避雷寫法)
1. 把整張表都抓下來
雷點:速度慢、超時。
避雷:鎖定範圍(標題列 + 有效資料列)。
2. 在迴圈裡呼叫 getValue()/setValue()
雷點:API 往返太多次。
避雷:先 getValues(),最後一次 setValues()。
3. 忘了二維陣列結構
雷點:values[0] 是一整列,values[0][0] 才是 A1。
避雷:先 Logger.log(values[0]) 看清楚。
4. 空白格處理不當
雷點:空白是 "",用 if (!cell) 判斷會把 0、false 也當空。
避雷:cell === "" 明確判斷。
5. 日期型別誤判
雷點:有時是 Date,有時是字串。
避雷:val instanceof Date 判斷;輸出統一格式化。
6. 寫回尺寸對不起來
雷點:setValues(out) 大小不符。
避雷:在寫回前 Logger.log(out.length, out[0].length)。
7. 表尾殘影(getLastRow 過大)
雷點:過去套過格式或貼過資料,邊界卡住。
避雷:清除多餘格式,或使用「找到最後一筆有效資料」邏輯(掃目標欄位)。
8. 用 getDisplayValues() 當原始值
雷點:數字、日期都變字串,後續計算怪怪的。
避雷:計算用 getValues(),輸出給人看才用 getDisplayValues()。
9. 觸發器做重運算
雷點:用 onEdit/onFormSubmit 跑全表,容易超時。
避雷:只處理被插入/修改的那一列,或延後批次跑。
10. 沒有錯誤處理與日誌
雷點:出事找不到原因。
避雷:try/catch + Logger.log,必要時寫到專用「Log」工作表。
