スプレッドシート集計の数え間違いを更新前に発見|GASで状態別件数をdry-run確認

スプレッドシート集計の数え間違いを更新前に発見|GASで状態別件数をdry-run確認

ツールおじ|GoogleアカウントをAIエージェント化

ツールおじ|GoogleアカウントをAIエージェント化

進捗表や公開準備表を集計するとき、「完了」と「完了済み」が混ざっていたり、同じ項目IDが2行あったりすると、集計式が動いていても数字は正しくなりません。誤った件数を報告用シートへ上書きする前に、まず入力の不備と集計予定値を確認したいところです。

この記事では、テスト用スプレッドシートの行を読み取り、区分×状態の件数をdry-runで集計する無料GASを紹介します。セルの更新、シート追加、メール送信は行わず、問題がある行は値をログへ出さずに行番号と理由だけを返します。

先に知っておきたいこと

  • 対象は、開いているスプレッドシート内の「公開準備」シートです。
  • 初期設定ではデータ500行までを確認し、超えた場合は停止します。
  • 許可する状態は「未着手」「作業中」「確認待ち」「完了」の4種類です。
  • 期限は空欄または `yyyy-MM-dd` 形式で入力します。
  • 項目ID、担当名、内容などの値はログへ出しません。
  • この例は読み取り専用です。集計結果の書き込みや自動通知は行いません。

準備するシート

個人情報や機密情報を含まないテスト用スプレッドシートを作り、シート名を「公開準備」にします。1行目は次の5列を、この順番で用意してください。

  • 項目ID
  • 区分
  • 状態
  • 担当
  • 期限

たとえば、区分には「本文」「画像」「リンク」「設定」、状態には許可した4種類のいずれかを入れます。動作確認用として、状態の表記ゆれや重複した項目IDも少量だけ含めると、停止条件を確認できます。

コピーして使える最小コード

function auditSpreadsheetSummaryFree() {
  const CONFIG = {
    DRY_RUN: true,
    SHEET_NAME: '公開準備',
    MAX_ROWS: 500,
    ALLOWED_STATUSES: ['未着手', '作業中', '確認待ち', '完了']
  };

  if (CONFIG.DRY_RUN !== true) {
    throw new Error('この無料例はDRY_RUN=trueのまま使用してください。');
  }

  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(CONFIG.SHEET_NAME);
  if (!sheet) {
    throw new Error('公開準備シートが見つかりません。');
  }

  const values = sheet.getDataRange().getDisplayValues();
  if (values.length < 2) {
    throw new Error('見出し行の下にテストデータを1行以上用意してください。');
  }

  const expectedHeaders = ['項目ID', '区分', '状態', '担当', '期限'];
  const actualHeaders = values[0].map(function (value) {
    return String(value).normalize('NFKC').trim();
  });
  const headersMatch = expectedHeaders.every(function (header, index) {
    return actualHeaders[index] === header;
  });
  if (!headersMatch) {
    throw new Error('1行目の見出し名または列順が違います。');
  }

  const rows = values.slice(1).filter(function (row) {
    return row.some(function (value) {
      return String(value).trim() !== '';
    });
  });
  if (rows.length > CONFIG.MAX_ROWS) {
    throw new Error('確認上限を超えました。対象を分けて再実行してください。');
  }

  const seenIds = {};
  const summary = {};
  const issues = [];
  let validRows = 0;

  rows.forEach(function (row, index) {
    const rowNumber = index + 2;
    const itemId = String(row[0]).normalize('NFKC').trim();
    const category = String(row[1]).normalize('NFKC').trim();
    const status = String(row[2]).normalize('NFKC').trim();
    const owner = String(row[3]).normalize('NFKC').trim();
    const dueDate = String(row[4]).normalize('NFKC').trim();
    const codes = [];

    if (!itemId || !category || !status || !owner) codes.push('REQUIRED_EMPTY');
    if (itemId && seenIds[itemId]) codes.push('DUPLICATE_ITEM_ID');
    if (itemId) seenIds[itemId] = true;
    if (status && CONFIG.ALLOWED_STATUSES.indexOf(status) === -1) codes.push('STATUS_NOT_ALLOWED');
    if (dueDate && !isValidIsoDate_(dueDate)) codes.push('DATE_FORMAT_INVALID');

    if (codes.length) {
      issues.push({ row: rowNumber, codes: codes });
      return;
    }

    if (!summary[category]) summary[category] = {};
    summary[category][status] = (summary[category][status] || 0) + 1;
    validRows += 1;
  });

  const issueCounts = issues.reduce(function (counts, issue) {
    issue.codes.forEach(function (code) {
      counts[code] = (counts[code] || 0) + 1;
    });
    return counts;
  }, {});

  const result = {
    dryRun: true,
    checkedRows: rows.length,
    validRows: validRows,
    issueRows: issues.length,
    issueCounts: issueCounts,
    issues: issues.slice(0, 50),
    summary: summary
  };

  console.log(JSON.stringify(result));
  return result;
}

function isValidIsoDate_(value) {
  const match = String(value).match(/^(\d{4})-(\d{2})-(\d{2})$/);
  if (!match) return false;
  const year = Number(match[1]);
  const month = Number(match[2]);
  const day = Number(match[3]);
  const date = new Date(Date.UTC(year, month - 1, day));
  return date.getUTCFullYear() === year
    && date.getUTCMonth() === month - 1
    && date.getUTCDate() === day;
}

実行前の準備

1. テスト用スプレッドシートに「公開準備」シートを作ります。

2. 見出し5列と、個人情報を含まないサンプル行を入力します。

3. 拡張機能からApps Scriptを開き、コードを貼り付けます。

4. `DRY_RUN` が `true`、`MAX_ROWS` が500であることを確認します。

5. `auditSpreadsheetSummaryFree` を選び、手動で実行します。

6. 実行ログの `issueRows` と `issueCounts` を先に確認します。

7. 問題行を手動で直した後に再実行し、`summary` の件数を元表と照合します。

ログの読み方

  • `checkedRows`: 空行を除いて確認した行数です。
  • `validRows`: 入力検証を通過し、集計へ含めた行数です。
  • `issueRows`: 1件以上の問題が見つかった行数です。
  • `issueCounts`: 空欄、ID重複、状態の許可値外、日付形式不正の件数です。
  • `issues`: 最大50件まで、行番号と理由コードだけを表示します。
  • `summary`: 区分ごとの状態別件数です。

値そのものをログへ出していないため、どの行を直すかは行番号でシートへ戻って確認します。`issueRows` が1件でもある場合は、集計値を報告や公開判断へ使わないでください。

集計結果を使う前のチェックリスト

  • 見出し名と列順が想定どおりか確認する。
  • 状態の表記を4種類へ統一する。
  • 重複した項目IDが別作業を表していないか人が確認する。
  • 空欄行を勝手に「未着手」とみなさない。
  • `validRows + issueRows` が `checkedRows` と一致するか確認する。
  • 状態別件数を少量の元データと手計算で照合する。
  • 問題がないことを確認してから、必要なら別工程で集計表を更新する。

停止条件

  • 対象シートが見つからない。
  • 見出しまたは列順が違う。
  • 500行を超えている。
  • `issueRows` が1件以上ある。
  • 集計件数と目視確認が一致しない。
  • 実データに個人情報や機密情報が含まれ、ログ範囲を判断できない。

該当した場合は、許可値を広げたり問題行を除外したりして続行せず、入力ルールと対象範囲を確認してください。

制限と安全上の注意

このコードは表示値を使うため、数式の計算式そのものや変更履歴は監査しません。また、区分と状態の件数を確認するだけで、作業の完了品質や公開可否を自動判断するものではありません。

この無料例はセル更新、シート作成、メール送信、外部API通信、時間主導トリガー作成を行いません。集計結果の自動書き込みを追加する前に、コピー環境で入力検証と再実行時の挙動を確認してください。

まとめ

集計処理では、数式やコードが動くことよりも、入力の空欄・表記ゆれ・重複を先に見つけることが重要です。まずは読み取り専用のdry-runで問題行と状態別件数を確認し、元表と照合してから次の更新工程へ進めてください。

公開準備の入力確認を仕組み化したい方へ

個人クリエイター公開準備チェックGAS Proを見る

GasおじのTips商品一覧を見る

GasおじのTips商品一覧を見る

※このコードは学習・検証用の例です。実データへ適用する前にコピー環境で確認してください。Google公式・Google公認の商品ではありません。


あなたも記事の投稿・販売を
始めてみませんか?

Tipsなら簡単に記事を販売できます!
登録無料で始められます!

Tipsなら、無料ですぐに記事の販売をはじめることができます Tipsの詳細はこちら
 

この記事のライター

ツールおじ|GoogleアカウントをAIエージェント化

おう、Gasおじだ。 AIツールとSaaSに散々突っ込んで遠回りした結果、最後に強かったのは「今あるGoogleアカウント」だった。 今ではAIエージェントであらゆるタスクを自動化して無双してるぞ。 GAS、スプレッドシート、フォーム、Gmail、AIエージェントを組み合わせれば、個人でも小さな会社でも、手作業だらけの業務はかなり自動化できる。 ここでは机上のAI論じゃなく、現場で動くテンプレ、コード、手順だけを置いていくぞ。 高いツールを増やす前に、まずお前さんのGoogleアカウントを仕事するエージェントに変えようじゃないか。

このライターが書いた他の記事

  • Gmailの定型連絡を「送信せず」まとめて下書き化する|Gmail下書きテンプレGAS Pro

    ¥2,980
    1 %獲得
    (29 円相当)
  • カレンダーの二重登録を作成前に発見|GASで予定候補をdry-run監査

  • 毎朝のToDoを区分・優先度・期限つきでまとめる Morning ToDo Mail Pro

    ¥2,980
    1 %獲得
    (29 円相当)

関連のおすすめ記事

  • USBメモリで持ち運べるLinux Mint 22.1と永続的な書き込み領域の作成法【Rufus だけで作成する方法を初心者向けに徹底解説】

    ¥500
    1 %獲得
    (5 円相当)
    SmartSenior

    SmartSenior

  • Gmailの定型連絡を「送信せず」まとめて下書き化する|Gmail下書きテンプレGAS Pro

    ¥2,980
    1 %獲得
    (29 円相当)
    ツールおじ|GoogleアカウントをAIエージェント化

    ツールおじ|GoogleアカウントをAIエージェント化

  • 毎朝のToDoを区分・優先度・期限つきでまとめる Morning ToDo Mail Pro

    ¥2,980
    1 %獲得
    (29 円相当)
    ツールおじ|GoogleアカウントをAIエージェント化

    ツールおじ|GoogleアカウントをAIエージェント化