店舗コードや銀行コード、支店コードを、マスタの表を見て別の値に変換する。見つからないコードは、エラーとして記録する。VBAでは、よく出てくる処理です。
私は、この処理をマスタの先頭から順に見ていく線形探索で書いていました。Foundフラグを立て、最後までフラグが立たなければエラー記録に回す形です。この形を定型にしておくと、店舗・銀行・支店のように複数の箇所で同じ書き方になり、見落としが減ります。
この記事では、同じ処理をDictionaryのExistsで書き換える方法を、線形探索版と並べて紹介します。書き換えたときに結果が変わる点(キーの型、大文字小文字、スペース、空のコードなど)は、実機で確かめた結果を載せます。
件数が増えたときの速度差は、【VBA】線形探索とDictionaryの速度差を実測比較で測っています。この記事では速度ではなく、書き方に絞ります。
こんな方に向けた記事です
- マスタ照合を、ForループとFoundフラグで書いている
- Dictionaryは使ったことがあるが、存在チェックの書き方に自信がない
- 書き換えて結果が変わらないか、先に知っておきたい
線形探索版:Foundフラグで探す
まず、元の形です。マスタの1列目がコード、2列目が変換後の値の2次元配列とします。
Private errList As Collection
Private Sub RecordError(ByVal code As String)
errList.Add code
End Sub
Function ConvertLinear(ByVal code As String, master As Variant) As String
Dim i As Long
Dim found As Boolean
For i = LBound(master, 1) To UBound(master, 1)
If master(i, 1) = code Then
ConvertLinear = master(i, 2)
found = True
Exit For
End If
Next i
If Not found Then RecordError code
End Function
コードを1件変換するたびに、マスタの先頭から比べていきます。一致したら値を返して抜け、最後までフラグが立たなければエラーを記録します。
Dictionaryで存在チェックを書く
マスタを先に1回だけDictionaryに積み、探すときはExistsで存在を確かめます。
Function BuildMasterDict(master As Variant) As Object
Dim d As Object
Dim i As Long
Set d = CreateObject("Scripting.Dictionary")
For i = LBound(master, 1) To UBound(master, 1)
d.Add CStr(master(i, 1)), master(i, 2)
Next i
Set BuildMasterDict = d
End Function
Function ConvertByDict(ByVal code As String, d As Object) As String
If d.Exists(code) Then
ConvertByDict = d(code)
Else
RecordError code
End If
End Function
呼び出す側は、次のようになります。
Sub DemoMasterCheck()
Dim master As Variant
Dim tmp(1 To 3, 1 To 2) As Variant
Dim d As Object
Dim codes As Variant
Dim i As Long
Dim x As Variant
tmp(1, 1) = "A001": tmp(1, 2) = "Store-A"
tmp(2, 1) = "A002": tmp(2, 2) = "Store-B"
tmp(3, 1) = "007": tmp(3, 2) = "Store-C"
master = tmp
Set d = BuildMasterDict(master)
Set errList = New Collection
codes = Array("A001", "007", "A999", "a002")
For i = 0 To UBound(codes)
Debug.Print codes(i) & " -> " & ConvertByDict(CStr(codes(i)), d)
Next i
Debug.Print "not found: " & errList.Count
For Each x In errList
Debug.Print " " & x
Next x
End Sub
実行すると、イミディエイトウィンドウには次のように出ます。

見つからないA999と、大文字小文字が違うa002が、エラー記録に入りました。変換結果は空文字で返ります。
解説:何が置き換わったのか
ループとFoundフラグが消え、存在チェックはd.Exists(code)の1行になりました。見つかったときの値は、同じキーでd(code)と書けば取り出せます。
マスタの登録は、変換を始める前に1回だけ行います。登録時にキーをCStrで文字列にそろえているのは、次の章で説明するキーの型の違いを避けるためです。
Dictionaryの基本操作は、【VBA】個数を数える3つの方法とその速度比較でも使っています。Existsの仕様は、Microsoft LearnのExistsメソッドに載っています。
置き換えで結果が変わる点を実機で確かめた
Excel 16.0(64bit)、日本語版Windows(コードページ932)で確認しました。別のPCやMac版では確かめていません。
まず、線形探索版とDictionary版が同じ結果になるかを見ました。マスタ4件に対して、見つかるコード3件(A001・B100・007)と、見つからないコード2件(A999・XYZ)を渡すと、どちらも同じ値を返し、同じように見つかりませんでした。空のコードとa001を混ぜた6件でも、エラー一覧はA999・空文字・a001の3件で、両方とも同じでした。
そのうえで、書き換えるときに気をつけたい点を6つ確かめました。
1. 未登録のコードをd(code)で読むと、キーが増える
4件を登録したDictionaryで、未登録の"ZZZ"をd("ZZZ")と読みました。戻り値はEmptyでしたが、件数は4から5に増え、Exists("ZZZ")もTrueになりました。
存在の確認はExistsで行い、値を読むのは見つかったあとにします。
2. コードの型が違うと、別のキーになる
文字列の"007"・数値の7・文字列の"7"は、すべて別のキーでした。"007"だけを登録したとき、Exists("007")だけがTrueで、Exists(7)とExists("7")はFalseです。数値のキー1は、Integer・Long・Double・Singleのどれで渡しても同じキーとして見つかり、文字列の"1"では見つかりませんでした。
線形探索の=も、型の組み合わせで結果が変わりました。
| 左辺(マスタ側) | 右辺(検索側) | 結果 |
|---|---|---|
Variant(文字列"001") | Variant(整数1) | 一致しない |
Variant(文字列"1") | Variant(整数1) | 一致しない |
Variant(文字列"007") | Variant(Double 7) | 一致しない |
Variant(整数1) | String型の"001" | 一致しない |
Variant(Double 7) | String型の"7" | 一致する |
Variant(Double 7) | String型の"007" | 一致しない |
マスタの数値型のコード(Double)に、String型の"7"を渡すと見つかり、"007"は見つかりませんでした。マスタを登録するときにCStrで文字列にそろえ、検索側も文字列で渡すと、型の違いによるずれを避けられます。先頭に0が付くコードは、数値で持つと0が失われるため、文字列で持ちます。
3. 大文字小文字は区別される。CompareModeは登録の前に指定する
CompareModeの既定値は0(バイナリ)で、"a001"は"A001"として見つかりません。線形探索の=も同じでした(モジュール先頭にOption Compare Textを書いていない場合)。
CompareMode = 1(vbTextCompare)にすると、"a001"で見つかり、値も取り出せます。全角のA001も、テキストモードでは見つかり、バイナリでは見つかりませんでした。
キーを1件登録したあとでCompareMode = 1を設定すると、実行時エラー5になり、CompareModeは0のままでした。指定は、キーを登録する前に行います。
4. 末尾のスペースは見つからない。Trimで除ける
"A001 "(末尾に半角スペース)は、線形探索でもDictionaryでも見つかりませんでした。Trimで除くと見つかります。
全角スペース(U+3000)をA001の後ろに付けた文字列にTrimをかけると、今回の環境では長さが4になり、全角スペースも除かれました。Replaceで除いても見つかります。
シートのVLOOKUPでも、末尾のスペースで#N/Aになります。関数側の確認は、【Excel関数】#N/A・#VALUE!・#REF!が出たら最初に確認にまとめています。
5. 空のコードは、マスタに空行があると「見つかる」
空のDictionaryでは、Exists("")はFalseです。空文字をキーとして登録すると、Trueになります。線形探索でも、マスタに空行があれば、空のコードは見つかりました。
マスタの範囲に空行が2行あると、BuildMasterDictの2つ目の空キーで実行時エラー457になりました。空行のないマスタに空のコードを渡すと、見つからず、空文字がエラー記録に入ります。
6. 重複キーは、Addではエラー、代入では上書きになる
登録済みの"A001"にd.Add "A001", "Dup"を実行すると、実行時エラー457(メッセージは「このキーは既にこのコレクションの要素に割り当てられています。」)になりました。件数は4のまま、値もStore-Aのままです。
d("A001") = "Over"と代入した場合は、エラーにならず、件数は4のまま、値がOverに変わりました。
マスタの重複を検出したいときはAdd、後の値で上書きしてよいときは代入を使います。
応用例:マスタが複数あるとき
店舗・銀行・支店のようにマスタが複数あるときは、マスタごとにDictionaryを作り、同じConvertByDictに渡します。
Set dShop = BuildMasterDict(shopMaster)
Set dBank = BuildMasterDict(bankMaster)
shopName = ConvertByDict(shopCode, dShop)
bankName = ConvertByDict(bankCode, dBank)
どのマスタで見つからなかったかを残したいときは、RecordErrorにマスタ名を渡す引数を足します。
まとめ
- マスタ参照の存在チェックは、
Existsで書くと、ループとFoundフラグが消える - 値の取り出しは
Existsのあとにd(code)。d(code)だけで確認するとキーが増える - 置き換えると、キーの型・大文字小文字・スペース・空のコードで結果が変わる。登録時に
CStrでそろえ、CompareModeは登録の前に指定する - 速度を測った記事は、【VBA】線形探索とDictionaryの速度差を実測比較。ソートの速度差は【VBA】バブルソートとクイックソートの速度差を実測比較で測っています


コメント