整理試算表最惱人的,往往不是公式,而是那些用不到、卻每天跟著你跑報表的多餘欄位。
這篇要帶你把 Google Apps Script 的 deleteColumn() 用順,用穩,也用得漂亮。從「什麼時候該刪欄、什麼時候只該清空或隱藏」開始,帶你認識欄位索引為何是 1 起算(A=1),為什麼大量刪除要「從右刪到左」才不會位移出包。
接著實作單欄刪除、連續多欄刪除、不連續欄合併刪、依標題黑白名單刪,以及自動偵測全空欄位的腳本。最後還會補上避雷:如何避免 #REF!、保護範圍與權限踩雷、以及超出欄數錯誤。
讀完你就能把表單回應、匯入資料、歷史欄位一次瘦身,報表更輕、效能也更順。希望本篇文章能夠幫助到需要的您。
目錄
{tocify} $title={目錄}
deleteColumn() 是什麼?適用哪些情境
deleteColumn() 是 Apps Script 的 Spreadsheet 服務中 Sheet 物件的成員方法,用來刪除指定位置的整個欄位(不是清空,是直接拿掉整欄)。常見應用像是:清理匯入資料的多餘欄、報表前置處理、移除隱私欄位、縮減欄數以避開 1,000 萬儲存格上限等。官方文件註明此方法存在於 Sheet 類別,另有 deleteColumns(start, howMany) 可一次刪除連續多欄。
索引從 1 開始(A 欄 = 1)。這點跟 JavaScript 陣列從 0 開始不同,寫腳本時務必分清楚。Google 的教學也特別提醒:處理列與欄時索引是 1-based。
另外,Google 試算表每個檔案最多 1,000 萬個儲存格,欄數上限為 18,278 欄(到 ZZZ 欄),刪掉不用的欄位可有效降載。
快速開始:一步步跑起第一支刪欄腳本
1. 在欲處理的試算表中,點 擴充功能 → Apps Script。
2. 新增腳本,貼上下方範例,儲存。
3. 第一次執行會要求權限,依指示授權(需要 spreadsheets 編輯範圍)。
範例 A:刪掉第一欄(A 欄)
function deleteCol_A() {
const sheet = SpreadsheetApp.getActive().getActiveSheet();
sheet.deleteColumn(1); // A 欄
}
說明:deleteColumn(columnPosition) 的參數是欄位位置(1 起算)。
常用情境範例
範例 B:刪除第 N 欄(用數字定位)
function deleteCol_N(n) {
const sh = SpreadsheetApp.getActive().getActiveSheet();
sh.deleteColumn(n);
}
// 用法:deleteCol_N(5) // 刪第 5 欄(E 欄)
寫防呆:可先用 getMaxColumns() 檢查 n 是否超界。
範例 C:用「欄字母」刪除(把字母轉索引)
function letterToIndex(letter) {
let idx = 0;
for (const ch of letter.toUpperCase()) {
idx = idx * 26 + (ch.charCodeAt(0) - 64); // 'A' -> 1
}
return idx;
}
function deleteColByLetter(letter) {
const sh = SpreadsheetApp.getActive().getActiveSheet();
sh.deleteColumn(letterToIndex(letter));
}
// deleteColByLetter('C') // 刪 C 欄
小心:欄索引是 1-based;陣列常是 0-based。
範例 D:一次刪除「連續多欄」→ deleteColumns(start, howMany)
function deleteBlock(start, count) {
const sh = SpreadsheetApp.getActive().getActiveSheet();
sh.deleteColumns(start, count);
}
// deleteBlock(2, 3) // 刪掉 B~D 三欄
用 deleteColumns() 比逐欄 deleteColumn() 快,因為能 減少與試算表互動次數。
範例 E:刪除「多個不連續欄」的最佳實務(從右往左 + 合併連續段)
function deleteNonAdjacentColumns(cols) {
// cols: 欲刪的欄索引陣列(1-based),例如 [2,3,7,8,9,12]
const sh = SpreadsheetApp.getActive().getActiveSheet();
const sorted = [...cols].sort((a,b)=>a-b);
// 先把連續索引合併成區段,再從「右到左」刪
const blocks = [];
let start = sorted[0], prev = sorted[0];
for (let i=1;ib[0]-a[0]).forEach(([s,c]) => sh.deleteColumns(s, c));
}
// deleteNonAdjacentColumns([2,3,7,8,9,12])
為何「右到左」?因為刪掉左邊欄位會導致右邊尚未處理的索引改變,從右往左能避免位移問題。
範例 F:依「欄標題」刪除或保留(黑白名單)
function deleteColumnsByHeader({
sheetName = null,
headerRow = 1,
removeHeaders = [], // 黑名單:遇到這些標題就刪
keepHeaders = [] // 白名單:只保留這些,其餘全刪
} = {}) {
const ss = SpreadsheetApp.getActive();
const sh = sheetName ? ss.getSheetByName(sheetName) : ss.getActiveSheet();
if (!sh) throw new Error('找不到指定工作表');
const lastCol = sh.getLastColumn();
if (lastCol === 0) return;
const headers = sh.getRange(headerRow, 1, 1, lastCol).getValues()[0]
.map(h => String(h).trim());
let toDelete = [];
if (keepHeaders.length) {
const keep = new Set(keepHeaders);
headers.forEach((h, i) => { if (!keep.has(h)) toDelete.push(i+1); });
} else if (removeHeaders.length) {
const ban = new Set(removeHeaders);
headers.forEach((h, i) => { if (ban.has(h)) toDelete.push(i+1); });
}
if (toDelete.length) deleteNonAdjacentColumns(toDelete);
}
// 範例:只保留白名單欄位
// deleteColumnsByHeader({keepHeaders: ['日期','品名','金額']});
範例 G:刪除「全空」欄位
function deleteEmptyColumns() {
const sh = SpreadsheetApp.getActive().getActiveSheet();
const lastRow = sh.getLastRow();
const lastCol = sh.getLastColumn();
if (lastRow === 0 || lastCol === 0) return;
const values = sh.getRange(1, 1, lastRow, lastCol).getValues();
const toDelete = [];
for (let c = 0; c < lastCol; c++) {
let hasData = false;
for (let r = 0; r < lastRow; r++) {
const v = values[r][c];
if (v !== '' && !(typeof v === 'string' && v.trim() === '')) { hasData = true; break; }
}
if (!hasData) toDelete.push(c + 1);
}
if (toDelete.length) deleteNonAdjacentColumns(toDelete);
}
範例 H:刪除「最後有內容欄」右側的所有欄(釋出配額)
function deleteColumnsRightOfLastContent() {
const sh = SpreadsheetApp.getActive().getActiveSheet();
const lastCol = sh.getLastColumn();
const maxCol = sh.getMaxColumns();
if (maxCol > lastCol) {
sh.deleteColumns(lastCol + 1, maxCol - lastCol);
}
}
getLastColumn() 取得最後一個「有內容」的欄;getMaxColumns() 取得工作表目前欄總數。
刪掉冗餘欄可幫助避免觸碰 1,000 萬儲存格與 18,278 欄的限制。
效能與大量刪除:怎麼刪得又快又穩
能用 deleteColumns() 就別迭代 deleteColumn():
把相鄰欄合併成區段,單次呼叫刪除,可大幅減少與試算表的來回。
從右往左刪:
避免每刪一次就讓尚未處理的索引位移。
極大量刪除(分散多段):
改走 Advanced Sheets Service 的 spreadsheets.batchUpdate + DeleteDimensionRequest,能把多段刪除打包成一次 API 呼叫,速度與穩定度更好。
Advanced Sheets 實戰範例(批次刪多段欄位)
/**
* 先在「服務」中啟用 Advanced Google Services 的 Sheets API,
* 並在雲端專案啟用 Sheets API。
*/
function batchDeleteColumnsByRanges(sheetId, ranges /* 陣列,如 [{start:1,end:3},{start:7,end:9}] */) {
const requests = ranges.map(({start, end}) => ({
deleteDimension: {
range: {
sheetId,
dimension: 'COLUMNS',
startIndex: start - 1, // API 是 0-based、左閉右開
endIndex: end // end 已是 1-based 轉 0-based 右開
}
}
}));
Sheets.Spreadsheets.batchUpdate({requests}, SpreadsheetApp.getActive().getId());
}
避雷清單:最常見錯誤 & 排除方式
1. 索引搞錯(拿 0 當 A 欄)
列欄方法一律 1-based(A=1),陣列才 0-based。寫換算時特別注意。
2. 刪左邊導致索引位移
逐欄刪除請「右→左」;或先合併連續段再用 deleteColumns()。
3. #REF! 公式毀損
直接刪欄會讓依賴該欄的公式出現 #REF!。可在刪除前先「貼上為值」、或採用命名範圍、INDEX/MATCH、LET 等較不脆弱寫法。Google 社群亦說明刪除參照即會導致 #REF!。
4. 受保護範圍/受保護工作表
非擁有者或無權限時,刪除會拋出例外。先檢查與移除保護,或要求權限。可透過 Apps Script 的 Protection 類別管理保護區域。
5. 超出範圍(欲刪欄位 > 最大欄數)
在刪前先用 getMaxColumns() 驗證,避免「超界」錯誤。
6. 大量刪除耗時
把多段刪除改成 Advanced Sheets 的 batchUpdate,單次請求完成。
7. 表單回應工作表
刪除與表單題目對應的欄,可能使回應錯位。建議先在表單端調整或只刪非表單欄位。
與「清空/隱藏」的差別
刪除欄(deleteColumn/s):直接移除維度,公式參照會位移或壞掉。
清空(clear() / clearContent()):資料變空,但欄仍在,參照結構較不受影響。
隱藏欄(hideColumns()):只是看不到,資料與參照都還在。
穩健的實務流程建議
1. 先備份:複製分頁或用版本記錄回復點。
2. 先檢測:列出即將刪除的欄名與索引,讓你目視確認。
3. 先斷開敏感參照:報表頁先貼值、或改用命名範圍。
4. 右→左刪 + 區段化:減少位移與 API 呼叫次數。
5. 大量刪除就批次 API:DeleteDimensionRequest 一次搞定。
問題集
Q1:怎麼拿到某個工作表的 sheetId 給 Advanced Sheets 用?
A:SpreadsheetApp.getActive().getSheetByName('工作表').getSheetId() 即可。
Q2:刪欄會不會影響群組、篩選?
A:會按實際維度改變,群組與篩選的範圍也會隨之變動(建議先關閉或重新設定)。
Q3:我只想釋出檔案容量,不動參照,該用什麼?
A:優先考慮 clear() 或 hideColumns();真的要縮欄數才用 deleteColumn()。
