ねらい:VBAコードは一切触らず「設定だけ」でJOINできるようにする
「明細にマスタをくっつけたい」「別システムのコードを名称に変えたい」
こういう“JOIN作業”を、毎回 VLOOKUP や XLOOKUP、あるいは専用マクロを書き直して対応していると、
そのたびに式が増え、マクロが増え、メンテする人も増えていきます。
ここで目指す「完全ノーコードのJOINツール」は、こういう世界です。
JOINの内容はすべて「設定シート」に書く。
VBAのコードは一切触らない。
ユーザーは、設定シートを書き換えるだけで、別のJOINにもそのまま使える。
つまり、JOINのロジックは汎用エンジンに閉じ込めておき、
「どのシートの、どの列をキーに、どの列を持ってくるか」を設定だけで差し替えられるようにします。
全体設計:JOIN設定シート+汎用JOINエンジン
シート構成のイメージ
最低限、次の3種類のシートを用意します。
JOIN設定シート(ConfigJoin)
JOINの内容をすべてここに書きます。キー列、JOIN元シート、JOIN先シート、持ってくる列など。
明細シート(Detail)
JOINの「左側」になるシートです。売上明細、取引明細など。
マスタシート(MasterXXX)
JOINの「右側」になるシートです。顧客マスタ、商品マスタ、コードマスタなど。
VBA側は、「ConfigJoin を読み、そこに書かれたルール通りに JOIN を実行する汎用エンジン」だけを持ちます。
JOINの内容を変えたいときは、ConfigJoin の内容だけを変えます。
JOIN設定シートの設計:何をどう書くか
1行=1つのJOINルールという形にする
ConfigJoin シートに、次のような列を用意します。
A列:有効フラグ(Y の行だけ処理)
B列:DetailSheet(明細シート名)
C列:DetailKeyCol(明細側キー列)
D列:MasterSheet(マスタシート名)
E列:MasterKeyCol(マスタ側キー列)
F列:MasterValueCol(マスタから持ってくる列)
G列:DetailOutCol(明細側の出力列)
H列:NotFoundValue(マスタに無いときに入れる値。空なら空欄)
例えば、こういう感じです。
| A | B | C | D | E | F | G | H |
|---|---|---|---|---|---|---|---|
| Y | Detail | B | MstCust | A | B | D | #顧客未登録 |
| Y | Detail | C | MstItem | A | B | E | #商品未登録 |
1行目は「Detail の B列(顧客コード)をキーに、MstCust の A列とJOINし、B列(顧客名)を Detail の D列に書く。見つからなければ #顧客未登録 と書く」という意味になります。
2行目は「Detail の C列(商品コード)をキーに、MstItem の A列とJOINし、B列(商品名)を Detail の E列に書く」という意味です。
ここまで決めてしまえば、JOINの中身はすべて「表」で表現できます。
VBAは、この表を読み取って動くだけです。
コア部品:マスタをDictionaryに載せる汎用関数
任意のマスタシートを「キー列→値列」のDictionaryにする
JOINのたびにマスタを読み直すと遅くなるので、「マスタ名+キー列+値列」を指定して Dictionary を作る汎用関数を用意します。
' ModJoinEngine.bas
Option Explicit
Private Function LoadMasterDict( _
ByVal sheetName As String, _
ByVal keyCol As Long, _
ByVal valCol As Long) As Object
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets(sheetName)
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, keyCol).End(xlUp).Row
If lastRow < 2 Then
Set LoadMasterDict = CreateObject("Scripting.Dictionary")
Exit Function
End If
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = 1
Dim r As Long
Dim key As String
Dim val As Variant
For r = 2 To lastRow
key = CStr(ws.Cells(r, keyCol).Value)
If key <> "" Then
val = ws.Cells(r, valCol).Value
If Not dict.Exists(key) Then
dict.Add key, val
End If
End If
Next
Set LoadMasterDict = dict
End Function
VBここでのポイントは、「どのマスタでも同じ関数で Dictionary 化できる」ことです。
シート名、キー列、値列を変えるだけで、顧客マスタでも商品マスタでも同じ関数が使えます。
JOIN設定を読み込む:1行を構造体にして配列で持つ
JOIN設定1行分の型を定義する
ConfigJoin の1行を、VBA側では構造体として扱います。
Private Type JoinRule
Enabled As Boolean
DetailSheet As String
DetailKeyCol As Long
MasterSheet As String
MasterKeyCol As Long
MasterValueCol As Long
DetailOutCol As Long
NotFoundValue As String
End Type
VB列番号は数字で扱いたいので、列記号(A,B,C…)で書かれていても番号に変換できるようにしておきます。
Private Function ColToNumber(ByVal colRef As Variant) As Long
If IsNumeric(colRef) Then
ColToNumber = CLng(colRef)
Else
ColToNumber = Range(CStr(colRef) & "1").Column
End If
End Function
VBConfigJoin から JoinRule 配列を作る
Private Function LoadJoinRules() As Variant
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("ConfigJoin")
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
If lastRow < 2 Then
LoadJoinRules = Empty
Exit Function
End If
Dim data As Variant
data = ws.Range(ws.Cells(2, 1), ws.Cells(lastRow, 8)).Value
Dim rules() As JoinRule
ReDim rules(1 To UBound(data, 1))
Dim i As Long
For i = 1 To UBound(data, 1)
rules(i).Enabled = (UCase$(CStr(data(i, 1))) = "Y")
rules(i).DetailSheet = CStr(data(i, 2))
rules(i).DetailKeyCol = ColToNumber(data(i, 3))
rules(i).MasterSheet = CStr(data(i, 4))
rules(i).MasterKeyCol = ColToNumber(data(i, 5))
rules(i).MasterValueCol = ColToNumber(data(i, 6))
rules(i).DetailOutCol = ColToNumber(data(i, 7))
rules(i).NotFoundValue = CStr(data(i, 8))
Next
LoadJoinRules = rules
End Function
VBこれで、「ConfigJoin の内容」が JoinRule の配列としてメモリに載ります。
以降の処理は、この配列を見ながら動くだけです。
汎用JOINエンジン:設定に従ってJOINを実行する
速度アップ用のラッパー
JOINは行数が多くなりがちなので、画面更新や再計算を止めておきます。
Private Sub SpeedOn()
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.Calculation = xlCalculationManual
End Sub
Private Sub SpeedOff()
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
VBメイン処理:ルールごとにJOINをかける
「完全ノーコード」にするために、JOINの中身は一切ハードコーディングしません。
JoinRule の配列をなめて、1ルールずつ汎用JOINを呼びます。
Public Sub RunJoinTool()
Dim rules As Variant
rules = LoadJoinRules()
If IsEmpty(rules) Then
MsgBox "ConfigJoinにJOIN設定がありません。", vbInformation
Exit Sub
End If
SpeedOn
Dim i As Long
For i = LBound(rules) To UBound(rules)
If rules(i).Enabled Then
ApplyJoinRule rules(i)
End If
Next i
SpeedOff
MsgBox "JOINツールの処理が完了しました。", vbInformation
End Sub
VB1つのJOINルールを適用する汎用処理
ここが心臓部です。
Private Sub ApplyJoinRule(ByRef rule As JoinRule)
Dim wsD As Worksheet
Set wsD = ThisWorkbook.Worksheets(rule.DetailSheet)
Dim lastRowD As Long
lastRowD = wsD.Cells(wsD.Rows.Count, rule.DetailKeyCol).End(xlUp).Row
If lastRowD < 2 Then Exit Sub
Dim dictM As Object
Set dictM = LoadMasterDict(rule.MasterSheet, rule.MasterKeyCol, rule.MasterValueCol)
Dim r As Long
Dim key As String
Dim val As Variant
For r = 2 To lastRowD
key = CStr(wsD.Cells(r, rule.DetailKeyCol).Value)
If key <> "" Then
If dictM.Exists(key) Then
val = dictM(key)
wsD.Cells(r, rule.DetailOutCol).Value = val
Else
If rule.NotFoundValue <> "" Then
wsD.Cells(r, rule.DetailOutCol).Value = rule.NotFoundValue
Else
wsD.Cells(r, rule.DetailOutCol).Value = ""
End If
End If
End If
Next
End Sub
VBここで注目してほしいのは、「JOINの中身は一切固定していない」という点です。
どのシートを明細とするか。
どのシートをマスタとするか。
どの列をキーにするか。
どの列を持ってくるか。
見つからなかったときに何を書くか。
すべて rule(=ConfigJoin の1行)に書かれていて、
エンジンはそれを忠実になぞっているだけです。
例題:売上明細に顧客名と商品名をJOINする
想定するシート構成
Detail シート
A列:伝票番号
B列:顧客コード
C列:商品コード
D列:数量
E列:金額
F列:顧客名(JOINで付ける)
G列:商品名(JOINで付ける)
MstCust シート
A列:顧客コード
B列:顧客名
MstItem シート
A列:商品コード
B列:商品名
ConfigJoin シートは、次のように書きます。
1行目
DetailSheet = Detail
DetailKeyCol = B
MasterSheet = MstCust
MasterKeyCol = A
MasterValueCol = B
DetailOutCol = F
NotFoundValue = #顧客未登録
2行目
DetailSheet = Detail
DetailKeyCol = C
MasterSheet = MstItem
MasterKeyCol = A
MasterValueCol = B
DetailOutCol = G
NotFoundValue = #商品未登録
あとは、RunJoinTool を実行するだけです。
VBAのコードは一切触りません。
結果として、Detail の F列に顧客名、G列に商品名が一気に埋まります。
顧客マスタや商品マスタの中身が変わっても、ConfigJoin はそのまま使えます。
重要ポイントの深掘り:本当に「ノーコード」で回すための工夫
ConfigJoin の「検証」を入れておくべき理由
完全ノーコードにすると、現場の人が自由に設定を変えられる反面、
シート名のタイプミス、列指定のミスなども起こりやすくなります。
実務で使うなら、RunJoinTool の最初に
指定された DetailSheet/MasterSheet が存在するか
指定された列番号がシートの列数を超えていないか
NotFoundValue に変な式が入っていないか
といったチェックを入れて、問題があれば処理を止めてメッセージを出す、という“安全装置”を付けておくと安心です。
キーの正規化(全角半角・大文字小文字)をどこでやるか
JOINの精度は、「キーがどれだけ揃っているか」に依存します。
顧客コードや商品コードのような“きれいなコード”ならそのままで良いですが、
メールアドレスや会社名などをキーにするJOINでは、正規化が必要です。
完全ノーコードでやるなら、ConfigJoin に「NormalizeKey関数を使うかどうか」のフラグを追加する、
あるいは「キー列のTransform」を別途持たせる、という拡張も考えられます。
まずは「コード系のキー」を対象にして運用を始め、
必要になったら「キー正規化付きJOINルール」を追加する、という段階的な導入がおすすめです。
JOINの種類を増やす(LEFT JOIN/INNER JOINなど)
今のテンプレは、実質的に「LEFT JOIN」です。
明細に行があれば、マスタに無くても行は残り、NotFoundValue が入ります。
INNER JOIN(マスタにあるものだけ残したい)
RIGHT JOIN(マスタ側を基準にしたい)
などが必要になったら、ConfigJoin に「JoinType」列を追加し、
ApplyJoinRule の中で「行を残すかどうか」を切り替える、という拡張もできます。
ここまで行くと、ほぼ「Excel内の簡易SQLエンジン」になりますが、
土台は今回の「JoinRule+汎用エンジン」のままで十分対応できます。
まとめ:完全ノーコードJOINツールは「JOINを表にして、コードはエンジンに閉じ込める」発想
このテンプレの本質は、とてもシンプルです。
JOINの中身(どのシートのどの列をどう結びつけるか)を、すべて ConfigJoin という表に書き出す。
VBA側は、その表を読み取り、Dictionary を使って高速にJOINする汎用エンジンだけを持つ。
JOIN内容を変えたいときは、ConfigJoin の行を追加・修正するだけで、コードは一切触らない。
これができると、「顧客×マスタ」「商品×マスタ」「コード×名称」など、
JOINパターンがいくつ増えても、ツールは1本で済みます。
