目次
スプレッドシートを業務で運用していると、外部システムからのCSVインポート時や、複数人が同時に手入力を行った際、まったく同じデータ行が何行も重なってしまう問題が頻繁に発生します。データが重複して並んでいる状態を放置すると、SUMIFやCOUNTIFといった集計関数の結果が不正確になり、二重発注や顧客情報の重複登録といった致命的な業務トラブルにつながります。
手動で1行ずつ確認しながら削除するのは莫大な時間がかかり、目視での作業は削除漏れや誤削除のリスクを伴います。こうした場面でスプレッドシート 重複行を消す処理を自動化するために利用されるのが、Google Apps Script(GAS)です。ただし、用意されているメソッドの挙動や仕様を理解せずに実行すると、最新のデータを誤って消してしまったり、数式の参照が崩れてエラーが多発したりすることがあります。この記事では、処理の前後の変化を対比しながら、意図したデータだけを正しく残す書き方と現場でハマりやすい罠の回避策を詳しく整理していきます。
重複したデータが残る状態と削除処理を通したあとの変化
処理を実行する前のシートでは、例えば注文番号、顧客名、購入金額、受付日時といった全項目が完全に一致するデータ行が、何重にも積み重なって存在しています。フォームの連動ミスや二重送信によって同じレコードが3行入っている場合、売上合計を集計すると実態の3倍の数値が算出されてしまいます。また、フィルター機能を使って手動で整理しようとしても、どの行が正しくてどの行が重複なのかを判断しながら1行ずつ消していくのは非常に効率が低く、作業ミスを誘発します。
一方、GASの専用メソッドを用いて重複削除の処理を通したあとのシートでは、全く同じ内容を持つ複数の行のうち、一番上(最初)に出現した1行だけがそのまま残り、下に続いていた2行目以降の重複行はシート上から完全に削除されます。削除された行のスペースは自動的に下から詰められるため、データ間に不自然な空行が挟まることもありません。結果として、データの一貫性が保たれ、集計関数も正確な数値を返すようになり、綺麗なデータベースの形に一瞬で整います。
getDataRange と removeDuplicates を組み合わせてまとめて処理する書き方
スプレッドシート 重複行を消す作業を自動化する場合、実務で最も直感的かつ高速に動作するのが、シート全体からデータ領域を自動取得して重複を排除する方法です。毎朝実行されるデータクレンジングのバッチ処理や、フォーム回答を受け取った直後のデータ整形スクリプトなどでよく使われます。
以下は、アクティブなシート全体を対象にし、同一の内容を持つ行を1行だけ残して残りを削除する基本的な実装例です。
function removeDuplicateRows() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getActiveSheet();
var range = sheet.getDataRange();
range.removeDuplicates();
}
上記のコードでは、まず sheet.getDataRange() を呼び出すことで、データが存在するA1セルから最終行・最終列までの範囲を動的に取得しています。その範囲に対して removeDuplicates() を実行するだけで、全列の内容が完全に一致している重複行を検出して削除してくれます。
このメソッドの大きな仕様として、範囲の1行目にある見出し(ヘッダー行)も含めて処理対象に渡しても問題ありません。Google Apps Script の内部処理が判定を行い、データ部分の重複だけを取り除いてくれます。手作業でループ文を書いて1行ずつ削除判定を行う必要がないため、記述がシンプルでありながら可読性が高いという利点があります。
上にあるデータを残す仕様によって発生する意図しない上書きと回避策
一見すると利便性の高い removeDuplicates() ですが、実務で使う上で絶対に把握しておかなければならない仕様が存在します。それは「先に出現した行(上の行)を残し、後から出現した行(下の行)を削除する」という挙動です。
この仕様が原因で問題になるのが、履歴データや更新日時が含まれるシートです。例えば、同一ユーザーのステータス変更履歴が下に追記されていく運用をしている場合、シートの上部には過去の古いデータが存在し、下部にいくほど新しい最新のデータが記録されます。この状態でそのまま重複削除を実行すると、先頭にある古いデータが残り、下に記録されていた最新の変更データが消し去られてしまいます。結果として、最新の状態を維持したい業務において、過去データに先祖返りしてしまうという悲惨なトラブルを引き起こします。
この「データがずれる・意図しない行が残る」という点を回避するためには、重複削除を実行する直前に、データを適切な順序でソート(並び替え)しておく処理を挟みます。最新のデータを残したいのであれば、更新日時列を基準にして降順(新しい順)に並び替えた上で removeDuplicates() を呼び出します。こうすることで、最新のデータ行がシートの上側に配置されるため、削除処理が走った際に「最新の行」が残るようになります。業務ロジックに応じて、どのデータを行として採用すべきかを明確にし、事前ソートを組み合わせるのが安全な回避手順です。
数式や空行が混ざっているシートで重複処理を実行したときに起こる崩れ
実務で使われているスプレッドシートは、単純なテキスト値だけでなく、ARRAYFORMULA や VLOOKUP といった数式、あるいは視認性を高めるための空行や結合セルが混ざっているケースが多々あります。このような複雑な構成のシートに対してそのまま removeDuplicates() を適用すると、レイアウトや計算結果が大幅に崩れる原因となります。
例えば、行全体に数式が埋め込まれている場合、行が物理的に削除されて詰まることで、他のセルから参照していた範囲がズレて `#REF!` エラーが発生することがあります。また、行の一部に計算用として空白セルが存在すると、意図しない組み合わせで「完全一致」とみなされたり、逆に別データとして残ってしまったりする不整合が生じます。さらに、データ行の間に意味を持たせて挿入していた空行も「重複した空白行」と判定され、最初の一行を残して全て詰められてしまい、シートの見た目が破壊されます。
こうした崩れを回避するためには、大きく分けて2つのアプローチがあります。1つ目は、数式が含まれる列やレイアウト用の空行を対象範囲から外し、純粋な値のみが入っているデータ範囲(例えばA2からE500など)を getRange() で明示的に指定して削除処理を行う方法です。2つ目は、GAS側で一度データを配列として受け取り、数式を値に確定させてから処理するか、重複判定を特定の列(ID列など)だけに限定するロジックを組むことです。シート構造に合わせた範囲限定を行うことが、崩れを防ぐ鍵となります。
大容量シートでの処理速度低下を防ぎ実行時間を安全に収める工夫
数万行を超えるような大規模なスプレッドシートで重複整理を行う場合、GASの実行時間制限(標準のアカウントでは6分)を意識する必要があります。初心者によくある失敗として、for ループを回して1行ずつ値を比較し、重複していたら sheet.deleteRow(i) を実行するという自作の削除処理を書いてしまうケースがあります。
GASにおける deleteRow() は、1回呼び出されるたびにGoogleのサーバーとスプレッドシート間で通信と画面の再描画処理が発生するため、非常に処理が重いです。数千行のデータに対してループ内で deleteRow() を何度も呼び出すと、数分間フリーズした挙動になり、最終的には6分制限を超過してタイムアウトエラーで途中で停止します。処理が途中で落ちると、一部の重複だけが消えた中途半端で修復が困難なシートが残されてしまいます。
その点、今回紹介した removeDuplicates() は、Googleのバックエンド(C++層)で一括処理されるため、JavaScriptのループ処理に比べて数十倍から数百倍の速度で完了します。大量データが存在する環境でスプレッドシート 重複行を消す場合は、独自ループによる個別削除を避け、必ず範囲に対する一括処理メソッドを採用してください。万が一、処理前にデータが欠損した場合に備えて、GASの処理冒頭でシートのバックアップ(別名保存やコピー作成)を自動実行するコードを1行追加しておくと、万全の体制で運用できます。

