Google 試算表:取得Row 列的編號 getRow()

 


有沒有這種經驗:同事在 E 欄改了「完成」,你想讓腳本自動把 F 欄填上完成時間、整列變淡綠,還要順便檢查數量是否合法。

其實核心就一句:先知道「哪一列被改到」。getRow() 回傳當前選區左上角的列號(1-based),讓你把所有動作鎖在正確那一列上。

本文從實務角度下手:帶你用 onEdit(e.range.getRow()) 做同列回寫、如何搭配 getColumn()、getNumRows() 算出範圍終點、以及大量操作時的效能做法(一次讀寫、由下而上刪列)。

也會把常見雷點攤開來說:把 Sheet 當 Range 用、把 0-based 陣列當 1-based 列、以為 getRow() 會理可見列順序…通通逐一拆解。看完,你就能把看似零碎的流程,串成穩定又好維護的自動化。希望本篇文章能夠幫助到需要的您。


目錄

{tocify} $title={目錄} 


為什麼是 getRow()?核心概念先釐清

所屬類別:Range(不是 Sheet、不是 Spreadsheet)。

回傳型別:Number(整數)。

起算方式:1 為第一列(Apps Script 的試算表列、欄皆為 1-based)。

對應位置:若 Range 是多格(例如 B3:D8),getRow() 回傳 3,因為它取左上角儲存格(B3)的列。

與其它親戚:

    getColumn():回傳左上角欄號。

    getLastRow():整張工作表最後一列有資料的列號(屬於 Sheet)。

    getMaxRows():工作表目前的總列數(屬於 Sheet)。

    Advanced Sheets API 的 rowIndex 是 0-based,跟 Apps Script 的 getRow() 完全不同,別混。

一句話總結:getRow() 幫你在「我現在處理的這個範圍」與「整張表的絕對列號」之間搭橋。


最常見的四種使用場景

onEdit:依編輯列決定流程

function onEdit(e) {
  if (!e) return;                       // 保險起見
  const sh = e.range.getSheet();
  if (sh.getName() !== '表單') return;   // 只處理特定工作表

  const row = e.range.getRow();         // 重點:拿到被編輯的「列」
  const col = e.range.getColumn();

  // 範例:只有當使用者修改「狀態」欄(第 5 欄)才處理
  if (col === 5 && row > 1) {           // 跳過標題列
    const status = sh.getRange(row, 5).getValue();
    if (status === '完成') {
      sh.getRange(row, 6).setValue(new Date());  // 在「完成時間」欄寫入時間戳
    }
  }
}


略過標題列、只跑資料列

function processDataRows() {
  const sh = SpreadsheetApp.getActiveSheet();
  const last = sh.getLastRow();
  for (let r = 2; r <= last; r++) {   // 從第 2 列開始
    const val = sh.getRange(r, 1).getValue();
    // TODO: 對每一列做事
  }
}


搭配 getRow() 的思路是:當你從某個 Range 切入(例如 getActiveRange()),要判斷「是否為資料列」,最直覺就是 range.getRow() > 1。


定位多格範圍的左上角列

function topLeftRowDemo() {
  const sh = SpreadsheetApp.getActiveSheet();
  const range = sh.getRange('B3:D8');
  const topLeftRow = range.getRow(); // 3
  Logger.log(topLeftRow);
}


搭配 find 結果或條件篩選,回寫到同列其他欄

function updateSameRowByKey(key) {
  const sh = SpreadsheetApp.getActiveSheet();
  const values = sh.getRange(1, 1, sh.getLastRow(), 1).getValues(); // A 欄
  for (let i = 0; i < values.length; i++) {
    if (values[i][0] === key) {
      const row = i + 1; // 因為 getValues() 是 0-based 陣列
      sh.getRange(row, 2).setValue('Hit'); // 在同列 B 欄回寫
      break;
    }
  }
}


雖然這段沒有直接呼叫 getRow(),但概念類似:把相對索引換成絕對列號,以便同列寫入。


一步步操作:從 0 開始實作 getRow() 常見任務

情境 A:建立「狀態看板」欄位自動化

目的:使用者在「狀態」欄(E 欄)改成「完成」,同列自動填上完成時間、把整列底色改為淡綠。

步驟

    1.    開啟 Apps Script 編輯器 → 新增程式。

    2.    貼上:

function onEdit(e) {
  if (!e) return;
  const sh = e.range.getSheet();
  if (sh.getName() !== '任務清單') return;

  const row = e.range.getRow();
  const col = e.range.getColumn();

  // 標題列不處理
  if (row === 1) return;

  // 只在 E 欄(狀態)動作
  if (col === 5) {
    const status = sh.getRange(row, 5).getValue();
    if (status === '完成') {
      sh.getRange(row, 6).setValue(new Date()); // 完成時間(F 欄)
      sh.getRange(row, 1, 1, sh.getLastColumn())
        .setBackground('#e8f5e9'); // 整列淡綠
    } else {
      sh.getRange(row, 6).clearContent();
      sh.getRange(row, 1, 1, sh.getLastColumn())
        .setBackground(null); // 還原底色
    }
  }
}


    3.    儲存、回到試算表測試:修改 E 欄,觀察是否正確改動同一列。

關鍵點:e.range.getRow() 是判斷「哪一列被動到」的直覺解法;你無須再去搜尋座標。


情境 B:表單回應工作表,自動編號與驗證

目的:每次有新資料填入時,於同列自動帶入遞增編號、同時檢查必填欄。

步驟

    1.    表單連到試算表,假設回應寫在 回應 1 工作表。

    2.    使用「安裝觸發條件」→ 選擇「試算表 → 試算表變更(onChange)」或「表單提交(onFormSubmit)」;若走 onEdit 也可,但表單寫入通常用 onFormSubmit 最穩。

    3.    程式碼示意(onFormSubmit):

function onFormSubmit(e) {
  const sh = e.range.getSheet();     // 仍然可以用 e.range
  const row = e.range.getRow();      // 拿到此次寫入的起始列
  const lastCol = sh.getLastColumn();

  // 自動編號(假設 A 欄為流水號)
  const idCell = sh.getRange(row, 1);
  if (!idCell.getValue()) {
    const prev = sh.getRange(row - 1, 1).getValue();
    const nextId = (Number(prev) || 0) + 1;
    idCell.setValue(nextId);
  }

  // 必填檢查(例如 C 欄、D 欄)
  const mustC = sh.getRange(row, 3).getValue();
  const mustD = sh.getRange(row, 4).getValue();
  if (!mustC || !mustD) {
    sh.getRange(row, 1, 1, lastCol).setBackground('#ffebee'); // 淡紅
    // 也可寄信或寫入備註
    sh.getRange(row, 2).setNote('缺少必填欄位,請補齊');
  }
}


關鍵點:表單一次寫入可能是「整列」或多欄範圍的左上角,getRow() 讓你快速定位「此次寫入的起點列」。


情境 C:多選區編輯(合併儲存格/矩形範圍)

目的:使用者一次選了 B3:D8 來套用格式,我們只想以左上角列當基準做判斷。

function formatByTopLeft() {
  const range = SpreadsheetApp.getActiveRange();
  const row = range.getRow();      // 3
  const col = range.getColumn();   // 2 (B)
  // 例如:僅在選取的左上列做標記
  const sh = range.getSheet();
  sh.getRange(row, 1, 1, sh.getLastColumn()).setBackground('#fff3e0');
}


與其它方法的比較與搭配

getRow() vs getLastRow()

    前者回傳「這個範圍的左上角列」。

    後者回傳「整張工作表最後有資料的列」。

    常見搭配:用 getRow() 知道事件發生在哪一列;用 getLastRow() 來界定資料邊界。

getRow() vs 陣列索引(getValues())

    getValues() 讀出二維陣列(0-based),需要自算 rowIndex + 起始列 才能回到絕對列。

    getRow() 直接給你絕對列,適合事件導向、互動式處理。

getRow() + getColumn()

    一次取得左上角座標,對於要「同步寫回同列/同欄」尤其好用。

getRow() + 篩選器/隱藏列

    getRow() 回的永遠是絕對列號,與是否被隱藏、是否通過篩選無關。若你需要「可見列序號」,就得額外計算。


實務範例包

避免刪除標題列的安全檢查

function safeDeleteRow(row) {
  const sh = SpreadsheetApp.getActiveSheet();
  if (row <= 1) throw new Error('不允許刪除標題列');
  sh.deleteRow(row);
}


批次刪列(避免索引位移的寫法)

function deleteRowsByStatus() {
  const sh = SpreadsheetApp.getActiveSheet();
  const last = sh.getLastRow();
  // 從下往上刪,避免列位移
  for (let r = last; r >= 2; r--) {
    const status = sh.getRange(r, 5).getValue();
    if (status === '作廢') {
      sh.deleteRow(r);
    }
  }
}


欄位驗證:只對被編輯列生效

function onEdit(e) {
  if (!e) return;
  const sh = e.range.getSheet();
  if (sh.getName() !== '訂單') return;

  const row = e.range.getRow();
  if (row === 1) return; // 標題

  // 規定:數量(C 欄)必須為正整數
  const qty = sh.getRange(row, 3).getValue();
  if (!(Number.isInteger(qty) && qty > 0)) {
    sh.getRange(row, 3).setBackground('#ffebee').setNote('數量需為正整數');
  } else {
    sh.getRange(row, 3).setBackground(null).setNote('');
  }
}


合併儲存格的注意(只會回左上角)

function mergedCellsDemo() {
  const sh = SpreadsheetApp.getActiveSheet();
  // 假設 B3:D3 合併
  const range = sh.getRange('B3:D3');
  Logger.log(range.getRow()); // 3
  // 若你需要「涵蓋幾列」,請用 getNumRows()
  Logger.log(range.getNumRows()); // 1
}


getRow() 鎖定當列資料做 API 呼叫

function onEditCallApi(e) {
  if (!e) return;
  const sh = e.range.getSheet();
  if (sh.getName() !== '出貨') return;

  const row = e.range.getRow();
  const tracking = sh.getRange(row, 4).getValue(); // 物流單號
  if (!tracking) return;

  // 以單號呼叫外部 API(假裝)
  const info = { status: 'In Transit', eta: '2025-10-10' };
  sh.getRange(row, 5, 1, 2).setValues([[info.status, info.eta]]);
}

效能與維護:讓 getRow() 走得更遠

1.    減少單格 I/O:需要讀/寫多欄時,盡量以區塊方式一次 getRange(...).getValues() / setValues(),比逐格快非常多。

2.    條件先算清楚再動手:先用 getRow()、getColumn() 判斷是否在你關心的區域,再去讀寫資料,避免多餘操作。

3.    避免在 onEdit 中做重計算:像是整表 getDataRange().getValues() 這類重操作,盡量不要在每次編輯都跑。

4.    錯誤處理與備註:對於不合規的輸入,用 setNote() 說明原因;未必每次都要 throw。

5.    由下而上刪列:前面示範過,避免索引位移導致「跳刪」。

針對不同 sheet 做白名單:if (sh.getName() !== 'xxx') return; 是維護的救命繩。


getRow() 雷點清單

1.    把 getRow() 用在 Sheet 上

症狀:TypeError: getRow is not a function

因為 getRow() 是 Range 的方法。若你只有工作表,先拿範圍:sheet.getRange(r, c)。

2.    把 0-based 陣列當 1-based 列

用 getValues() 迴圈時,i 從 0 開始;要換成列號,請 i + 起始列。最常見 off-by-one 錯誤就在這。

3.    與 Advanced Sheets API 混用

Advanced API 的 rowIndex 是 0-based。Apps Script getRow() 是 1-based。跨 API 時務必轉換。

4.    合併儲存格的誤會

getRow() 永遠回左上角列。以為會回所有涵蓋列,會寫到錯地方。需要範圍高度請用 getNumRows()。

5.    篩選/隱藏列造成的期望落差

getRow() 不理會可見與否;它給的是絕對列號。如果你想「第幾個可見列」,得自己計算可見列清單。

6.    在 onEdit 中大量操作整表

用戶每打一個字就掃全表,腳本很快會被節流或超時。先過濾:if (col !== 你要的欄) return;。

7.    批次刪列時由上而下

會因列位移漏刪。記得由下而上處理。

8.    假設 e 一定存在

手動執行 onEdit、或其他入口呼叫沒有事件物件 e,直接用會報錯。先 if (!e) return;。

9.    以為工作表永遠有標題列

你的腳本可能在空表就被觸發。請對 row === 1、getLastRow() === 0 這類邊界情況做保護。

10.    把 getRow() 當成資料高度

它不是範圍高度,請用 getNumRows();要終點列,用 getRow() + getNumRows() - 1。


除錯心法與排查清單

1.    先 Logger.log(e.range.getA1Notation(), e.range.getRow(), e.range.getColumn()) 看看你以為的座標是不是事實。

2.    檢查工作表名:if (sh.getName() !== 'XXX') return; 很常救命。

3.    確認你的觸發型別:onEdit、onChange、onFormSubmit 行為不同。

4.    若牽涉到合併儲存格,先取消合併再試,驗證是否是根因。

5.    大量操作前,先把資料讀成陣列在記憶體算完,再一次 setValues()。


問題集

Q1:選了 B3:D8,getRow() 回多少?

    A:回 3。永遠是左上角列。

Q2:想要知道選取範圍的最後一列?

    A:const endRow = range.getRow() + range.getNumRows() - 1;

Q3:為什麼 getRow() 在我這邊報 not a function?

    A:你很可能對著 Sheet 或 Spreadsheet 呼叫了它。請先拿到 Range。

Q4:用篩選器後,getRow() 會跳號嗎?

    A:會,以絕對列號回傳,與可見與否無關。

Q5:我用表單提交觸發,e.range.getRow() 一定等於最後一列嗎?

    A:不一定,但通常會是新寫入的起始列。實務上還是用 e.range 為準,別硬推 getLastRow()。


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