Google 試算表:刪除複數列 deleteRows()

 



整理試算表最怕「刪到手抖」:資料一多、空白列夾雜、狀態還有作廢與過期,滑一滑就迷路。

其實 Google Apps Script 的 deleteRows() 很適合處理「一段一段」的連續刪除,搭配先讀資料、篩出目標、再合併區段由下往上刪,就能又快又穩。

本篇文章用日常語氣帶你走過完整流程:從基礎語法到常見情境(空白列、作廢標記、日期過期)、效能心法、乾跑確認與記錄備份,全都給你。想把表整理到清清爽爽,從這裡開始。希望本篇文章能夠幫助到需要的您。


目錄

{tocify} $title={目錄} 


deleteRows() 是什麼?和 deleteRow() 有何不同?

在 Google Apps Script 裡,工作表(Sheet)物件提供兩種刪列方法:

    deleteRow(rowPosition):刪除單一列。

    deleteRows(rowPosition, howMany):從 rowPosition 起,連續刪除 howMany 列。

重點:Apps Script 的列、欄索引是從 1 開始(1-based)。第 1 列就是最上面那列。

基本語法:

const sheet = SpreadsheetApp.getActive().getSheetByName('工作表1');
// 從第 5 列開始,連續刪除 3 列(第 5、6、7 列會被刪掉)
sheet.deleteRows(5, 3);


參數限制與規則:

    rowPosition ≥ 1,且不得超過目前總列數。

    howMany ≥ 1,且 rowPosition + howMany - 1 不得超過總列數。

    刪除會直接改變資料位置與索引;後續操作要小心行號位移。


什麼情境適合用 deleteRows()?

1.    清除中間的空白列:整理匯入資料、去除視覺噪音。

2.    刪除符合條件的資料列:例如狀態=「作廢」、金額=0、標記=「已處理」。

3.    依日期或版本清檔:只保留最近 N 天資料,舊的全刪。

4.    縮小檔案體積:刪除尾端大量空列(常見於拖拉填滿後留下幾萬列空白)。

5.    資料重建前的重整:管線跑完,先清掉暫存列再寫入。


開始前的準備與建議流程

1.    建立測試副本:避免在正式表上直接實驗。

2.    開啟 Apps Script 編輯器:

        試算表 → 擴充功能 → Apps Script。

3.    加上自訂選單(可選):讓不懂程式的同事也能按鈕執行。

4.    權限:第一次執行會要求授權,一般情況僅需存取該試算表即可。

5.    資料備份:重要資料建議先複製到另一個工作表或檔案。


基礎範例:一次刪一段連續列

範例 1:從第 5 列起連刪 3 列

function demoDeleteRowsBasic() {
  const ss = SpreadsheetApp.getActive();
  const sheet = ss.getSheetByName('工作表1');
  sheet.deleteRows(5, 3);
}


範例 2:刪除尾端的多餘空白列(常見清檔)

function trimTrailingEmptyRows() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const lastRow = sheet.getLastRow();           // 最後一列「有資料」的位置
  const maxRows = sheet.getMaxRows();           // 工作表實際持有的列數
  const toDelete = maxRows - lastRow;
  if (toDelete > 0) {
    sheet.deleteRows(lastRow + 1, toDelete);    // 從有資料的下一列開始刪
  }
}


心法:getLastRow() 只看「有內容」的最後一列;很多人以為刪到最後一列 = 乾淨,實際上尾巴常殘留一大段空列,要搭配 getMaxRows() 才能一次砍掉。


條件刪除:先找出要刪的行,再由下而上刪

為何「由下而上」? 因為刪列會造成行號上移,若從上往下刪,下一個目標列號會改變,容易跳刪或誤刪。

範例 3:刪除「狀態」欄為「作廢」的列

假設第 1 列是標題,狀態 欄在第 3 欄(C 欄)。

function deleteRowsByStatus() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const startRow = 2;                     // 跳過標題列
  const lastRow = sheet.getLastRow();
  if (lastRow < startRow) return;

  const statusCol = 3;                    // C 欄
  const range = sheet.getRange(startRow, 1, lastRow - startRow + 1, sheet.getLastColumn());
  const values = range.getValues();

  const toDelete = [];
  for (let i = 0; i < values.length; i++) {
    const row = values[i];
    if ((row[statusCol - 1] + '').trim() === '作廢') {
      // 真實行號 = startRow + i
      toDelete.push(startRow + i);
    }
  }

  // 由下而上刪
  for (let i = toDelete.length - 1; i >= 0; i--) {
    sheet.deleteRow(toDelete[i]);         // 單列刪除更直覺
  }
}


範例 4:刪除整列都「空白」的資料列

function deleteEmptyRows() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const startRow = 2; // 保留標題
  const lastRow = sheet.getLastRow();
  if (lastRow < startRow) return;

  const lastCol = sheet.getLastColumn();
  const range = sheet.getRange(startRow, 1, lastRow - startRow + 1, lastCol);
  const values = range.getValues();

  const toDelete = [];
  values.forEach((row, i) => {
    const isEmpty = row.every(cell => (cell === '' || cell === null));
    if (isEmpty) toDelete.push(startRow + i);
  });

  for (let i = toDelete.length - 1; i >= 0; i--) {
    sheet.deleteRow(toDelete[i]);
  }
}


範例 5:刪除「日期」早於指定日的列

假設日期在第 1 欄(A 欄),格式為可被 new Date() 解析的值。

function deleteRowsBeforeDate() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const startRow = 2;
  const lastRow = sheet.getLastRow();
  if (lastRow < startRow) return;

  const cutoff = new Date('2024-01-01'); // 門檻日
  const dateCol = 1;
  const range = sheet.getRange(startRow, 1, lastRow - startRow + 1, sheet.getLastColumn());
  const values = range.getValues();

  const toDelete = [];
  values.forEach((row, i) => {
    const v = row[dateCol - 1];
    const d = (v instanceof Date) ? v : new Date(v);
    if (!isNaN(d) && d < cutoff) toDelete.push(startRow + i);
  });

  for (let i = toDelete.length - 1; i >= 0; i--) {
    sheet.deleteRow(toDelete[i]);
  }
}


效能最佳實務:加速、安全、少踩坑

1.    一次讀整塊再判斷

        使用 getRange(...).getValues() 取得矩陣,在記憶體決定要刪哪些列,比逐列讀取快很多。

2.    由下而上刪

        列索引不會因上方刪除而變動,避免跳刪/誤刪。

3.    合併連續刪除

        若預計刪除的行號是連續區段,優先選擇 deleteRows(start, len) 取代多次 deleteRow(),API 呼叫更少、耗時更短。

        範例:把 [10,11,12, 20,21, 50] 轉為區段 10x3, 20x2, 50x1 來刪。

function deleteByRanges(sheet, rows) {
  // rows 已排序的行號陣列,例如 [10,11,12,20,21,50]
  if (rows.length === 0) return;

  // 轉為連續區段
  const ranges = [];
  let start = rows[0], prev = rows[0];
  for (let i = 1; i < rows.length; i++) {
    if (rows[i] === prev + 1) {
      prev = rows[i];
    } else {
      ranges.push([start, prev]);
      start = prev = rows[i];
    }
  }
  ranges.push([start, prev]);

  // 由後往前刪(避免區段上移)
  for (let i = ranges.length - 1; i >= 0; i--) {
    const [s, e] = ranges[i];
    sheet.deleteRows(s, e - s + 1);
  }
}


4.    避免在迴圈中頻繁呼叫 Sheet API

        先算好名單再刪;不要在每個 for 內都 deleteRow()(除非很少量)。

5.    迴避保護範圍

        若你的檔有「受保護的範圍」或代理權限不夠,刪除可能失敗。必要時先檢查或暫時取消保護,或改只刪未受保護區段。

6.    與篩選器/資料檢視的互動

        標準「篩選器」並不會擋你刪列,但視覺上容易誤判。建議在取消篩選或切到所有列顯示時執行,避免混淆。

7.    配額與長時間作業

        如果要刪的列數很多(上萬),建議分批(例如一次處理 1000 列),中間可 Utilities.sleep(200) 休息一下,減少超時風險。


綜合案例:三段需求一次完成

需求:

        1.    刪除 C 欄=「作廢」

        2.    刪除整列空白

        3.    只保留最近 90 天,舊的刪掉

        4.    最後把尾端空列收乾淨

function cleanupSheet() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const header = 1;
  const startRow = header + 1;
  const lastRow = sheet.getLastRow();
  if (lastRow < startRow) {
    trimTrailingEmptyRows();
    return;
  }

  const lastCol = sheet.getLastColumn();
  const values = sheet.getRange(startRow, 1, lastRow - startRow + 1, lastCol).getValues();

  const STATUS_COL = 3; // C
  const DATE_COL = 1;   // A
  const cutoff = new Date();
  cutoff.setDate(cutoff.getDate() - 90);

  const toDelete = [];

  for (let i = 0; i < values.length; i++) {
    const row = values[i];
    const rowNum = startRow + i;

    // 1) 狀態=作廢
    const status = (row[STATUS_COL - 1] + '').trim();
    if (status === '作廢') { toDelete.push(rowNum); continue; }

    // 2) 整列空白
    const isEmpty = row.every(c => c === '' || c === null);
    if (isEmpty) { toDelete.push(rowNum); continue; }

    // 3) 日期早於 cutoff
    const v = row[DATE_COL - 1];
    const d = (v instanceof Date) ? v : new Date(v);
    if (!isNaN(d) && d < cutoff) { toDelete.push(rowNum); }
  }

  // 轉為區段並刪
  toDelete.sort((a, b) => a - b);
  deleteByRanges(sheet, toDelete);

  // 收尾:砍掉尾端空列
  trimTrailingEmptyRows();
}


與其他方法的比較

方法 適用情境 優點 注意
deleteRow(n) 少量、零散刪除 直覺 多次呼叫效率較差
deleteRows(start, len) 連續大區段 呼叫少、較快 需要先把行號整併成區段
改寫資料而非刪列(寫回過濾後的新資料) 大規模重整 可一次重建乾淨表 會重排資料,需小心格式/公式


加入自訂選單,讓同事可一鍵執行

function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu('清理工具')
    .addItem('清掉作廢/空白/過期', 'cleanupSheet')
    .addSeparator()
    .addItem('修剪尾端空列', 'trimTrailingEmptyRows')
    .addToUi();
}


常見錯誤與「雷點」總整理

1.    從上往下刪 → 行號位移

        症狀:跳刪、漏刪、錯刪。

        解法:由下往上,或先合併成區段用 deleteRows() 一次刪。

2.    參數越界

        錯誤訊息常見為 Range not found、The number of rows in the range must be at least 1。

        檢查:rowPosition >= 1、howMany >= 1、rowPosition + howMany - 1 <= maxRows。

3.    誤刪標題列

        忘了 startRow = 2。習慣:把標題列當常數,或先檢查第一列後再篩選。

4.    日期判斷失敗(時區/字串格式)

        new Date('2024-01-01') 在不同地區可能被解讀成 UTC 造成前一天或當天,或字串格式解析失敗。

        建議:儘量用真正的日期物件(工作表儲存格為日期格式),或用 Utilities.formatDate 規格化。

5.    空白判斷只看一欄

        以一欄為準容易誤判。整列 every() 判斷更穩。若有隱藏公式,請用 getDisplayValues() 或檢查 trim()。

6.    資料受保護

        受保護範圍(你沒權限)會使刪除失敗。

        解法:排除該區、調整權限、或先取消保護。

7.    濾鏡/資料檢視造成視覺混亂

        看似只刪到可見列,實際底層刪到其他列。

        習慣:執行前關閉篩選,或在乾淨視圖操作。

8.    迴圈中大量呼叫 API

        每刪一列就呼叫一次,速度會很慢且容易超時。

        解法:先收集行號 → 合併區段 → deleteRows() 批次刪。

9.    把「刪列」當成「清空」

        想保留列格式卻用了刪除。

        替代:僅清值用 clear() / clearContent();刪列會把該列整個結構拿掉。

10.    忽略 getMaxRows()

        只刪到 getLastRow() 以為乾淨,尾端實際還有數千空列。

        解法:搭配 trimTrailingEmptyRows()。


除錯與驗證清單

        先在副本測試,並在每次刪除前 Logger.log(toDelete) 檢查名單。

        加上乾跑(dry-run)模式:列出將被刪除的行號,不真的刪。

function dryRun(toDelete) {
  SpreadsheetApp.getUi().alert('將刪除行號:\n' + toDelete.join(', '));
}


        建議把「條件判斷」與「刪除動作」拆函式,單元測試更容易。

        若遇超時:改成分批處理,每批處理完 SpreadsheetApp.flush()。


問題集

Q1:能只刪「可見的」列嗎?

        Apps Script 沒有直接「只刪可見列」的 API。你需要自己依條件挑選行號,或先用公式/輔助欄標示,再由程式依標示刪除。

Q2:大規模(>5 萬列)要怎麼刪比較穩?

        分批讀、分批刪,每批合併區段;中間 Utilities.sleep(200);必要時將資料分頁到多個工作表處理。

Q3:刪除會破壞參照公式嗎?

        會影響相對位址的範圍,特別是整列被移除後,引用範圍可能縮短或錯位。建議:

        將公式集中在標題列或固定區塊。

        重要報表使用 命名範圍 降低風險。

Q4:想保留格式,只清掉資料?

        用 clear()(清含格式)或 clearContent()(只清內容)。


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