在整理 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));
}
