VBA実行時エラー457「Dictionaryキー重複」が出た時に確認した場所

Excel VBAで社員番号別の一覧を作っていました。元データを1行ずつ読み込み、Scripting.Dictionaryに入れていく処理です。最初は順調に動いていました。ところが途中で、実行時エラー457が出ました。画面には「このキーは既にこのコレクションの要素に割り当てられています」と出ています。私はこの文を見ても、最初は何を直せばよいのか分かりませんでした。

止まっていたのは、`dict.Add key, value` の行でした。この記事では、VBAのDictionaryで実行時エラー457が出たときに、私が確認した順番をまとめます。同じキーを2回入れていないか、Existsで避けるのか、上書きするのかを、実務で迷った目線で整理します。

Dictionaryの社員番号重複エラー

私が詰まったのは、社員番号をキーにして行データをまとめる処理でした。Excelの一覧には、社員番号、氏名、所属、金額が並んでいました。同じ社員番号の人は1行だけの想定でした。

Dictionaryに社員番号を入れておけば、あとで検索しやすいと思いました。そこで、行を上から順に読みました。値には、その行の情報をまとめて入れています。最初の数行は問題なく進みました。しかし、途中でマクロが停止します。エラー番号は457でした。黄色くなっていた行は、次のような場所でした。

dict.Add empNo, rowData

私はこの時点で、Dictionaryの中身をきちんと見られていませんでした。「社員番号は重複していないはずです」と思い込んでいました。しかし、実際のデータには同じ社員番号が2行ありました。

あとで確認すると、1行目は通常の社員情報でした。もう1行は、異動後の所属を追記した行でした。人が見ると同じ人の補足に見えます。しかしVBAから見ると、同じキーをもう一度Addしているだけでした。Dictionaryは同じキーを2回Addできません。ここを理解するまで、私はかなり遠回りしました。

VBEで実行時エラー457のダイアログが出て、dict.Add empNo, r

エラー457はDictionaryキー重複

実行時エラー457が出たら、最初に見る場所はAddの左側です。私の場合は、`dict.Add empNo, rowData` の `empNo` でした。ここに入っている値が、既にDictionary内にありました。

Microsoftの説明でも、このエラーはキーが既に使われているときに起きるものです。VBAのCollectionやDictionaryでは、キーは同じ箱の中で一意に扱われます。同じ名前の引き出しを、同じ箱に2つ作れないイメージです。私は最初、値のほうを疑っていました。氏名や所属の文字に変な記号が入っているのではと考えました。しかし、エラー457で見るべき場所は値ではありません。まずキーです。次のようなコードでは、2回目のAddで止まります。

Dictionary Addでキー重複エラー

Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")

dict.Add "A001", "山田"
' ここで再追加すると、次の行で止まります。
dict.Add "A001", "山田 太郎"

同じA001を2回Addしているためです。そのため、値が同じか違うかは関係ありません。キーが同じなら、2回目のAddで止まります。

この切り分けができると、確認する場所がかなり絞れます。元データに同じ社員番号があるのか。キーを作る処理で同じ文字になっているのか。そこだけを先に見ればよいと分かりました。

Debug.Printでの重複キー特定

次にやったのは、Addする直前にキーを表示することでした。VBAに慣れている人なら当たり前かもしれません。ただ、当時の私はイミディエイトウィンドウもあまり使えていませんでした。

次のように、Addの前へ1行足しました。

Debug.Print empNo
dict.Add empNo, rowData

これで、実際に追加しようとしている社員番号が出ます。私はマクロをもう一度実行しました。エラーで止まる直前に出ていた社員番号を確認します。

すると、同じ社員番号が少し前にも出ていました。やはり元データ側の重複でした。また、見た目では違うように見えるキーもあります。前後にスペースが入っている場合です。セルでは同じ番号に見えても、VBAの文字列としては違うことがあります。たとえば、`”A001″` と `”A001 “` は別の文字列です。逆に、Trimで前後スペースを消したあとなら同じキーになります。扱い方は、処理の目的で変わりました。

Debug.Printで重複確認

この確認をせずにExistsだけ入れると、問題が隠れることもあります。だから最初はDebug.Printで見ました。どの値が、どのタイミングで、2回目に出ているのかを確認しました。

VBEのイミディエイトウィンドウにA001、A002、A001のように重複キーが

ここで一度立ち止まって考えてみてください

Pythonや自動化スキルを体系的に習得して、ITエンジニアとしてのキャリアを切り開きたい方には「Enjoy Tech!(エンジョイテック)」が選択肢のひとつです。現役エンジニアのサポートで、未経験から実践的なスキルを身につけられます。

プログラミングスクール Enjoy Tech!(エンジョイテック) →

重複データの取捨選択

重複が分かったあと、次に迷ったのは直し方でした。最初に決めるのは、そのキーをスキップしたいのかどうかです。一方で、あとから来た値で更新したい場面もあります。ここを決めないままコードだけ直したときは、あとで集計結果がずれそうで不安でした。

単純に重複を避けたいだけなら、Existsで先に確認します。既にあるキーならAddしません。まだなければ、そのときだけAddします。

If Not dict.Exists(empNo) Then
    dict.Add empNo, rowData
End If

そのため、この形にすると、同じキーが2回目に出てもエラーでは止まりません。最初に入れた値を残します。2回目以降の値は無視します。

私が社員番号の一覧を作っていたときは、この方法が合っていました。同じ社員番号が複数行ある場合、最初の基本情報だけを使えばよかったためです。異動履歴の行まで混ぜると、逆に一覧が分かりにくくなりました。

データ処理確認とExistsの注意点

ただし、この方法には注意点があります。重複した行を捨てていることになります。本当に捨ててよいデータなのかは、事前に確認したほうが安心です。

エラーを消すことと、正しい集計にすることは別です。ここはかなり大事だと感じました。Existsは便利ですが、目的に合っているときだけ使うようにしています。

VBEでIf Not dict.Exists(empNo) Thenの行が表示さ

Dictionary 後勝ち処理と意図の明記

重複したキーのうち、後から来た行を正としたい場合もあります。たとえば、社員マスタで古い所属と新しい所属が混ざっている場合です。後ろの行ほど新しい情報なら、上書きしたほうが自然です。

その場合は、Addではなく代入に変えました。

dict(empNo) = rowData

この書き方では、キーが既にあれば値を上書きします。キーがなければ、新しく追加される形です。そのため、実行時エラー457で止まりにくくなります。

私はこの違いを知らず、何でもAddで書こうとしていました。この書き方は「新しいキーを追加する」動きです。一方で、代入は「そのキーの値をこの値にする」動きです。似ているようで、意味が違います。後勝ちにしたい処理なら、代入のほうが読みやすいことがあります。ただし、これも無条件で使うと危険です。知らないうちに古い値が消えるためです。私は上書きする処理では、コメントを残すようにしました。なぜ後勝ちにしているのかを、あとで見ても分かるようにするためです。

未来の自分を助けるDictionaryの方針

' 同じ社員番号が複数ある場合は、後ろの行を最新情報として採用します。
dict(empNo) = rowData

この一言があるだけで、翌月の自分が助かりました。自分で書いたコードでも、数週間たつと意図を忘れます。特にDictionaryは動きが見えにくいので、方針を文章で残すのが大事だと思っています。

キーの見た目とデータ整備

エラー457で意外と厄介だったのが、キーの見た目です。同じに見えるのに違う場合があります。違うと思っていたのに、同じ扱いになる場合もありました。

そのため、空欄をキーにしないなら、先に除外します。

empNo = Trim(CStr(ws.Cells(i, "A").Value))

If empNo <> "" Then
    If Not dict.Exists(empNo) Then
        dict.Add empNo, rowData
    End If
End If

大文字小文字の扱いも確認しました。DictionaryにはCompareModeがあります。設定によって、A001とa001の扱いが変わります。

私は最初、この設定をほとんど意識していませんでした。英字を含むコードを扱うなら、ここも見ておくと安心です。全角半角も同じです。実務データは、空欄も表記ゆれも混ざります。

empNo = Trim(CStr(ws.Cells(i, "A").Value))
empNo = StrConv(empNo, vbNarrow)

ただし、何でも半角にすればよいわけでもありません。キーにしてよい列かどうかを見てから、整える範囲を決めました。

Excelの社員番号列に空欄、前後スペース、全角英数字が混ざっており、隣の列でT

Dictionaryエラー、キー表示と方針決定

確認する順番は、次の流れが一番落ち着きました。まず、黄色くなった行がAddかどうかを見ます。次に、キーに何が入っているかをDebug.Printで確認しました。そのうえで、元データの重複か、キー作成処理の問題かを切り分けます。

重複していたら、処理方針を決めました。最初の値を残すなら、Existsで先に確認します。後から来た値を採用するなら、Addではなく代入に変えました。合計したいなら、既存値に足し込む処理にします。空欄や前後スペースも確認しました。全角半角、大文字小文字、数値と文字列の混在も見ておくと安心です。このあたりを放置すると、直ったように見えて別の集計ミスが出ることがあります。私の場合、エラー457を直したことで、Dictionaryの使い方が少し分かりました。ただエラーを消すのではなく、キーをどう扱うかを決めるのが大事でした。自分なりには、ここを決めてからコードを書くのが一番マシだと思っています。

Dictionaryエラー、キー確認が解決の近道

Dictionaryは便利です。ただ、キーの扱いを雑にするとすぐ止まります。だからこそ、Add、Exists、代入の違いだけでも押さえておくと、かなり落ち着いて直せます。同じエラーで止まっている人がいたら、まず追加直前のキーを表示してみてください。私はそこから、ようやく原因に近づけました。焦って全体を書き換える前に、まずキーだけを見るのが近道でした。

無料プレゼント

Excel業務を自動化する前に確認するチェックリスト(PDF)

自動化していい作業かどうか、VBAかPythonか、最初に避けるべき落とし穴。実務でよく迷うポイントを1枚にまとめました。メールアドレスだけで受け取れます。

無料でチェックリストを受け取る

¥980 ミニキット

コピペで動かせる3スクリプト+自動化チェックリスト

最新ファイルの自動選択・部署名ゆれの正規化・CSV文字コード確認の3本セット。今週の作業を1つだけ楽にするための最小キットです。

ミニキットを見る(¥980)

関連リンクとチェックリスト

関連書籍

Excel・VBA関連書籍を、手元で見返せる形にしておきたい場合

ExcelやVBAのトラブルは、基本の考え方をあとから見返せるだけでかなり楽になります。手元に1冊置いておくと、同じエラーで止まったときに確認しやすいです。

Excel・VBA関連書籍をAmazonで探す

学習サービスとアンケート

このスキルを活かしてさらに前へ進むなら

PythonやExcel自動化スキルを持ったまま、ITエンジニアとして転職したい方には「EBAエデュケーション」が選択肢です。企業が求めるエンジニア像に合わせたカリキュラムで、実務直結のスキルを習得できます。

ITエンジニア転職・EBAエデュケーション →

[アンケート] この記事は役に立ちましたか?


1問だけ回答する