TL;DR —
Range.RemoveDuplicatesは データ > 重複の削除 ボタンをコード 1 行にしたものです。 ループにはない形でこれを危険にする点が 2 つあります。第一に、破壊的 です。行をその場で 削除し、各重複の 最初の 出現を残し、いったんマクロが動いたら 元に戻せません。第二に、 そのColumns引数はシートの列番号ではなく 範囲内のオフセット を取ります —Columns:=Array(1, 2)は 範囲の 1 列目と 2 列目を意味するので、範囲が C 列から始まって いれば、それは A と B ではなく C と D です。範囲に本当に見出しがない場合を除いて必ずHeader:=xlYesを渡し、実行する前にデータをバックアップしましょう。
' 同じ Email が重複する行を削除する(この範囲の 3 列目)。各値の最初の 1 つを残す。
Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets("Contacts")
ws.Range("A1").CurrentRegion.RemoveDuplicates _
Columns:=3, _ ' A1 から数えて、範囲の 3 列目
Header:=xlYes ' 先頭行は見出し。データとして扱わない
リストの重複除去は最もよくあるクリーンアップ作業の 1 つであり、Excel にはすでにそのための
エンジンがあります — 手作業でループし、比較し、削除する必要はありません。ですが
RemoveDuplicates は、その便利さの裏に 2 つの鋭い刃を隠しています — 安全網なしにデータを
破壊する こと、そしてそのキー引数がほとんどの人の思い込みとは違う意味を持つことです。
本記事はこの 2 つの事実を軸に組み立てられています。
この記事で学べること
- 考え方の軸 — これは重複の削除ボタンであり、その場で削除する
- 最も大事なルール —
Columnsの数値はシートの列ではなく範囲オフセット - なぜ
Header:=xlYesがほぼ必ず必要なのか - 残るのは 最後 ではなく 最初 の行 — そして取り消しはない
- 1 列か複数列か — 実際に何が「重複」と数えられるのか
RemoveDuplicates、AdvancedFilter、Dictionaryをそれぞれいつ使うか
考え方の軸:これはボタンであり、削除する
Range.RemoveDuplicates は、リボンの データ > 重複の削除 とまったく同じことをします。範囲を
走査し、キー列が前の行と重複するすべての行について、その行を削除します。下の行は、ほかの
行削除と同じように、隙間を埋めるためにせり上がります。
帰結がただちに 2 つ生じ、どちらも人を引っかけます。データを その場で 変えます —
「一意な行をどこか別の場所にコピーする」モードはありません。元の行は消えます。そしてマクロが
動いたあとは、Excel の取り消しスタックは空 なので、あなたもユーザーも Ctrl+Z で
取り戻せません。RemoveDuplicates は Delete と同じように扱いましょう — バックアップ済みの
データ、あるいは意図して作ったコピーに対してだけ実行するものだ、と。
最も大事なルール:Columns はシートの列ではなく範囲オフセット
これが RemoveDuplicates に「間違った行を削除」させるバグです。Columns 引数は、範囲の
左端から 1 始まりで列を数えます — シートの列文字やその絶対番号とは何の関係もありません。
' 範囲は C 列から始まる。重複判定は Email 列で行いたく、それはシートの E 列。
' Email は範囲の 3 列目(C=1, D=2, E=3)なので:
ws.Range("C1:F500").RemoveDuplicates Columns:=3, Header:=xlYes ' 正しい - E
' 間違い - シートの列番号 5 を渡すと範囲の 1 つ外を指す(またはエラー):
ws.Range("C1:F500").RemoveDuplicates Columns:=5, Header:=xlYes ' Email 列ではない
範囲がたまたま A 列から始まっていれば、オフセットはシートの列番号と一致し、バグは隠れます —
だからこそ、範囲がそれ以外の場所から始まった最初の一度、人は驚くのです。2 列以上で重複判定
するときは、範囲オフセット の配列を渡します。Columns:=Array(1, 3) は、範囲の 1 列目と
3 列目が合わさってキーを成す、という意味です。
Header:=xlYes — さもないと見出しがデータになる
RemoveDuplicates は、先頭行が見出しなのか本物のデータなのかを知る必要があります。引数は
Header で、省略すると既定値(xlNo)が 1 行目をデータとして扱います。これには嫌な効果が
2 つあります。見出し行がデータと比較され — そしてもしデータ行がたまたま見出しのテキストと
一致すれば — 削除されることすらあります。
ws.Range("A1").CurrentRegion.RemoveDuplicates Columns:=1, Header:=xlYes
範囲が見出し行を含むとき — つまりほとんどの場合 — は Header:=xlYes を渡しましょう。xlNo を
使うのは、本当に見出しのない値のブロックのときだけです。ここを間違えてもエラーはめったに
出ません — ただ静かに、表の一番上を台無しにするだけです。
残るのは最後ではなく最初 — そして取り消しはない
行が衝突すると、RemoveDuplicates は必ず 最初の 出現を残し、後のものを削除します。たいてい
はそれで問題ありません。ですが行が時系列に並んでいて、キーごとに 最新の レコードが欲しい
なら、「最初を残す」は必要なものの正反対です。
これを反転させるオプションはありません。定石は まずソート、それから重複除去 です。残したい
行がそのキーの中で一番上に来るようにソートすれば、「最初を残す」挙動が正しい行を残してくれます。
顧客ごとに最新のエントリが欲しいですか? 日付の降順でソートし、それから顧客キーで
RemoveDuplicates します。(ソートが列をぐちゃぐちゃにしないための Header:=xlYes とレコード
全体のルールは VBA Sort を参照してください。)
そしてもう一度言っておきます。痛い目を見る間違いだからです。マクロのあとに取り消しは
ありません。 削除した行が今後少しでも重要になりうるなら、削除してはいけません — 代わりに
AdvancedFilter で一意な行を 新しい 場所に抽出しましょう。元データはそのまま残ります。
1 列か複数列か:何が重複と数えられるのか
Columns 引数は、「重複」が何を意味するか も定義します。1 列を渡せば、その単一のフィールドが
一致したときに 2 つの行は重複です。複数を渡せば、指定した列が すべて そろって一致したときに
だけ、行は重複になります。
' 重複 = 同じ Email(1 列のキー)。
rng.RemoveDuplicates Columns:=3, Header:=xlYes
' 重複 = First と Last の名前が両方そろって同じ(複合キー)。
rng.RemoveDuplicates Columns:=Array(1, 2), Header:=xlYes
実行する前に少し考える価値があります。どの行が消えるかを決めるからです。「同じ人物」は、同じ メール、同じ名前、あるいは同じ名前 かつ 生年月日を意味するかもしれません — そしてどの選択も、 削除する行の集合が違います。あなたが指定する列が定義であり、行のそれ以外はすべて、生き残った コピーに付いてくるだけです。
RemoveDuplicates と AdvancedFilter と Dictionary
重複を除く道具は 3 つあり、互換ではありません。
RemoveDuplicates— 書くのが最も速く、その場で変更し、最初を残し、取り消しなし。本当に データそのものを縮めたく、バックアップがあるときに正解です。AdvancedFilter— 一意なコピーを新しい場所に 抽出し、 元データはそのまま残します。元を破壊 せずに 重複のないリストが必要なとき、あるいは同時に 条件でフィルターした一意な値が欲しいときに正解です。Dictionary— コードで完全に制御。重複判定のキーが計算で決まる (大文字小文字を無視、トリム、組み合わせ)とき、進めながら出現回数を 数える 必要があるとき、 あるいは行ごとにどのコピーを残すかを決めたいときに正解です。
はっきり言うべき判断はこうです。削除した行を後で取り戻したくなる可能性が少しでもあるなら、
RemoveDuplicates は使わないこと — その取り消しのなさこそ、AdvancedFilter が存在する理由の
すべてです。
ExcelMaster の活用
RemoveDuplicates は 1 行のコードに見えて、装填された銃のように振る舞います — シートの列では
ない Columns オフセット、見出し行を黙って食べる Header フラグ、間違ったレコードを残す
「最初であって最後ではない」ルール、そして間違いを永久にする取り消しの不在。そのどれもが、
エラーを出さずにデータを削除します。
ExcelMaster なら、
クリーンアップを説明するだけで済みます。「重複した連絡先を、日付で最新のものを残して削除して」
「メールで重複除去して、でも元のシートはそのままにして」と言えば、正しい道具を選び — 最新を
残したいときは RemoveDuplicates の前にソートし、元を残さねばならないときは新しい範囲への
AdvancedFilter を使い — そして何かを削除する前に必ずシートをバックアップします。ブックも
コードもあなたの手元に残り、1 列ずれたキーが静かに間違った行を削除する、あの一幕だけを
飛ばせます。
よくある質問
VBA で重複を削除するには?
範囲に対して RemoveDuplicates を呼びます。ws.Range("A1").CurrentRegion.RemoveDuplicates Columns:=1, Header:=xlYes のように。Columns 引数はどの列が重複を定義するかを(範囲内の
オフセットで)指定し、Header:=xlYes は先頭行が見出しだと Excel に伝えます。一致する行をその場で
削除し、各値の最初の 1 つを残すので、まずデータをバックアップしましょう。
VBA の RemoveDuplicates が間違った行を削除するのはなぜ?
ほぼ必ず、Columns がシートの A 列からではなく範囲の先頭から数えるからです。範囲が C 列から
始まっていれば、Columns:=1 は A ではなく C 列です。キー列の 範囲内での オフセットを渡し、
複数列のキーにはオフセットの Array(...) を使いましょう。
VBA の RemoveDuplicates は最初と最後のどちらの重複を残す?
最初の 出現を残し、後のものを削除します。そしてそれを変えるオプションはありません。最後
(たとえば最新のレコード)を残すには、残したい行がそのキーの中で一番上に来るようにデータを
ソートし、それから RemoveDuplicates を実行しましょう。
VBA で RemoveDuplicates を取り消せる?
いいえ。マクロから実行すると Excel の取り消しスタックがクリアされるので、Ctrl+Z も
Application.Undo も削除した行を復元しません。先にデータをバックアップするか、その場で削除する
代わりに AdvancedFilter で一意な行を新しい場所にコピーしましょう。
VBA で複数列に対して重複を削除するには?
範囲オフセットの配列を Columns に渡します。rng.RemoveDuplicates Columns:=Array(1, 2), Header:=xlYes のように。指定した列が すべて 一致したときにだけ、行は重複として扱われます。
数値は範囲内の位置で、その 1 列目を 1 として数えます。
検証環境
動作確認: Excel 365 (Windows 11), VBA 7.1 — 最終確認 2026-08-10。
関連ガイド: VBA Advanced Filter · VBA Dictionary · VBA Sort · VBA Delete Rows · VBA Range
