【テクニカル・上級編】【CSV型推論エンジン】先頭データ走査により数値・日付・文字列を全自動判定する動的スキーマ解析ロジック – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

【CSV型推論エンジン】先頭データ走査により数値・日付・文字列を全自動判定する動的スキーマ解析ロジック

レガシーシステムとモダンなデータパイプラインが交錯する現場において、最も不毛で時間を奪われる作業は何か。それは「ヘッダーすらない、スキーマ定義がドキュメント化されていない巨大CSVのインポート」である。

「この列は整数なのか浮動小数点なのか」「この文字列は日付としてパースすべきか、あるいは先頭のゼロを維持すべきコードなのか」――。
これを人力で判定し、CREATE TABLE文を書き起こす作業は、エンジニアの寿命を確実に削る。

今回は、VBScript(WSH環境)の限界を知り尽くしたアーキテクトのために、外部ライブラリに一切依存せず、純粋なVBScriptのメモリ管理と正規表現エンジンを極限までチューニングして構築した「CSV型推論エンジン」の全貌を公開する。

1. レガシー環境におけるCSV解析のパラダイムシフト

VBScriptによるテキスト処理において、最大のボトルネックは「メモリの断片化」と「不適切なオブジェクトの生成・破棄」である。特に`ADODB.Stream`や`Scripting.FileSystemObject`を雑に扱うと、数万行の走査でメモリリークを引き起こし、WSHホストプロセス(`wscript.exe` / `cscript.exe`)がOSにメモリを返さなくなる。

今回の推論エンジンでは、以下のアーキテクチャ上の制約をクリアしている。

  • ストリーミング型先頭走査: ファイル全体をメモリにロードせず、先頭 $N$ 行(デフォルト5000行)のみをバッファリングして解析を打ち切る。
  • ゼロ・ガベージに近いオブジェクト管理: ループ内でのオブジェクト生成を極力排除し、処理終了時には確実に参照を断ち切る。
  • 型降格(Type Demotion)の回避: 一度でも「文字列」と判定された列は、それ以降の数値データを無視して安全に「String」へと収束させる。

2. 型推論アルゴリズムの設計思想

型判定の優先順位は、データの整合性を担保するために極めて重要である。VBScriptの暗黙の型変換(Variant型)に身を委ねると、意図しないデータ破壊を招く。

判定ロジックは以下の順序で評価を行う。

1. Null / 空白判定: すべての行で値が空の場合はデフォルトで `String`(または `Null`)。
2. 整数型 (Integer / Long): 正規表現 `^\-?[0-9]+$`。ただし、先頭が `0` で始まる文字列(例: `0123`)は、郵便番号やコード値とみなし、数値ではなく文字列として扱う。
3. 浮動小数点型 (Double): 正規表現 `^\-?[0-9]\.[0-9]+$`。
4. 日付型 (Date): VBScriptの `IsDate()` 関数に加え、Excel等の影響で混入する不自然な日付フォーマットを排除するバリデーションを通す。
5. 文字列 (String): 上記のいずれにも該当しない場合。

3. 実装コード:完全自律型CSV型推論エンジン

以下のコードは、指定されたCSVファイルの先頭行を解析し、最適なデータ型と、SQL Server等にそのまま流し込めるDDLの断片を出力する実用スクリプトである。

‘ ==============================================================================
‘ Script Name: CsvTypeInferenceEngine.vbs
‘ Description: 先頭データの走査によるCSV動的スキーマ解析・型推論エンジン
‘ Architecture: Chief Architect Verified
‘ ==============================================================================

Option Explicit

Const ForReading = 1
Const SampleLimit = 5000 ‘ 型推論に使用する最大行数

‘ 実行のエントリーポイント
Main

Sub Main()
Dim fso, targetPath
Set fso = CreateObject(“Scripting.FileSystemObject”)

‘ テスト対象のCSVパス(環境に合わせて変更してください)
targetPath = fso.BuildPath(fso.GetParentFolderName(WScript.ScriptFullName), “sample.csv”)

If Not fso.FileExists(targetPath) Then
WScript.Echo “Error: 対象のCSVファイルが見つかりません -> ” & targetPath
Exit Sub
End If

WScript.Echo “=== CSV Schema Inference Started ===”
AnalyzeCsvSchema targetPath, fso
WScript.Echo “=== Process Completed ===”

Set fso = Nothing
End Sub

Sub AnalyzeCsvSchema(filePath, fso)
Dim stream, line, rowCount, colCount, i
Dim headers, types, isInitialized

Set stream = CreateObject(“ADODB.Stream”)
stream.Type = 2 ‘ text
stream.Charset = “UTF-8” ‘ 必要に応じて “Shift_JIS” 等に変更
stream.Open
stream.LoadFromFile filePath

rowCount = 0
isInitialized = False

Dim regExNum, regExFloat
Set regExNum = CreateObject(“VBScript.RegExp”)
regExNum.Pattern = “^\-?[1-9][0-9]$|^0$”
regExNum.IgnoreCase = True
regExNum.Global = False

Set regExFloat = CreateObject(“VBScript.RegExp”)
regExFloat.Pattern = “^\-?([0-9]+\.[0-9]+|[0-9]+\.|[0-9]\.[0-9]+)$”
regExFloat.IgnoreCase = True
regExFloat.Global = False

Do While Not stream.EOS And rowCount < SampleLimit line = stream.ReadText(-2) ' adReadLine rowCount = rowCount + 1 ' 簡易CSVパーサー(ダブルクォーテーションのエスケープ等を考慮した本格的スプリット) Dim fields fields = ParseCsvLine(line) If Not isInitialized Then colCount = UBound(fields) ReDim types(colCount) ' 初期状態では全列を最高精度の型(Integer等)と仮定し、検証によって降格させる For i = 0 To colCount types(i) = "Integer" Next isInitialized = True End If ' 各列のデータ型を評価・更新 For i = 0 To UBound(fields) If i <= colCount Then types(i) = EvaluateType(fields(i), types(i), regExNum, regExFloat) End If Next Loop ' ストリームのクローズとメモリ解放 stream.Close Set stream = Nothing Set regExNum = Nothing Set regExFloat = Nothing ' 結果の出力(DDL生成) WScript.Echo "Analyzed Rows: " & rowCount WScript.Echo vbCrLf & "--- Suggested SQL DDL ---" WScript.Echo "CREATE TABLE ImportedCsvData (" For i = 0 To colCount WScript.Echo " Column_" & (i + 1) & " " & GetSqlDataType(types(i)) & "," Next WScript.Echo ");" End Sub ' 1セルごとの型評価と収束ロジック(型降格の核心) Function EvaluateType(val, currentType, regExNum, regExFloat) val = Trim(val) ' 空値は現在の型を維持 If val = "" Then EvaluateType = currentType Exit Function End If ' すでに String ならこれ以上降格しない If currentType = "String" Then EvaluateType = "String" Exit Function End If ' Integer の評価 If currentType = "Integer" Then If regExNum.Test(val) Then EvaluateType = "Integer" Exit Function Else ' 整数でなければ Float の評価へ移行 currentType = "Float" End If End If ' Float の評価 If currentType = "Float" Then If regExFloat.Test(val) Or regExNum.Test(val) Then EvaluateType = "Float" Exit Function Else ' 数値でなければ Date の評価へ移行 currentType = "Date" End If End If ' Date の評価 If currentType = "Date" Then ' IsDateは "123" などの数値も日付と誤認するため、厳格なチェックが必要 If IsDate(val) And Not IsNumeric(val) Then EvaluateType = "Date" Exit Function Else ' どれにも当てはまらない場合は最強の型である String へ降格 EvaluateType = "String" Exit Function End If End If EvaluateType = "String" End Function ' 簡易CSV行パーサー(カンマ区切り・ダブルクォーテーション対応) Function ParseCsvLine(strLine) Dim inQuotes, i, c, currentField, fields() Dim fCount inQuotes = False fCount = 0 ReDim fields(0) currentField = "" For i = 1 To Len(strLine) c = Mid(strLine, i, 1) Select Case c Case """" inQuotes = Not inQuotes Case "," If Not inQuotes Then ReDim Preserve fields(fCount) fields(fCount) = currentField fCount = fCount + 1 currentField = "" Else currentField = currentField & c End If Case Else currentField = currentField & c End Select Next ReDim Preserve fields(fCount) fields(fCount) = currentField ParseCsvLine = fields End Function ' 推論された型をRDB用のデータ型へマッピング Function GetSqlDataType(vbsTypeName) Select Case vbsTypeName Case "Integer" GetSqlDataType = "INT" Case "Float" GetSqlDataType = "FLOAT" Case "Date" GetSqlDataType = "DATETIME" Case Else GetSqlDataType = "VARCHAR(255)" End Select End Function ---

4. チーフアーキテクトが解説するコードの急所

A. ADODB.Streamによるエンコード安全性の確保

`FileSystemObject`の`OpenTextFile`は、システムのデフォルトANSIコードページに依存するため、現代のUTF-8(BOMなし)やLF改行混じりのCSVにおいて文字化けや無限ループを引き起こす。
本コードでは `ADODB.Stream` を明示的に採用し、Charsetを指定することで、環境依存のトラブルを完全に排除している。

B. 型降格(Type Demotion)のステートマシン

配列データの初期値を最も制約の厳しい `Integer` とし、データが出現するたびに `Integer` → `Float` → `Date` → `String` へと自動的に安全な方向へ降格(Demotion)させている。
これにより、先頭の数行がたまたま整数であっても、途中に小数や文字列が混入した瞬間に、スキーマ破綻を起こすことなく自動的に `VARCHAR` や `FLOAT` へエスカレーションされる。

C. 正規表現オブジェクトのスコープ最適化

`VBScript.RegExp` オブジェクトのインスタンス化は、COMコンポーネントの生成を伴うため重い処理である。これをループの「内側」で生成すると、数千行の走査でパフォーマンスが劇的に低下する。
本エンジンでは、ループの外側で一度だけインスタンス化し、`Pattern` を固定した上で `Test` メソッドを高速に使い回す設計としている。

5. レガシーの皮を被ったモダン・インフラの構築へ

「古い言語だから」「VBScriptだから」と言い訳をする必要はない。WSHとVBScriptの本質を理解し、メモリのライフサイクルと型システムの挙動を完全に掌握していれば、これほど軽量で、OS標準機能だけで動作する強力な自動化ツールは他に存在しない。

現場のデータエンジニアリングをスマートに自動化し、無意味な手作業の苦役から自らを解放せよ。これが、真のプロフェッショナルエンジニアの仕事である。

タイトルとURLをコピーしました