Google 試算表:刪除複數欄 deleteColumns()

 



在整理 Google 試算表時,最常遇到的不是「不夠資料」,而是「太多沒用的欄」。匯入 CSV、抓 API、同事加了一堆暫存欄,畫面越看越亂,報表也慢半拍。

這篇就帶你把焦點放在一個小而關鍵的技能:deleteColumns()。它不像清除內容那麼溫柔,是真的把欄位移除,後面的欄會整個左移,公式也會跟著重算。

你會看到最基礎的用法、一次刪一段、依標題清欄、只留白名單,再到大量刪除時的效能做法。中間也會提醒常見坑:索引從 1 開始、順序要從右刪到左、INDIRECT() 會出事、遇到篩選器或保護範圍該怎麼辦。看完你就能把表格瘦身到剛剛好,之後的分析與自動化都會更順。希望本篇文章能夠幫助到需要的您。


目錄

{tocify} $title={目錄} 


deleteColumns() 是什麼?什麼時候該用?

在 Google Apps Script(SpreadsheetApp/Sheet 類別)裡,deleteColumns(columnPosition, howMany) 用來從指定起點連續刪除多個欄位。它是真正移除欄位(後方欄位會左移),不是隱藏,也不是清除內容。官方文件明確列出方法簽名與範例,且欄列索引從 1 開始(不是從 0)。

使用情境:

        清理匯入資料(一次移除不需要的多個欄位)

        報表產前處理(只留下白名單欄位)

        批次刪除右側多餘的空欄,加速檔案載入


基礎概念與差異:刪除 vs. 隱藏 vs. 清除

1.    deleteColumns():

        把欄位直接拿掉,後方欄位左移;公式可能重算、參照也會跟著位移(間接參照如 INDIRECT() 不會自動更新,見下方雷點)。

2.    hideColumns():只把欄位藏起來,資料仍在。

3.    清除內容:用 Range.clear() 等,欄位還在、只是值被清空。


快速上手:3 個最常用的基本範例

刪除從第 2 欄開始的 3 欄(也就是刪掉 B:D)

function demoDelete3ColsFromB() {
  const sheet = SpreadsheetApp.getActive().getActiveSheet();
  sheet.deleteColumns(2, 3); // B、C、D
}


索引從 1 開始:1 是欄 A、2 是欄 B…(官方頁面也以此示範)。


只保留左側有內容的區域:一口氣刪掉右側「全空白」欄

function deleteEmptyRightSideColumns() {
  const sh = SpreadsheetApp.getActive().getActiveSheet();
  const lastCol = sh.getLastColumn();   // 最後一個「有內容」的欄
  const maxCol  = sh.getMaxColumns();   // 工作表實際總欄數
  if (maxCol > lastCol) {
    sh.deleteColumns(lastCol + 1, maxCol - lastCol);
  }
}


由右往左刪:避免索引位移

function deleteManyByIndices(indices) {
  const sh = SpreadsheetApp.getActive().getActiveSheet();
  // 例如 indices = [2, 5, 6, 10] 代表要刪 B、E、F、J
  indices.sort((a, b) => b - a).forEach(idx => sh.deleteColumns(idx, 1));
}


刪除多個欄位時務必從右往左,否則前面的刪除會改變後面欄位的位置。開發者社群與問答也建議「先處理較大的索引」。


實務範例大全

A. 依「欄位標題」選擇要刪的欄

/**
 * 只保留白名單欄位,其他一律刪除
 * keepHeaders: 想要保留的欄位標題(位於第1列)
 */
function keepOnlyColumns(keepHeaders = ['日期','姓名','金額']) {
  const sh = SpreadsheetApp.getActive().getActiveSheet();
  const lastCol = sh.getLastColumn();
  const header = sh.getRange(1, 1, 1, lastCol).getValues()[0];

  // 找出「要刪除」的欄索引(不是白名單且標題非空)
  const toDelete = [];
  header.forEach((h, i) => {
    const idx = i + 1; // 1-based
    if (h && !keepHeaders.includes(String(h).trim())) toDelete.push(idx);
  });

  // 由右往左合併連續區段,一次刪掉整段
  compressAndDelete(sh, toDelete);
}

function compressAndDelete(sh, indices) {
  if (!indices.length) return;
  indices.sort((a,b)=>a-b);
  const blocks = [];
  let start = indices[0], prev = indices[0];
  for (let i=1;ib[0]-a[0]).forEach(([s,count]) => sh.deleteColumns(s, count));
}


B. 刪除「標題為空」或「整欄全空」的欄

function deleteEmptyHeaderOrAllBlankColumns() {
  const sh = SpreadsheetApp.getActive().getActiveSheet();
  const lastCol = sh.getLastColumn();
  const lastRow = sh.getLastRow();
  const values  = sh.getRange(1, 1, lastRow || 1, lastCol || 1).getValues();

  const toDelete = [];
  for (let c = 1; c <= lastCol; c++) {
    const header = values[0][c-1];
    const allBlank = values.every(row => String(row[c-1] || '') === '');
    if (!header || allBlank) toDelete.push(c);
  }
  compressAndDelete(sh, toDelete);
}


C. 有篩選器時的處理(避免刪除衝突)

function deleteWithFilterGuard(startCol, howMany) {
  const sh = SpreadsheetApp.getActive().getActiveSheet();
  const filter = sh.getFilter(); // 有些情況下先移除篩選器較穩
  if (filter) filter.remove();
  sh.deleteColumns(startCol, howMany);
}


getFilter() 可取得工作表上的篩選器,必要時可先移除再操作。


D. 大量刪欄、極致效能:改用 Sheets API batchUpdate

當刪除規模很大(例如上百欄、多張分頁),以 Apps Script 逐次呼叫可能較慢。可考慮呼叫 Sheets API 的 batchUpdate,透過 DeleteDimensionRequest 一次送出多筆刪除指令,通常更快、更穩。

DeleteDimensionRequest(刪欄)核心結構如下(概念示意):

{
  "requests": [
    {
      "deleteDimension": {
        "range": {
          "sheetId": 123456789,
          "dimension": "COLUMNS",
          "startIndex": 1,   // 注意:API 是 0-based
          "endIndex": 4
        }
      }
    }
  ]
}


常見錯誤與雷點

1.    用錯索引、越界

        columnPosition 從 1 開始;howMany 需 ≥ 1。

        若超出現有欄數會拋出例外(Exception)。呼叫前可先用 getMaxColumns()、getLastColumn() 檢查。

2.    刪除次序導致位移錯亂

        一次要刪多個索引,請「由右往左」處理,或先把連續索引合併成區段再刪(上方 compressAndDelete 範例)。社群經驗也建議先刪較大的索引以避免位移。

3.    受保護範圍/試算表保護

        欄位在受保護範圍內,或工作表被保護,無權限時會丟例外(例如「嘗試編輯受保護的儲存格」)。請先檢查並移除或調整保護。可用 getProtections() / Protection 類別判斷與調整。

4.    公式參照毀損(#REF!)

        直接參照(如 =B2、=SUM(B:B))通常會跟著更新;但文字型引用(例如 INDIRECT("B:B"))不會自動更新,刪欄後易出現 #REF! 或指到錯位。建議改採非 INDIRECT 的寫法,或在刪除前先替換為可自動更新的參照。

5.    有篩選器、群組、凍結欄

        篩選器可能影響操作穩定度(可先 getFilter().remove());凍結欄位(getFrozenColumns())不會阻止刪除,但邏輯上要確認刪除區間是否涵蓋凍結區域,以免報表結構跑掉。

6.    效能與配額

        Apps Script 單次執行有時間限制(一般常見上限約 6 分鐘);大量刪欄請合併請求或改用 Sheets API batchUpdate。Apps Script 與 Sheets API 各自有配額與每分鐘限制,請留意官方配額頁。


安全防呆:備份、模擬、記錄

1.    先做備份(複製工作表)

function backupCurrentSheet() {
  const ss = SpreadsheetApp.getActive();
  const sh = ss.getActiveSheet();
  const copy = sh.copyTo(ss).setName(sh.getName() + ' (備份 ' + new Date().toISOString().slice(0,10) + ')');
  ss.setActiveSheet(copy); // 視需要切到備份
}


2.    模擬(Dry-run)列出將刪哪些欄,不真正刪

function dryRunDeleteByHeader() {
  const keep = ['日期','姓名','金額'];
  const sh = SpreadsheetApp.getActive().getActiveSheet();
  const lastCol = sh.getLastColumn();
  const header = sh.getRange(1,1,1,lastCol).getValues()[0];
  const toDelete = [];
  header.forEach((h,i)=>{
    if (h && !keep.includes(String(h).trim())) toDelete.push(i+1);
  });
  Logger.log('[DryRun] 欲刪欄索引:' + JSON.stringify(toDelete.sort((a,b)=>a-b)));
}


3.     執行紀錄(Log)

        把實際刪掉的欄位索引、標題、時間寫到另一張「Log」分頁,方便回溯。


與 deleteColumn()、insertColumns() 等方法如何搭配?

單欄刪除可用 deleteColumn(columnPosition);新增欄位可用 insertColumnsBefore/After 或 insertColumns(start, num)。這些方法都在同一份 Sheet 類別文件有完整清單與範例,可相互搭配建構資料管線。


極大表格/多人協作的加速思路

1.    把多次刪欄合成最少的區段(上方 compressAndDelete)

2.    先移除篩選器,刪除完再重建(若必要)

3.    跨多分頁操作時,優先考慮 Sheets API batchUpdate 一次送出多筆     

       deleteDimension,降低來回延遲與 Apps Script 執行時間壓力。

4.    若仍超時,改成分批刪除 + 時間型觸發器(分段跑)以避開單次執行時間上限與每分鐘限流。


完整範例:「留白名單、刪其餘」 + 備份 + 紀錄

/**
 * 需求:
 * 1) 先複製目前工作表作為備份
 * 2) 只保留 keepHeaders,其餘一律刪除
 * 3) 執行結果寫入 Log 分頁
 */
function keepWhitelistWithBackupAndLog() {
  const keepHeaders = ['日期','姓名','金額'];
  const ss = SpreadsheetApp.getActive();
  const sh = ss.getActiveSheet();

  // 1) 備份
  const backupName = sh.getName() + ' (備份 ' + new Date().toISOString().slice(0,10) + ')';
  sh.copyTo(ss).setName(backupName);

  // 2) 算出要刪欄位
  const lastCol = sh.getLastColumn();
  const header = sh.getRange(1, 1, 1, lastCol).getValues()[0];
  const toDelete = [];
  header.forEach((h, i) => {
    const idx = i + 1;
    if (h && !keepHeaders.includes(String(h).trim())) toDelete.push(idx);
  });

  // 先記住要刪掉的標題名稱(刪後就不存在了)
  const deleteNames = toDelete.map(i => header[i-1]);

  // 3) 真正刪除(合併區段、右到左)
  compressAndDelete(sh, toDelete);

  // 4) 記錄到 Log
  const log = ss.getSheetByName('Log') || ss.insertSheet('Log');
  log.appendRow([
    new Date(),
    sh.getName(),
    '保留:' + keepHeaders.join(','),
    '刪除欄標題:' + deleteNames.join(','),
  ]);
}

// 依連續索引合併、右到左刪除
function compressAndDelete(sh, indices) {
  if (!indices.length) return;
  indices.sort((a,b)=>a-b);
  const blocks = [];
  let start = indices[0], prev = indices[0];
  for (let i=1;ib[0]-a[0]).forEach(([s,count]) => sh.deleteColumns(s, count));
}


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