GASでスプレッドシートの重複データを
自動検出・削除する方法
フォームの二重送信やAPI連携の重複実行で発生する重複行を、Setを使った判定と定期トリガーで自動的にクリーンにする方法を解説します。
Table of Contents
なぜ重複データが発生するのか
業務用のスプレッドシートでは、以下のようなケースで重複行が発生しやすくなります。
フォームの二重送信
ユーザーが送信ボタンを連打してしまい、同じ回答が複数回記録される
API連携の重複実行
Webhookの再送やリトライ処理で、同じデータが複数回書き込まれる
手入力によるミス
複数人で同じシートを編集する際に、同じ内容を別々に入力してしまう
インポート処理の重複
CSVの取り込みを複数回実行してしまい、同じ行が繰り返し追加される
単一列で重複を削除する基本コード
最もシンプルなのは、1つの列(メールアドレスなど一意になりやすい列)を基準に重複を除去する方法です。Setで既出の値を管理しながら1行ずつ走査します。
function removeDuplicatesByColumn(sheet, colIndex) {
const data = sheet.getDataRange().getValues();
const header = data[0];
const rows = data.slice(1);
const seen = new Set();
const uniqueRows = [];
rows.forEach((row) => {
const key = String(row[colIndex]).trim();
if (!seen.has(key)) {
seen.add(key);
uniqueRows.push(row);
}
});
sheet.clearContents();
sheet.getRange(1, 1, 1, header.length).setValues([header]);
if (uniqueRows.length > 0) {
sheet.getRange(2, 1, uniqueRows.length, header.length).setValues(uniqueRows);
}
}複数列(複合キー)で重複判定する方法
「氏名とメールアドレスの組み合わせが一致したら重複」のように、複数の列をまとめて判定したい場合は、 各列の値を区切り文字で連結した複合キーを作成します。
function removeDuplicatesByKeys(sheet, keyColumnIndexes) {
const data = sheet.getDataRange().getValues();
const header = data[0];
const rows = data.slice(1);
const seen = new Set();
const uniqueRows = [];
rows.forEach((row) => {
const key = keyColumnIndexes
.map((i) => normalize(row[i]))
.join("|");
if (!seen.has(key)) {
seen.add(key);
uniqueRows.push(row);
}
});
sheet.clearContents();
sheet.getRange(1, 1, 1, header.length).setValues([header]);
if (uniqueRows.length > 0) {
sheet.getRange(2, 1, uniqueRows.length, header.length).setValues(uniqueRows);
}
}表記ゆれを考慮した正規化
全角と半角、大文字と小文字、前後の空白の違いがあると、本来は同じデータでも別物として扱われてしまいます。 比較用のキーを作る前に正規化関数を通しておくと、こうした表記ゆれによる見逃しを防げます。
function normalize(value) {
return String(value)
.trim()
.toLowerCase()
.replace(/[A-Za-z0-9]/g, (s) =>
String.fromCharCode(s.charCodeAt(0) - 0xfee0)
);
}削除前に安全確認するドライラン
いきなり削除を実行する前に、どの行が重複と判定されるのかをログに出力して確認する関数を用意しておくと安全です。
function findDuplicateRowsDryRun(sheet, keyColumnIndexes) {
const data = sheet.getDataRange().getValues();
const rows = data.slice(1);
const seen = new Set();
const duplicateRowNumbers = [];
rows.forEach((row, i) => {
const key = keyColumnIndexes.map((idx) => normalize(row[idx])).join("|");
if (seen.has(key)) {
duplicateRowNumbers.push(i + 2); // ヘッダー分+1
} else {
seen.add(key);
}
});
Logger.log("重複と判定された行: " + duplicateRowNumbers.join(", "));
return duplicateRowNumbers;
}トリガーで定期実行を自動化する
フォーム回答やAPI連携で継続的にデータが増えるシートには、時間主導トリガーを設定して毎日自動でクリーンアップするのが効果的です。
function createDailyCleanupTrigger() {
ScriptApp.newTrigger("dailyCleanupJob")
.timeBased()
.everyDays(1)
.atHour(3)
.create();
}
function dailyCleanupJob() {
const sheet = SpreadsheetApp.getActive().getSheetByName("受付データ");
removeDuplicatesByKeys(sheet, [0, 2]); // 例: A列・C列の組み合わせ
}実務での注意点
1. 削除前に必ずバックアップを取る
clearContents()やsetValues()は取り消せません。実行前にシートをコピーするか、Driveにエクスポートしておきましょう。
2. どちらの行を残すかを明確にする
上記のコードは「先に出現した行」を残す実装です。最新の行を残したい場合は、配列を逆順にしてから走査するなど、要件に合わせて調整が必要です。
3. 大量データでは実行時間に注意
数万行規模のシートでは処理に時間がかかり、GASの実行時間制限に達する場合があります。分割実行の仕組みと組み合わせると安定します。
まとめ
GASでの重複データ削除は、Setによる既出チェックと複合キーの設計、表記ゆれの正規化を組み合わせることで、実務レベルの精度で自動化できます。 定期トリガーと組み合わせれば、人手を介さずにデータをきれいな状態で保つことが可能です。
よくある質問
対象列の値をキーにして、Setやオブジェクトで既出の値を記録しながら1行ずつ走査する方法が最もシンプルです。getValues()で全データを一括取得し、重複していない行だけを配列に残してsetValues()で書き戻すと高速に処理できます。
各行から判定に使う列の値を取り出し、区切り文字で連結した文字列をキーにします。例えば氏名とメールアドレスの組み合わせで重複を判定する場合は「氏名|メールアドレス」のような複合キーを作り、Setで既出チェックを行います。
比較用の正規化関数を用意し、trim()で前後の空白を除去し、toLowerCase()で大文字小文字を統一し、必要に応じて全角英数字を半角に変換してからキーを作成します。表示用のデータは元の値のまま残し、比較にだけ正規化した値を使うのがポイントです。
できます。時間主導トリガー(ScriptApp.newTrigger)を使って、毎日決まった時間に重複削除関数を実行するように設定できます。フォーム回答やAPI連携で継続的にデータが追加されるシートとの相性が良い方法です。
本番シートに直接setValues()する前に、削除対象の行番号や件数をログ(Logger.logやスプレッドシートの別シート)に出力して確認する運用がおすすめです。また削除前の状態をコピーシートやDriveにバックアップしておくと安全です。
GAS開発・業務Webシステムを
相談する。
重複削除以外にも、フォーム連携・帳票出力・Gmail自動化など、日々の業務を自動化するGAS開発をご相談いただけます。