ねらい:CSVの「ヘッダ・型・必須・重複・範囲・書式」を一括検証して、エラーを“見える化”する
CSV取り込み前に“壊れていないか”を機械的にチェックできると事故が激減します。VBAなら“安全読込→ルール定義→行ごと検証→エラーレポート出力”の型で、列変更にも強く、数万行でも短時間で完了します。初心者でも貼って動くテンプレを、例題付きでやさしく解説します。
入力仕様と共通基盤(安全読込・列アクセス・ログ)
シート前提とファイル条件
- Dataシートは不要。CSVファイルを直接読み込み、配列で処理します。
- 1行目はヘッダ。区切りはカンマ。改行や引用はExcelのOpenで解決します。
ユーティリティ(貼って動く最小セット)
' ModCsv_Base.bas
Option Explicit
Public Function OpenCsvToArray(ByVal csvPath As String) As Variant
' Excelで開いて CurrentRegion を配列化→閉じる(値のみ)
Dim wb As Workbook, ws As Worksheet
Set wb = Application.Workbooks.Open(Filename:=csvPath)
Set ws = wb.Worksheets(1)
OpenCsvToArray = ws.Range("A1").CurrentRegion.Value
wb.Close SaveChanges:=False
End Function
Public Sub WriteBlock(ByVal ws As Worksheet, ByVal a As Variant, ByVal startCell As String)
ws.Range(startCell).Resize(UBound(a, 1), UBound(a, 2)).Value = a
End Sub
Public Sub FormatBlock(ByVal ws As Worksheet, ByVal startCell As String)
With ws.Range(startCell).CurrentRegion
.Columns.AutoFit
.Borders.LineStyle = xlContinuous
End With
End Sub
Public Function ColIndex(ByVal headers As Variant, ByVal name As String) As Long
Dim c As Long
For c = 1 To UBound(headers, 2)
If LCase$(Trim$(CStr(headers(1, c)))) = LCase$(Trim$(name)) Then
ColIndex = c: Exit Function
End If
Next
ColIndex = 0 ' 見つからない
End Function
Public Function IsEmptyCell(ByVal v As Variant) As Boolean
IsEmptyCell = (Len(Trim$(CStr(v))) = 0)
End Function
VB重要部分の深掘り
- CSVは“開く→CurrentRegion→閉じる”で安全・高速。一切セル操作せず配列で処理します。
- ヘッダは1行目固定。列アクセスは“名前で検索”にして、順番が変わっても壊れない設計にします。
- 出力は必ず一括書式適用(罫線・AutoFit)でレビュアーが読みやすい形に整えます。
ルール定義:必須・型・長さ・正規表現・範囲・ユニーク
バリデーションルールを構造体で持つ(初心者に優しい書き方)
' ModCsv_Rules.bas
Option Explicit
Public Type FieldRule
Name As String ' 列名(ヘッダと一致)
Required As Boolean ' 必須
TypeName As String ' "text"/"number"/"date"/"email"/"phone"
MinLen As Long ' 最小文字数(テキスト)
MaxLen As Long ' 最大文字数(テキスト)
MinVal As Double ' 最小値(数値・日付序数)
MaxVal As Double ' 最大値(数値・日付序数)
Pattern As String ' 正規表現(簡易:Like記法)
Unique As Boolean ' ユニーク制約
End Type
Public Function SampleRules() As Variant
' 例:受注CSV(OrderDate, Customer, Email, Item, Qty, Price)
Dim r() As FieldRule: ReDim r(1 To 6)
r(1) = MakeRule("OrderDate", True, "date", 0, 0, 0, 0, "", False)
r(2) = MakeRule("Customer", True, "text", 1, 100, 0, 0, "", False)
r(3) = MakeRule("Email", False, "email", 0, 100, 0, 0, "*@*.*", False)
r(4) = MakeRule("Item", True, "text", 1, 100, 0, 0, "", False)
r(5) = MakeRule("Qty", True, "number", 0, 0, 1, 100000, "", False)
r(6) = MakeRule("Price", True, "number", 0, 0, 0, 100000000, "", False)
SampleRules = r
End Function
Private Function MakeRule(ByVal name As String, ByVal req As Boolean, ByVal typeName As String, _
ByVal minLen As Long, ByVal maxLen As Long, ByVal minVal As Double, ByVal maxVal As Double, _
ByVal pattern As String, ByVal unique As Boolean) As FieldRule
Dim fr As FieldRule
fr.Name = name: fr.Required = req: fr.TypeName = typeName
fr.MinLen = minLen: fr.MaxLen = maxLen
fr.MinVal = minVal: fr.MaxVal = maxVal
fr.Pattern = pattern: fr.Unique = unique
MakeRule = fr
End Function
VB重要部分の深掘り
- ルールは“列名基準”で定義。順番が変わっても適用できます。
- 型は最低限の5種に整理。実務で増やすなら TypeName を拡張(URL、郵便番号など)。
- 正規表現はLike記法を使い、初心者でも「@.*」のような簡易パターンで検証できます。
検証ロジック:行ごとにルール適用、重複・範囲・型の判定
検証の本体(高速・わかりやすい)
' ModCsv_Validate.bas
Option Explicit
Public Sub ValidateCsv(ByVal csvPath As String, ByVal rules As Variant, ByVal outSheet As String)
Dim data As Variant: data = OpenCsvToArray(csvPath)
Dim headers As Variant: headers = data ' 1行目がヘッダ
' 列名→インデックス
Dim idx As Object: Set idx = CreateObject("Scripting.Dictionary"): idx.CompareMode = 1
Dim c As Long
For c = 1 To UBound(data, 2)
idx(LCase$(Trim$(CStr(data(1, c))))) = c
Next
' ユニークチェック用の辞書(列ごと)
Dim uniq As Object: Set uniq = CreateObject("Scripting.Dictionary"): uniq.CompareMode = 1
Dim i As Long
For i = LBound(rules) To UBound(rules)
If rules(i).Unique Then
Set uniq(rules(i).Name) = CreateObject("Scripting.Dictionary")
uniq(rules(i).Name).CompareMode = 1
End If
Next
' エラー出力配列: Row, Field, Issue, Value
Dim out() As Variant: ReDim out(1 To 1, 1 To 4)
out(1, 1) = "Row": out(1, 2) = "Field": out(1, 3) = "Issue": out(1, 4) = "Value"
Dim rowsOut As Long: rowsOut = 1
Dim r As Long
For r = 2 To UBound(data, 1)
For i = LBound(rules) To UBound(rules)
Dim col As Long: col = ColIndex(headers, rules(i).Name)
If col = 0 Then
rowsOut = rowsOut + 1: ReDim Preserve out(1 To rowsOut, 1 To 4)
out(rowsOut, 1) = r: out(rowsOut, 2) = rules(i).Name: out(rowsOut, 3) = "Missing header": out(rowsOut, 4) = ""
GoTo NextField
End If
Dim v As Variant: v = data(r, col)
Dim vText As String: vText = Trim$(CStr(v))
' 必須
If rules(i).Required And IsEmptyCell(v) Then
AddErr out, rowsOut, r, rules(i).Name, "Required", vText
GoTo NextField
End If
' 型チェック
If Not IsEmptyCell(v) Then
Select Case LCase$(rules(i).TypeName)
Case "number": If Not IsNumeric(v) Then AddErr out, rowsOut, r, rules(i).Name, "Not number", vText
Case "date": If Not IsDate(v) Then AddErr out, rowsOut, r, rules(i).Name, "Not date", vText
Case "email": If InStr(1, vText, "@") = 0 Or InStrRev(vText, ".") = 0 Then AddErr out, rowsOut, r, rules(i).Name, "Invalid email", vText
Case "phone": If Not vText Like "*#*" Then AddErr out, rowsOut, r, rules(i).Name, "Invalid phone", vText
Case "text": ' 何もしない
End Select
End If
' 長さ
If rules(i).MinLen > 0 And Len(vText) < rules(i).MinLen Then AddErr out, rowsOut, r, rules(i).Name, "Too short", vText
If rules(i).MaxLen > 0 And Len(vText) > rules(i).MaxLen Then AddErr out, rowsOut, r, rules(i).Name, "Too long", vText
' 範囲(数値/日付)
If rules(i).TypeName = "number" And Not IsEmptyCell(v) Then
If rules(i).MinVal <> 0 Or rules(i).MaxVal <> 0 Then
Dim num As Double: num = CDbl(v)
If rules(i).MinVal <> 0 And num < rules(i).MinVal Then AddErr out, rowsOut, r, rules(i).Name, "Below min", vText
If rules(i).MaxVal <> 0 And num > rules(i).MaxVal Then AddErr out, rowsOut, r, rules(i).Name, "Above max", vText
End If
ElseIf rules(i).TypeName = "date" And Not IsEmptyCell(v) Then
Dim dt As Date: dt = CDate(v)
If rules(i).MinVal <> 0 And dt < rules(i).MinVal Then AddErr out, rowsOut, r, rules(i).Name, "Before min date", vText
If rules(i).MaxVal <> 0 And dt > rules(i).MaxVal Then AddErr out, rowsOut, r, rules(i).Name, "After max date", vText
End If
' パターン(Like)
If Len(rules(i).Pattern) > 0 And Not IsEmptyCell(v) Then
If Not (vText Like rules(i).Pattern) Then AddErr out, rowsOut, r, rules(i).Name, "Pattern mismatch", vText
End If
' ユニーク
If rules(i).Unique And Not IsEmptyCell(v) Then
Dim k As String: k = LCase$(vText)
If uniq(rules(i).Name).Exists(k) Then
AddErr out, rowsOut, r, rules(i).Name, "Duplicate", vText
Else
uniq(rules(i).Name)(k) = True
End If
End If
NextField:
Next i
Next r
' 出力
Dim ws As Worksheet
On Error Resume Next: Set ws = Worksheets(outSheet): On Error GoTo 0
If ws Is Nothing Then Set ws = Worksheets.Add: ws.Name = outSheet Else ws.Cells.Clear
WriteBlock ws, out, "A1"
FormatBlock ws, "A1"
End Sub
Private Sub AddErr(ByRef out() As Variant, ByRef rowsOut As Long, ByVal r As Long, ByVal field As String, ByVal issue As String, ByVal vText As String)
rowsOut = rowsOut + 1: ReDim Preserve out(1 To rowsOut, 1 To 4)
out(rowsOut, 1) = r
out(rowsOut, 2) = field
out(rowsOut, 3) = issue
out(rowsOut, 4) = vText
End Sub
VB重要部分の深掘り
- ループは“行→ルール”の二重でシンプル。各チェックは早期にエラー追加し、次の項目へ進みます。
- ユニーク判定は“列ごとの辞書”で高速。大量件数でも一瞬です。
- パターンはLikeで十分な場面が多い。高度な正規表現が必要なら、後から拡張(VBScript.RegExp)可能です。
拡張オプション:ヘッダ検証・必須列の存在・サマリー出力
追加チェックと要約レポート
' ModCsv_Extras.bas
Option Explicit
Public Sub CheckHeaders(ByVal data As Variant, ByVal rules As Variant, ByRef out() As Variant, ByRef rowsOut As Long)
Dim need As Object: Set need = CreateObject("Scripting.Dictionary"): need.CompareMode = 1
Dim i As Long: For i = LBound(rules) To UBound(rules): need(rules(i).Name) = False: Next
Dim c As Long
For c = 1 To UBound(data, 2)
Dim name As String: name = Trim$(CStr(data(1, c)))
If need.Exists(name) Then need(name) = True
Next
Dim k As Variant
For Each k In need.Keys
If Not need(k) Then
rowsOut = rowsOut + 1: ReDim Preserve out(1 To rowsOut, 1 To 4)
out(rowsOut, 1) = 1: out(rowsOut, 2) = CStr(k): out(rowsOut, 3) = "Header missing": out(rowsOut, 4) = ""
End If
Next
End Sub
Public Sub SummarizeErrors(ByVal outSheet As String)
Dim ws As Worksheet: Set ws = Worksheets(outSheet)
Dim a As Variant: a = ws.Range("A1").CurrentRegion.Value
If UBound(a, 1) < 2 Then Exit Sub
Dim dIssue As Object: Set dIssue = CreateObject("Scripting.Dictionary")
Dim r As Long
For r = 2 To UBound(a, 1)
Dim key As String: key = CStr(a(r, 2)) & " | " & CStr(a(r, 3)) ' Field | Issue
dIssue(key) = IIf(dIssue.Exists(key), dIssue(key) + 1, 1)
Next
Dim out() As Variant: ReDim out(1 To dIssue.Count + 1, 1 To 2)
out(1, 1) = "Field | Issue": out(1, 2) = "Count"
Dim i As Long: i = 2, k As Variant
For Each k In dIssue.Keys
out(i, 1) = k: out(i, 2) = dIssue(k): i = i + 1
Next
WriteBlock ws, out, "G1"
FormatBlock ws, "G1"
ws.Columns("H").NumberFormatLocal = "#,##0"
End Sub
VB重要部分の深掘り
- “ヘッダが存在しない”は最大級のエラー。最初に検知して直すべきです。
- サマリー(Field | Issue | Count)を右側に出すと、どこが壊れているか一目で把握できます。
例題の通し方:ルール定義→検証→サマリー→出力
実行テンプレ(貼って動く)
' ModCsv_Example.bas
Option Explicit
Public Sub Run_CsvValidation()
Dim csvPath As String: csvPath = ThisWorkbook.Path & "\orders.csv" ' 例:同フォルダに置く
Dim rules As Variant: rules = SampleRules()
' メイン検証
ValidateCsv csvPath, rules, "CSV_Errors"
' 要約
SummarizeErrors "CSV_Errors"
MsgBox "CSV検証が完了しました。エラー一覧と要約を確認してください。", vbInformation
End Sub
VB期待動作:orders.csvを読み込み、CSV_Errorsシートに「Row, Field, Issue, Value」のエラー一覧が出ます。右側に“Field | Issue | Count”の集計が出て、修正の優先度がすぐ決められます。
落とし穴と対策(深掘り)
文字列日付・文字数値の型崩れ
型チェックで弾くだけでなく、前処理(ToDate/ToNumber)に落としてから再検証する運用も有効です。
必須列が欠けているのに処理を続行
ヘッダ検証で“Missing header”を最初に出し、修正が終わるまで取り込みを保留にするのが安全です。
パターンが厳しすぎて過剰検知
Likeのパターンは“緩め”から始め、現場の誤検知率を見て精密化します(例:メールは最低限“@と.”の存在チェック)。
ユニーク制約の誤判定
前後空白・大小の揺れで重複を見逃します。ユニーク判定前に Trim+LCase を掛ける(本テンプレは vText を小文字化しています)。
セル逐次処理で遅い
配列に載せて検証→一括書き戻しが鉄則。数万行でも短時間で完了し、画面が固まりません。
まとめ:ルールを“列名”で持ち、型・必須・範囲・重複を一撃で見える化する
- 入力は配列化して安全に。ルールは列名基準で、順番変更に強くする。
- 検証は“必須→型→長さ→範囲→パターン→ユニーク”の順で早期にエラー化。
- エラーは「Row, Field, Issue, Value」で出し、要約を併記。修正の優先度が即決できます。
