🚀The world's best VBA AI has evolved. ExcelMaster is now an autonomous Agent.Read more →
Back to Blog

Excel VBA で Remove Duplicates を使う — 1 行で重複を削除(そしてなぜ元に戻せないのか)

|

Excel VBA で Remove Duplicates を使う — 1 行で重複を削除(そしてなぜ元に戻せないのか)

TL;DRRange.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 列か複数列か — 実際に何が「重複」と数えられるのか
  • RemoveDuplicatesAdvancedFilterDictionary をそれぞれいつ使うか

考え方の軸:これはボタンであり、削除する

Range.RemoveDuplicates は、リボンの データ > 重複の削除 とまったく同じことをします。範囲を 走査し、キー列が前の行と重複するすべての行について、その行を削除します。下の行は、ほかの 行削除と同じように、隙間を埋めるためにせり上がります。

帰結がただちに 2 つ生じ、どちらも人を引っかけます。データを その場で 変えます — 「一意な行をどこか別の場所にコピーする」モードはありません。元の行は消えます。そしてマクロが 動いたあとは、Excel の取り消しスタックは空 なので、あなたもユーザーも Ctrl+Z で 取り戻せません。RemoveDuplicatesDelete と同じように扱いましょう — バックアップ済みの データ、あるいは意図して作ったコピーに対してだけ実行するものだ、と。

最も大事なルール: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+ZApplication.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