跳到主要內容跳到頁尾

Apps Script 批次讀寫:一次讀、整理、一次寫回客戶名單

6 分鐘閱讀Lv.2 中級摘要與日期

以客戶名單示範:一次讀成二維陣列,整理姓名空白與電話格式,再一次寫回指定範圍。附完整程式、前後對照表,以及 2000 列的耗時對照。

發布

文章目錄

業務窗口丟來一份 2000 列的客戶名單:姓名前後有空白,電話有的帶橫線、有的帶空格、有的整串變成數字,開頭的 0 不見了。要整理成統一格式,又不能動到旁邊的人工備註。

手動整理 2000 列太慢,逐格寫程式也慢。這篇用一次讀、整理、一次寫回的做法處理。

先看流程

概念示意:程式一次讀出客戶名單 A2 到 C 欄,在記憶體裡逐列整理姓名與電話,再一次寫回同一範圍;先檢查表頭,D 欄備註不在範圍內。← 左右滑動看完整的圖,點圖可放大

試算表的讀寫都是對 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
列 / 欄ABCD
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、空白電話原樣保留:

列 / 欄ABC
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 分鐘的時間上限,資料量大到超過上限時,程式會被中斷。

分享:
#Apps Script#批次讀寫#二維陣列#資料整理
喜特先生 Mr. Sheet

喜特先生 Mr. Sheet

數據分析師/商業分析師

八年工作經驗,跨不同業界解決數據需求。做過的案例都整理在作品集。

接下來可以

相關文章

查看所有文章

留言討論

共 0 則留言

訂閱電子報,新文章上線就通知你

免費,隨時可取消。

送出即表示同意服務條款與隱私權政策。