ねらい:名寄せ(同一人物・同一企業の突合せ)を「正規化→判定→スコア→統合」の型で安全に回す
名寄せは「表記ゆれ・略称・住所の揺れ」を潰し、重複候補を拾い、確信度でランク分けして、統合ルール(どの列を残すか)に従って1件へまとめる作業です。配列I/O+Dictionary+正規化+スコアリングで“速く・壊れない”名寄せパイプラインを作り、初心者でも貼って動くテンプレを例題付きで解説します。
共通基盤:配列I/O・正規化・簡易距離関数
一括読み書き・正規化ユーティリティ
' ModNayo_Base.bas
Option Explicit
Private Const SEP As String = Chr$(30)
Public Function ReadRegion(ByVal ws As Worksheet, Optional ByVal topLeft As String = "A1") As Variant
ReadRegion = ws.Range(topLeft).CurrentRegion.Value
End Function
Public Sub WriteBlock(ByVal ws As Worksheet, ByVal a As Variant, ByVal topLeft As String)
ws.Range(topLeft).Resize(UBound(a, 1), UBound(a, 2)).Value = a
End Sub
Public Function NormText(ByVal s As Variant) As String
Dim t As String: t = LCase$(Trim$(CStr(s)))
t = Replace(t, " ", "") ' 全角空白除去
t = Replace(t, " ", "") ' 半角空白除去
t = Replace(t, "-", "") ' - を除去(電話など)
t = Replace(t, "-", "") ' 全角-
NormText = t
End Function
Public Function NormCompany(ByVal s As Variant) As String
Dim t As String: t = LCase$(Trim$(CStr(s)))
t = Replace(t, "株式会社", "")
t = Replace(t, "(株)", "")
t = Replace(t, "(株)", "")
t = Replace(t, "有限会社", "")
t = Replace(t, "合資会社", "")
t = Replace(t, "合同会社", "")
t = Replace(t, " ", ""): t = Replace(t, " ", "")
NormCompany = t
End Function
Public Function NormAddress(ByVal s As Variant) As String
Dim t As String: t = LCase$(Trim$(CStr(s)))
t = Replace(t, " ", ""): t = Replace(t, " ", "")
t = Replace(t, "丁目", "-")
t = Replace(t, "番地", "-")
t = Replace(t, "−", "-"): t = Replace(t, "ー", "-")
NormAddress = t
End Function
Public Function NormPhone(ByVal s As Variant) As String
Dim t As String: t = LCase$(Trim$(CStr(s)))
t = Replace(t, "-", ""): t = Replace(t, " ", ""): t = Replace(t, " ", "")
NormPhone = t
End Function
Public Function NormEmail(ByVal s As Variant) As String
NormEmail = LCase$(Trim$(CStr(s)))
End Function
VB簡易レーベンシュタイン距離(文字列類似度)
' 小型・十分速い文字列距離。短文の氏名・会社名・住所ブロック向け
Public Function EditDistance(ByVal a As String, ByVal b As String) As Long
Dim la As Long: la = Len(a)
Dim lb As Long: lb = Len(b)
Dim i As Long, j As Long
Dim dp() As Long: ReDim dp(0 To la, 0 To lb)
For i = 0 To la: dp(i, 0) = i: Next
For j = 0 To lb: dp(0, j) = j: Next
For i = 1 To la
For j = 1 To lb
Dim cost As Long: cost = IIf(Mid$(a, i, 1) = Mid$(b, j, 1), 0, 1)
dp(i, j) = WorksheetFunction.Min(dp(i - 1, j) + 1, dp(i, j - 1) + 1, dp(i - 1, j - 1) + cost)
Next
Next
EditDistance = dp(la, lb)
End Function
Public Function SimilarityScore01(ByVal a As String, ByVal b As String) As Double
If Len(a) = 0 And Len(b) = 0 Then SimilarityScore01 = 1#: Exit Function
Dim d As Long: d = EditDistance(a, b)
Dim m As Long: m = WorksheetFunction.Max(Len(a), Len(b))
SimilarityScore01 = 1# - (d / m) ' 0〜1(1が完全一致)
End Function
VB重要ポイントの深掘り
- 正規化は“名寄せの入口”。会社名の法人格除去、住所の地番表記統一、電話・メールの揺れ潰しを必ず通します。
- 距離関数は「似ている」を数値化します。距離→類似度(0〜1)に換算して閾値で判定するのが扱いやすいです。
候補抽出:キーと近傍フィルタで対象を絞る
ブロッキング(候補集合の高速絞り込み)
' ModNayo_Block.bas
Option Explicit
' Data: A=顧客ID, B=氏名, C=会社名, D=住所, E=電話, F=メール
' BlockKey = 会社名の先頭3文字+住所の郵便番号先頭3文字など、軽い近傍キー
Public Sub BuildBlocks(ByVal sheetName As String, ByVal outStart As String, Optional ByVal prefixLen As Long = 3)
Dim a As Variant: a = ReadRegion(Worksheets(sheetName))
Dim blocks As Object: Set blocks = CreateObject("Scripting.Dictionary"): blocks.CompareMode = 1
Dim r As Long
For r = 2 To UBound(a, 1)
Dim comp As String: comp = NormCompany(a(r, 3))
Dim addr As String: addr = NormAddress(a(r, 4))
Dim key As String: key = Left$(comp, prefixLen) & SEP & Left$(addr, prefixLen)
If Not blocks.Exists(key) Then
Dim col As New Collection: col.Add r: Set blocks(key) = col
Else
blocks(key).Add r
End If
Next
' 出力:BlockKey, Count, Rows
Dim out() As Variant: ReDim out(1 To 1, 1 To 3)
out(1, 1) = "BlockKey": out(1, 2) = "Count": out(1, 3) = "Rows"
Dim rowsOut As Long: rowsOut = 1
Dim k As Variant
For Each k In blocks.Keys
Dim col As Collection: Set col = blocks(k)
If col.Count > 1 Then
rowsOut = rowsOut + 1: ReDim Preserve out(1 To rowsOut, 1 To 3)
out(rowsOut, 1) = k
out(rowsOut, 2) = col.Count
out(rowsOut, 3) = Join(CollectionToArray(col), ",")
End If
Next
WriteBlock Worksheets(sheetName), out, outStart
End Sub
Private Function CollectionToArray(ByVal col As Collection) As String()
Dim i As Long, arr() As String: ReDim arr(0 To col.Count - 1)
For i = 1 To col.Count: arr(i - 1) = CStr(col(i)): Next
CollectionToArray = arr
End Function
VB重要ポイントの深掘り
- 「ブロッキング」は名寄せのスピードの鍵。似ている可能性が高いレコードを狭い集合に絞ってから重い判定をかけます。
- 会社名の先頭・住所の先頭・郵便番号など、現場に合う近傍キーを設計しましょう。
スコアリング:複数属性を点数化して確信度を出す
氏名・会社名・住所・電話・メールの合成スコア
' ModNayo_Score.bas
Option Explicit
' 各属性の重みを調整可能(総合スコア 0〜1)
Public Function MatchScore(ByVal nameA As String, ByVal nameB As String, _
ByVal compA As String, ByVal compB As String, _
ByVal addrA As String, ByVal addrB As String, _
ByVal phoneA As String, ByVal phoneB As String, _
ByVal emailA As String, ByVal emailB As String) As Double
Dim sName As Double: sName = SimilarityScore01(NormText(nameA), NormText(nameB))
Dim sComp As Double: sComp = SimilarityScore01(NormCompany(compA), NormCompany(compB))
Dim sAddr As Double: sAddr = SimilarityScore01(NormAddress(addrA), NormAddress(addrB))
Dim sPhone As Double: sPhone = IIf(Len(NormPhone(phoneA)) > 0 And NormPhone(phoneA) = NormPhone(phoneB), 1#, 0#)
Dim sMail As Double: sMail = IIf(Len(NormEmail(emailA)) > 0 And NormEmail(emailA) = NormEmail(emailB), 1#, 0#)
' 重み(例):会社名0.35、住所0.25、氏名0.2、電話0.1、メール0.1
MatchScore = 0.35 * sComp + 0.25 * sAddr + 0.2 * sName + 0.1 * sPhone + 0.1 * sMail
End Function
VB重要ポイントの深掘り
- 属性ごとに“似ている”度合いを計算し、重み付きで合成します。会社名・住所に重みを置くと法人名寄せが安定します。
- 電話・メールは一致なら満点、欠損なら0扱い。欠損が多い現場は重みを下げるか、補助スコアにします。
重複候補の抽出:閾値でランク分けしてレビュー導線を作る
候補一覧の生成(High/Medium/Lowの3段階)
' ModNayo_Candidates.bas
Option Explicit
' inSheet: 元データ、outStart: 出力先開始セル
' thrHigh/Medium: 閾値(例:0.85/0.7)
Public Sub ListDuplicateCandidates(ByVal inSheet As String, ByVal outStart As String, _
Optional ByVal thrHigh As Double = 0.85, Optional ByVal thrMedium As Double = 0.7)
Dim a As Variant: a = ReadRegion(Worksheets(inSheet))
Dim out() As Variant: ReDim out(1 To 1, 1 To 7)
out(1, 1) = "RowA": out(1, 2) = "RowB": out(1, 3) = "Score": out(1, 4) = "Rank"
out(1, 5) = "NameA|NameB": out(1, 6) = "CompA|CompB": out(1, 7) = "AddrA|AddrB"
Dim rowsOut As Long: rowsOut = 1
Dim i As Long, j As Long
For i = 2 To UBound(a, 1) - 1
For j = i + 1 To UBound(a, 1)
' 同一ブロックのみ比較に絞るならブロッキングキーの一致でフィルタ(省略例)
Dim sc As Double
sc = MatchScore(a(i, 2), a(j, 2), a(i, 3), a(j, 3), a(i, 4), a(j, 4), a(i, 5), a(j, 5), a(i, 6), a(j, 6))
If sc >= thrMedium Then
rowsOut = rowsOut + 1: ReDim Preserve out(1 To rowsOut, 1 To 7)
out(rowsOut, 1) = i
out(rowsOut, 2) = j
out(rowsOut, 3) = sc
out(rowsOut, 4) = IIf(sc >= thrHigh, "HIGH", "MEDIUM")
out(rowsOut, 5) = a(i, 2) & " | " & a(j, 2)
out(rowsOut, 6) = a(i, 3) & " | " & a(j, 3)
out(rowsOut, 7) = a(i, 4) & " | " & a(j, 4)
End If
Next
Next
WriteBlock Worksheets(inSheet), out, outStart
End Sub
VB重要ポイントの深掘り
- スコアが高いものを「HIGH」、中程度を「MEDIUM」としてレビュー対象に。低スコアは無視して作業効率を上げます。
- 実運用では必ず「ブロッキング」で組合せ数を減らしてから比較します(全組合せは大規模データで非現実的)。
統合(サバイバーシップ):どの列を残すかのルール化
統合ルール例(最新・非空優先・長さ優先)
' ModNayo_Merge.bas
Option Explicit
' 同一人物/企業と判定された2行を統合し、1行にまとめる
' ルール例:最新登録日優先、非空優先、文字列は長い方優先、電話・メールは正規化一致ならどちらでも
Public Sub MergePair(ByVal ws As Worksheet, ByVal rowA As Long, ByVal rowB As Long, ByVal outStart As String)
Dim a As Variant: a = ws.Range("A1").CurrentRegion.Value
Dim out() As Variant: ReDim out(1 To 2, 1 To UBound(a, 2))
Dim c As Long
' ヘッダ
For c = 1 To UBound(a, 2): out(1, c) = a(1, c): Next
' 結果行
For c = 1 To UBound(a, 2)
Dim vA As String: vA = CStr(a(rowA, c))
Dim vB As String: vB = CStr(a(rowB, c))
Dim pick As String: pick = vA
If Len(vB) > 0 And Len(vA) = 0 Then pick = vB
If Len(vB) > Len(vA) Then pick = vB
' 登録日列(例:F列=6)は新しい方
If c = 6 Then
If IsDate(a(rowA, c)) And IsDate(a(rowB, c)) Then
pick = IIf(CDate(a(rowA, c)) >= CDate(a(rowB, c)), CStr(a(rowA, c)), CStr(a(rowB, c)))
End If
End If
out(2, c) = pick
Next
WriteBlock ws, out, outStart
End Sub
VB重要ポイントの深掘り
- 統合ルール(サバイバーシップ)は現場で必ず合意する項目。最新日付、非空優先、文字列は長い方が情報量が多い、などの原則を決めておきます。
- 片側が空ならもう片側を採用、両方値ありなら“優先基準”で決める、という順序にすると安定します。
例題の通し方:ブロック→候補抽出→統合→名寄せ済みリスト出力
パイプライン実行例(氏名・会社・住所・電話・メール)
' ModNayo_Example.bas
Option Explicit
Public Sub Run_NayosePipeline()
' 1) ブロッキング(会社名・住所の先頭で近傍キー)
BuildBlocks "Customers", "Z1", 3
' 2) 候補抽出(スコア 0.85/0.7でHIGH/MEDIUM)
ListDuplicateCandidates "Customers", "AA1", 0.85, 0.7
' 3) 統合(例:候補一覧から手動で RowA/RowB を選んで実行していく)
' ここでは例として 10行目と11行目を統合
MergePair Worksheets("Customers"), 10, 11, "AC1"
MsgBox "名寄せパイプラインの候補抽出と統合例が完了しました。", vbInformation
End Sub
VB期待動作の確認ポイント
- ブロック一覧で“似ている集合”が見える化。
- 候補抽出にスコアとランクが出て、レビューの焦点が絞れる。
- 統合結果がヘッダ付きで1行にまとまり、統合ルールが反映されている。
落とし穴と対策(深掘り)
正規化不足で候補漏れ・誤判定
会社法人格の残り、住所の表記差、電話のハイフン混在などは必ず前処理で統一します。NormCompany/NormAddress/NormPhone/NormEmailを両側で適用。
全組合せ比較で遅すぎる
「ブロッキング」で集合を絞った上でスコアリングします。近傍キーの設計(先頭一致、郵便番号、都道府県+市区など)が速度の鍵です。
スコア閾値の誤設定
高すぎると見逃し、低すぎるとノイズだらけ。まずは“HIGH=0.85、MEDIUM=0.7”で始め、現場の誤検知率を見ながら調整します。
統合ルールが曖昧で現場が迷う
「最新・非空・長さ優先」を柱に、列ごとの特則(住所は長い方、電話は正規化一致優先など)をドキュメント化します。
セル逐次書きで遅い・固まる
結果は配列で作り、一括書き戻し。10万件でもUIが安定し、毎日回せます。
まとめ:名寄せは「正規化→ブロッキング→スコア→統合」の型で速く・堅牢に
入口で揺れを潰し、候補集合を賢く絞り、重み付きスコアでランク分け、合意済みの統合ルールで1件へ。配列I/O+Dictionary+距離関数の組み合わせで、規模が大きくても壊れない名寄せ運用が実現します。
