Google 試算表:取得儲存格範圍的值 getValues()

 



你可能已經用過 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」工作表。


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