Google 試算表:取得最後一列列號 getLastRow()

 


如果你曾用 appendRow() 一直寫,一陣子後檔案開始變慢;或你用 getLastRow() 續寫,卻忽然發現資料出現在超遠的行數——別懷疑,你踩到的是同一串坑:合併儲存格、隱藏列、公式回傳空字串、還有過去貼上的殘留內容。

本篇文章盡量說明內容,將 getLastRow() 的行為攤開說:它怎麼判斷「有內容」、跟 getMaxRows()/getDataRange() 差在哪、為什麼單欄續寫不能偷懶。接著給你三種抓「某欄最後一列」的方法、批次 setValues() 的正確打開方式,以及寫入前的容量檢查與尾端清理。希望本篇文章能幫助到需要的您。


目錄

{tocify} $title={目錄} 


getLastRow() 是什麼?

簡單說:

        Sheet.getLastRow() 會回傳工作表中「有內容」的最後一列列號(1 起算)。

空工作表回傳 0。

        只要任何一欄有內容,它都視為那一列「有內容」。

        含 公式 的儲存格就算公式結果顯示空字串(""),仍視為「有內容」。

        受篩選/隱藏的列 一樣會被計入。

別搞混:

        getLastRow():最後「有內容」的列。

        getMaxRows():整張表目前總共有幾列(不管有沒有內容)。

        getDataRange():整張表目前「有資料」的矩形範圍。


基礎語法與最小可行範例

function demoGetLastRow() {
  const sh = SpreadsheetApp.getActive().getSheetByName('工作表1');
  const lastRow = sh.getLastRow(); // 例如 37
  Logger.log('最後有內容的列:' + lastRow);
}


續寫到下一列(最常用)

function appendAtEnd() {
  const sh = SpreadsheetApp.getActiveSheet();
  const lastRow = sh.getLastRow();
  const nextRow = lastRow + 1 || 1; // 空表時 lastRow=0 → 從第1列開始
  const values = [['A001', '王小明', new Date(), 1200]]; // 一筆資料(一列多欄)
  sh.getRange(nextRow, 1, values.length, values[0].length).setValues(values);
}


口訣:先抓 lastRow,再 +1 寫入。空表特例要顧到(回傳 0)。


一步步實作:打造「出貨紀錄」自動續寫流程

目標:每次執行,就把一筆資料安全地寫到資料區最後面。

步驟 1|準備試算表

新增一張工作表(例如「出貨紀錄」)。

第 1 列當表頭:訂單編號|客戶名稱|出貨日|金額|備註。

步驟 2|建立 Script

在試算表 → 擴充功能 → Apps Script → 新增檔案,貼上:

function addShipment(orderId, customer, amount, note) {
  const sh = SpreadsheetApp.getActive().getSheetByName('出貨紀錄');
  if (!sh) throw new Error('找不到工作表:出貨紀錄');

  // 1) 取得最後列
  const lastRow = sh.getLastRow();
  const nextRow = lastRow + 1 || 1;

  // 2) 若是空表,先自動補上表頭
  if (lastRow === 0) {
    sh.getRange(1, 1, 1, 5).setValues([['訂單編號','客戶名稱','出貨日','金額','備註']]);
  }

  // 3) 寫入一列資料(注意:setValues 需 2D 陣列)
  const values = [[orderId, customer, new Date(), amount, note || '']];
  sh.getRange(sh.getLastRow() + 1, 1, 1, 5).setValues(values);

  // 4) 基本防呆:金額數字格式化
  sh.getRange(sh.getLastRow(), 4).setNumberFormat('#,##0.00');
}


步驟 3|試跑

function testAddShipment() {
  addShipment('A-20251006-001', '好客戶股份有限公司', 12800, '急件');
}


步驟 4|最佳化(大量寫入)

function bulkAddShipments(rows) {
  // rows = [['A001','王小明', new Date(), 1200,''], ...]
  const sh = SpreadsheetApp.getActive().getSheetByName('出貨紀錄');
  if (!sh) throw new Error('找不到工作表:出貨紀錄');

  if (sh.getLastRow() === 0) {
    sh.getRange(1, 1, 1, 5).setValues([['訂單編號','客戶名稱','出貨日','金額','備註']]);
  }
  const insertAt = sh.getLastRow() + 1;
  sh.getRange(insertAt, 1, rows.length, 5).setValues(rows);
}

一次寫多筆時,避免逐列 setValues(效能差)。用批次寫入:


只想看某欄的「最後一列」?三種做法

getLastRow() 是看「整張表」。實務上常常只關心某一欄(例如 A 欄)。以下三招由快到慢:

作法 A|getNextDataCell(UP)(快、省記憶體)

function lastRowOfColumn(sheet, col) {
  const candidate = sheet.getRange(sheet.getMaxRows(), col)
                         .getNextDataCell(SpreadsheetApp.Direction.UP)
                         .getRow();
  // 若整欄都空,getNextDataCell 會回到第1列;再確認一下是否真的空
  return sheet.getRange(candidate, col).isBlank() ? 0 : candidate;
}


作法 B|向下掃一次,再向上找第一個非空(中)

function lastRowByColumn_scan(sheet, col) {
  const max = sheet.getMaxRows();
  const values = sheet.getRange(1, col, max, 1).getValues();
  for (let i = values.length - 1; i >= 0; i--) {
    if (values[i][0] !== '') return i + 1;
  }
  return 0;
}


作法 C|filter(String).length(簡單但重)

function lastRowByColumn_filter(sheet, colA1) {
  const values = sheet.getRange(colA1 + ':' + colA1).getValues(); // 讀進整欄
  return values.filter(r => r[0] !== '').length;
}
// 用法:lastRowByColumn_filter(sh, 'A')


大欄位+大量列時,作法 C 會讀超多空白列,不建議在大檔案用。


只計算「有顯示內容」的最後列(忽略公式空字串)

有時你想忽略「結果為空字串的公式列」。做法是取 display values 從底往上找第一列有顯示內容的列:


function lastVisibleRow(sheet) {
  const range = sheet.getDataRange().getDisplayValues(); // 只取有資料的矩形
  for (let r = range.length - 1; r >= 0; r--) {
    if (range[r].some(v => v !== '')) return r + 1;
  }
  return 0;
}


常見錯誤與雷點

1.    把 getLastRow() 當「某欄最後列」用

        它看的是整張表;右邊某欄有字,它就算進去。要看特定欄請用上述 A/B/C 作法。

2.    空表沒處理

        空表回 0,直接 lastRow + 1 還好,但如果你先拿 getRange(lastRow, 1, ...) 就炸了。請先判斷 === 0。

3.    表頭算進資料

        若第 1 列是表頭,常見寫法會從 lastRow + 1 開始沒錯;但讀取/統計時別把第 1 列當資料列。

4.    公式顯示空字串仍被當成有內容

        這是預期行為。若你要「忽略只含公式但結果為空的列」,用 getDisplayValues() 重新判定。

5.    篩選/隱藏讓你以為沒有資料

        getLastRow() 不理你有沒有被篩掉或隱藏,它照樣抓到最底的那列。續寫時要注意可能寫到視野外。

6.    合併儲存格

        上方合併、下方空白,getLastRow() 仍可能回到很下面(因為合併區域的邊界)。避免在資料區大量合併。

7.    逐列 setValue 導致超慢

        續寫多筆時務必組成 2D 陣列,一次 setValues。效能差異巨大。

8.    Range 尺寸不符

        setValues 的列數、欄數必須完全相等於 Range 定義,否則丟錯:

        The number of columns in the data does not match the number of columns in the range.

9.    把 getMaxRows() 誤認為最後資料列

        getMaxRows() 是表的總列數,通常比 getLastRow() 大很多。續寫請用 getLastRow()。

10.    留白尾巴拖慢效能

        一不小心把資料貼到第 5 萬列,之後每次讀整欄都被迫掃到 5 萬。清尾巴是必要功課(見下一節)。

11.    clearContent() 與 deleteRows() 差異沒搞懂

        clearContent() 清值但保留列;deleteRows() 直接移除列數。要重置「有效範圍」時,通常要刪列或清尾。

12.    單一欄續寫卻用整表 lastRow

        想接在 A 欄最後一筆後面,請抓 A 欄的 lastRow;用整表 lastRow 會被右邊零星資料拉著走。


清理與維護:把「看不到的尾巴」處理掉

清掉尾端空白列(讓表更輕)

function trimTrailingEmptyRows(sheet) {
  const lastDataRow = sheet.getLastRow(); // 或用 lastVisibleRow(sheet)
  const maxRows = sheet.getMaxRows();
  if (maxRows > lastDataRow) {
    sheet.deleteRows(lastDataRow + 1, maxRows - lastDataRow);
  }
}


續寫前自動擴充列數(避免超出)

function ensureRowsForInsert(sheet, rowsNeeded) {
  const needed = (sheet.getLastRow() + rowsNeeded) - sheet.getMaxRows();
  if (needed > 0) sheet.insertRowsAfter(sheet.getMaxRows(), needed);
}


實務技巧合集

安全續寫(含空表表頭、一次多筆)

function safeAppend(sheetName, rows2d, header) {
  const sh = SpreadsheetApp.getActive().getSheetByName(sheetName);
  if (!sh) throw new Error('找不到:' + sheetName);

  if (sh.getLastRow() === 0 && header) {
    sh.getRange(1, 1, 1, header.length).setValues([header]);
  }

  const start = sh.getLastRow() + 1;
  ensureRowsForInsert(sh, rows2d.length);
  sh.getRange(start, 1, rows2d.length, rows2d[0].length).setValues(rows2d);
}


只在某欄續寫(例如 A 欄當索引)

function appendByColumnA(valuesRow) {
  const sh = SpreadsheetApp.getActiveSheet();
  const lastA = lastRowOfColumn(sh, 1); // A 欄
  const next = lastA + 1 || 1;
  sh.getRange(next, 1, 1, valuesRow.length).setValues([valuesRow]);
}


找出「任一欄有顯示內容」的最後列(忽略公式空字串)

function lastVisibleRowInRange(sheet, startCol, numCols) {
  const maxR = sheet.getMaxRows();
  const range = sheet.getRange(1, startCol, maxR, numCols).getDisplayValues();
  for (let r = range.length - 1; r >= 0; r--) {
    if (range[r].some(v => v !== '')) return r + 1;
  }
  return 0;
}


日誌與除錯:一鍵檢查三值

function debugRows() {
  const sh = SpreadsheetApp.getActiveSheet();
  Logger.log({
    lastRow: sh.getLastRow(),
    lastVisibleRow: lastVisibleRow(sh),
    maxRows: sh.getMaxRows()
  });
}


效能觀念

1.    少讀多寫、整批操作:

       能一次拿 getDataRange().getValues() 就不要多次 getValue();能一次 setValues() 就不要迴圈寫。

2.    避免不必要的整欄讀取:

        A:A 會讀到 getMaxRows() 那麼長;先用 getLastRow() 縮小範圍。

3.    快取 lastRow:一段流程內需要多次用到,就放變數,不要一直呼叫。

4.    清尾巴:刪掉多餘空白列,getDataRange() 會更精準、更輕。

5.    少用 onEdit 重度運算:

        若每次編輯都掃表,會卡。能延後就延後、能批次就批次。


問題集

Q1:為什麼 getLastRow() 比我眼睛看到的還要大?

    多半是右側或下方某欄有值、公式、或被合併影響。用 lastVisibleRow() 檢查「顯示內容」的最後列,並清尾巴。

Q2:公式回傳空字串也算內容嗎?

    是。格內有公式就是「有內容」。想忽略請改判斷 getDisplayValues()。

Q3:我只想接在 A 欄的最後一個有值下面,怎麼做?

    用 getNextDataCell(UP) 或文中的 lastRowOfColumn() 方法。

Q4:appendRow() 可以嗎?

    可以,但它一次只寫一列、且彈性較低。大量資料建議自行組成 2D 陣列搭配 setValues(),效能好很多。

Q5:空表寫入會不會失敗?

    getLastRow() 為 0 時,lastRow + 1 是 1,不會有事。但你若直接 getRange(0, …) 就會炸。記得防呆。


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