業務窗口丟來一份 2000 列的客戶名單:姓名前後有空白,電話有的帶橫線、有的帶空格、有的整串變成數字,開頭的 0 不見了。要整理成統一格式,又不能動到旁邊的人工備註。
手動整理 2000 列太慢,逐格寫程式也慢。這篇用一次讀、整理、一次寫回的做法處理。
先看流程
試算表的讀寫都是對 Google 的一次呼叫,次數越多越慢。所以把整塊資料讀成二維陣列(外層是列、內層是欄),整理完再整塊寫回。
先準備與執行
- 新增「客戶名單」分頁,
A1:C1依序是「姓名」「電話」「地區」,資料從第 2 列開始。 - 在這份試算表選「擴充功能 → Apps Script」,把三個區塊依序貼進去並儲存。
- 在上方函式選單選
runCleanCustomers,按「執行」。若 Google 要求授權,先看清楚列出的權限再同意。 - 結果在「執行記錄」:整理幾列、修改幾列、需人工檢查幾列。
區塊一:整理一支電話
程式碼.gs
Apps Script
/**
* @OnlyCurrentDoc
*/
// 把可辨識的台灣手機整理成 0912-345-678;其他原樣保留並標記 ok=false
function normalizePhone(raw) {
const original = String(raw === null || raw === undefined ? "" : raw);
let digits = original.replace(/\D/g, "");
if (digits.length === 12 && digits.startsWith("886")) {
digits = "0" + digits.slice(3);
} else if (digits.length === 9 && digits.startsWith("9")) {
digits = "0" + digits; // 在試算表輸入成數字時,前導 0 會消失
}
if (/^09\d{8}$/.test(digits)) {
return { value: digits.slice(0, 4) + "-" + digits.slice(4, 7) + "-" + digits.slice(7), ok: true };
}
return { value: original, ok: false };
}- 最上面的
@OnlyCurrentDoc註解,宣告這支程式只會用到目前這份試算表。 - 先把所有非數字字元去掉,再判斷長度。
- 9 碼且開頭是 9,是被試算表吃掉前導 0 的手機,補回 0。
886開頭的 12 碼,換成 0 開頭。- 只有
09開頭的 10 碼才輸出0912-345-678;其他回傳原值,並標記ok: false。
區塊二:整理整張表
程式碼.gs
Apps Script
// 輸入 [姓名, 電話, 地區] 的二維陣列,回傳整理後的陣列與計數;不改變列數與欄數
function cleanCustomerRows(rows) {
let changed = 0;
let flagged = 0;
const cleaned = rows.map((row) => {
if (row.every((cell) => cell === "" || cell === null)) {
return row.slice();
}
const name = String(row[0]).trim();
const phone = normalizePhone(row[1]);
const next = [name, phone.ok ? phone.value : row[1], row[2]]; // 認不出的電話連型別都不動
if (String(row[1]) !== "" && !phone.ok) {
flagged++;
}
if (next.some((cell, i) => cell !== row[i])) {
changed++;
}
return next;
});
return { rows: cleaned, changed: changed, flagged: flagged };
}- 每一列是
[姓名, 電話, 地區]。全空的列原樣保留,列數與欄數都不變。 - 姓名用
trim()去頭尾空白。 - 認不出的電話連型別都不動(數字還是數字),並計入「需人工檢查」。
- 這個函式只處理陣列,不碰試算表,所以可以不開 Google 就檢查。
區塊三:讀、檢查、寫回
程式碼.gs
Apps Script
function runCleanCustomers() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("客戶名單");
if (!sheet) {
throw new Error("找不到「客戶名單」分頁。");
}
const header = sheet.getRange("A1:C1").getValues()[0];
if (header.join("|") !== "姓名|電話|地區") {
throw new Error("客戶名單 A1:C1 必須依序是:姓名、電話、地區。");
}
const lastRow = sheet.getLastRow();
if (lastRow < 2) {
return;
}
const range = sheet.getRange("A2:C" + lastRow);
const result = cleanCustomerRows(range.getValues());
range.setValues(result.rows);
Logger.log("整理 " + result.rows.length + " 列:修改 " + result.changed + "、需人工檢查 " + result.flagged);
}- 先確認分頁存在、
A1:C1是「姓名、電話、地區」,不符就丟錯停止,什麼都不寫。 - 從
A2到 C 欄最後一列的範圍只建立一次,讀和寫共用同一個range。 setValues的陣列尺寸要和範圍相同;cleanCustomerRows不改列數、欄數,所以一定相符。- 範圍只到 C 欄,旁邊 D 欄的人工備註不會被碰到。
前後對照
下表是示意用的整理前資料(取樣 9 筆資料,不含表頭),電話欄裡的數字,是試算表把 0912345678 這類輸入當成數字後的樣子,可以改改看;這張表不會執行程式。
A2
| 列 / 欄 | A | B | C | D |
|---|---|---|---|---|
| 1 | 姓名 | 電話 | 地區 | |
| 2 | 王小明 | 912345678 | 台北 | |
| 3 | 李小華 | 0922-333-444 | 新北 | |
| 4 | 陳大文 | 912345678 | 台中 | |
| 5 | 林小美 | 02-2345-6789 | 高雄 | |
| 6 | 張三 | 桃園 | ||
| 7 | 趙六 | 886912345678 | 台南 | |
| 8 | 周七 | 0933 444 555 | 新竹 | |
| 9 | 吳八 | 9123 | 基隆 | |
| 10 | 鄭九 | 0987-654-321 | 宜蘭 | |
| 11 | ||||
| 12 |
可直接編輯儲存格 • 重新整理即還原
整理後,認得出的手機統一成 0912-345-678 格式,姓名前後空白消失;市話、數字 9123、空白電話原樣保留:
| 列 / 欄 | A | B | C |
|---|---|---|---|
| 1 | 姓名 | 電話 | 地區 |
| 2 | 王小明 | 0912-345-678 | 台北 |
| 3 | 李小華 | 0922-333-444 | 新北 |
| 4 | 陳大文 | 0912-345-678 | 台中 |
| 5 | 林小美 | 02-2345-6789 | 高雄 |
| 6 | 張三 | 桃園 | |
| 7 | 趙六 | 0912-345-678 | 台南 |
| 8 | 周七 | 0933-444-555 | 新竹 |
| 9 | 吳八 | 9123 | 基隆 |
| 10 | 鄭九 | 0987-654-321 | 宜蘭 |
示範試算表
為什麼不逐格處理
下面是兩種做法在 Google 試算表上的耗時對照,實際耗時會隨資料與時段浮動:
| 做法 | 資料量 | 耗時 |
|---|---|---|
| 一次讀、整理、一次寫回 | 2000 列 | 約 1.0 秒(1037 ms) |
| 逐格讀、逐格寫 | 200 列 | 約 47 秒(46721 ms) |
表中兩種做法的資料量不同,逐格做法用的是 200 列。
換成你的情境
- 欄位位置:
A1:C1的表頭文字、"A2:C"的欄範圍,以及row[0]、row[1]、row[2]的順序。 - 電話規則:
normalizePhone的位數與輸出格式,改成你要的規則。 - 分頁名稱:
"客戶名單"。 - 哪些欄要整理:
cleanCustomerRows裡的next;沒列進去的欄會原樣寫回。
1
多一欄 Email,哪裡要改?
客戶名單新增第 4 欄 Email(D 欄),要去掉頭尾空白並轉成小寫。先預測:哪幾個地方要改?原本放在 D 欄的人工備註會怎樣?
延伸閱讀
說明
A:C 範圍內若有公式,執行後會被換成當下的值,用公式算出來的欄位請先搬出範圍。單次執行有 6 分鐘的時間上限,資料量大到超過上限時,程式會被中斷。
接下來可以
相關文章
查看所有文章留言討論
共 0 則留言
