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

スポンサーリンク

こんにちは!VBScriptの世界へようこそ。

「Excelマクロの自動記録は使えるようになったけれど、もっと本格的な自動化ツールを作りたい」「膨大なCSVファイルをデータベースに取り込む前処理を自動化したい」……そんなステップアップを目指すあなたのために、今回はVBScriptを使った本格的な「CSV型推論エンジン」の構築に挑戦します!

ヘッダー情報(列名)すら存在しない謎のCSVファイルが届いたとき、あなたならどうしますか? 1行ずつ人間が確認してデータベースのテーブル定義(`CREATE TABLE`文)を書くのは大変ですよね。

この記事では、先頭の数千行(サンプルデータ)を高速に走査し、各列が「数値(整数・小数)」「日付」「文字列」のどれに該当するかを自動判定して、最適なSQL文を吐き出す動的パーサーを一緒に作りましょう。

一見難しそうに見えますが、仕組みが分かればとてもシンプルです。「ここをクリアすれば、VBScriptの基本とデータ処理の本質はバッチリですよ!」と言えるレベルまで、優しく丁寧に解説していきますね。

1. 型推論エンジンの全体像(概念図)

まずは、今回作成するスクリプトがどのように動くのか、全体の処理フローを図解でイメージしてみましょう。

[ 巨大なCSVファイル ]


┌─────────────────────────┐
│ 先頭1,000行だけをサンプリング │ ◄─ メモリ消費を最小限に抑える!
└──────────┬──────────────┘


┌─────────────────────────┐
│ 1セルごとに型判定を実施 │
│ (IsNumeric / IsDate) │
└──────────┬──────────────┘


┌─────────────────────────┐
│ 「型の昇格ピラミッド」判定 │ ◄─ 1つでも文字列が混ざればStringへ昇格
└──────────┬──────────────┘


┌─────────────────────────┐
│ SQL DDL(CREATE TABLE) │
│ 文を動的に自動生成! │
└─────────────────────────┘

全件を読み込むのではなく、「先頭の一定行数(サンプル)」だけを高速走査するのがプロの現場の鉄則です。これにより、数ギガバイトあるような超巨大CSVであっても、一瞬で型推論を完了させることができます。

2. VBScriptにおける「型の昇格(Promotion)」ルール

型推論の心臓部となるのが「型の昇格ルール」です。

例えば、ある列の1行目が `100`(整数)だったとします。しかし、500行目に `123.45`(小数)が現れたら、その列は「整数型」から「小数型」に昇格しなければなりません。さらに900行目に `”エラー”` という文字が入っていたら、最終的にその列は「文字列型」として定義する必要があります。

この判定優先度(ピラミッド)を以下のように定義します。

1. UNKNOWN(未定):まだデータを見ていない状態
2. INTEGER(整数型):`IsNumeric` が True かつ 小数点がない
3. DOUBLE(浮動小数点型):`IsNumeric` が True かつ 小数点がある
4. DATE(日付型):`IsDate` が True
5. STRING(文字列型):上記以外、または矛盾が発生した場合の「最終退避先」

3. 完全実装:CSV型推論エンジン (VBScript)

それでは、実際に動作するコードを見ていきましょう!
テキストエディタ(メモ帳など)に貼り付け、拡張子を `.vbs` にして保存すれば、Windows環境でそのまま実行できます。

‘ ==============================================================================
‘ 【CSV dynamic Schema Parser & DDL Generator】
‘ 機能: CSVの先頭データを走査し、データ型を自動判定してCREATE TABLE文を生成する
‘ ==============================================================================
Option Explicit

‘ ——————————————————————————
‘ 定数定義(型の優先度)
‘ ——————————————————————————
Const TYPE_UNKNOWN = 0
Const TYPE_INTEGER = 1
Const TYPE_DOUBLE = 2
Const TYPE_DATE = 3
Const TYPE_STRING = 4

‘ 読み込み設定
Const SAMPLE_ROW_LIMIT = 1000 ‘ 走査するサンプル行数
Const CSV_FILE_PATH = “C:\data\sample.csv” ‘ 解析対象のCSVファイル名

‘ ——————————————————————————
‘ メイン処理を実行
‘ ——————————————————————————
Call Main()

Sub Main()
Dim fso, ts
Dim line, fields
Dim colProfiles()
Dim rowCount, colCount, i
Dim ddlSql

Set fso = CreateObject(“Scripting.FileSystemObject”)

‘ ファイルの存在確認
If Not fso.FileExists(CSV_FILE_PATH) Then
WScript.Echo “[ERROR] 指定されたCSVファイルが存在しません: ” & CSV_FILE_PATH
Exit Sub
End If

‘ ファイルを開く (1: ForReading)
Set ts = fso.OpenTextFile(CSV_FILE_PATH, 1, False)

rowCount = 0
colCount = 0

WScript.Echo “>>> スキーマ解析を開始します… (最大 ” & SAMPLE_ROW_LIMIT & ” 行)”

‘ 先頭から指定行数だけ走査(ストリーム読み込み)
Do While Not ts.AtEndOfStream And rowCount < SAMPLE_ROW_LIMIT line = ts.ReadLine ' 空行のスキップ If Trim(line) <> “” Then
‘ カンマ区切りで配列化(簡易パーサー)
fields = Split(line, “,”)

‘ 初回行の段階で列プロファイル配列を動的確保
If rowCount = 0 Then
colCount = UBound(fields)
ReDim colProfiles(colCount)
For i = 0 To colCount
Set colProfiles(i) = New ColumnInfo
colProfiles(i).ColumnName = “Col_” & (i + 1)
Next
End If

‘ 列ごとの型判定を実行
For i = 0 To colCount
If i <= UBound(fields) Then colProfiles(i).EvaluateType Trim(fields(i)) End If Next rowCount = rowCount + 1 End If Loop ' オブジェクトの後始末(明示的な解放) ts.Close Set ts = Nothing Set fso = Nothing ' -------------------------------------------------------------------------- ' 結果の出力(CREATE TABLE 文の生成) ' -------------------------------------------------------------------------- ddlSql = "CREATE TABLE [ImportedTable] (" & vbCrLf For i = 0 To colCount ddlSql = ddlSql & " [" & colProfiles(i).ColumnName & "] " & colProfiles(i).GetSqlType() If i < colCount Then ddlSql = ddlSql & "," End If ddlSql = ddlSql & " -- サンプル最大長: " & colProfiles(i).MaxLength & " 文字" & vbCrLf Next ddlSql = ddlSql & ");" WScript.Echo vbCrLf & "=== 解析完了: CREATE TABLE SQL ===" & vbCrLf WScript.Echo ddlSql End Sub ' ============================================================================== ' クラス定義: 各列の型推論状態をカプセル化するクラス ' ============================================================================== Class ColumnInfo Public ColumnName Public CurrentType Public MaxLength ' 初期化処理 Private Sub Class_Initialize() CurrentType = TYPE_UNKNOWN MaxLength = 0 End Sub ' データを判定して型を「昇格」させるメソッド Public Sub EvaluateType(ByVal rawVal) Dim valLen, detectedType ' ダブルクォーテーションの除去 rawVal = Replace(rawVal, """", "") valLen = Len(rawVal) ' 最大文字数の更新 If valLen > MaxLength Then
MaxLength = valLen
End If

‘ 空文字の場合は現在の型を維持
If rawVal = “” Then Exit Sub

‘ 1. 単一セルの型を判定
detectedType = DetectSingleType(rawVal)

‘ 2. 状態遷移(型の昇格ルール)の適用
Select Case CurrentType
Case TYPE_UNKNOWN
CurrentType = detectedType

Case TYPE_INTEGER
If detectedType = TYPE_DOUBLE Then
CurrentType = TYPE_DOUBLE
ElseIf detectedType <> TYPE_INTEGER Then
CurrentType = TYPE_STRING
End If

Case TYPE_DOUBLE
If detectedType <> TYPE_INTEGER And detectedType <> TYPE_DOUBLE Then
CurrentType = TYPE_STRING
End If

Case TYPE_DATE
If detectedType <> TYPE_DATE Then
CurrentType = TYPE_STRING
End If

Case TYPE_STRING
‘ 一度 STRING になったら以降は昇格(不変)
CurrentType = TYPE_STRING
End Select
End Sub

‘ 単一文字列の判定ロジック
Private Function DetectSingleType(ByVal val)
‘ 先頭の「0埋め数値(例: 00123)」は郵便番号やコード値のため文字列とみなす
If Len(val) > 1 And Left(val, 1) = “0” And IsNumeric(val) And InStr(val, “.”) = 0 Then
DetectSingleType = TYPE_STRING
Exit Function
End If

‘ 数値判定
If IsNumeric(val) Then
If InStr(val, “.”) > 0 Then
DetectSingleType = TYPE_DOUBLE
Else
DetectSingleType = TYPE_INTEGER
End If
Exit Function
End If

‘ 日付判定
If IsDate(val) Then
DetectSingleType = TYPE_DATE
Exit Function
End If

‘ どれにも当てはまらない場合は文字列
DetectSingleType = TYPE_STRING
End Function

‘ SQL用のデータ型文字列に変換
Public Function GetSqlType()
Select Case CurrentType
Case TYPE_INTEGER
GetSqlType = “INTEGER”
Case TYPE_DOUBLE
GetSqlType = “FLOAT”
Case TYPE_DATE
GetSqlType = “DATETIME”
Case Else
‘ 安全のため最大文字数の2倍(最低16文字)を割り当て
Dim varcharLen
varcharLen = MaxLength 2
If varcharLen < 16 Then varcharLen = 16 If varcharLen > 255 Then varcharLen = 255
GetSqlType = “VARCHAR(” & varcharLen & “)”
End Select
End Function
End Class

4. コードの解説と陥りやすい「罠」

このスクリプトには、VBScriptで安全かつ超高速にデータ処理を行うための技術が詰まっています。初心者の方が特にハマりやすいポイントを紐解いてみましょう!

1. 「0埋めデータ」の罠 (`00123` 問題)

VBScriptの `IsNumeric(“00123”)` は `True` を返します。
しかし、これを数値型(Integer)として扱うと、データベースに入れた時に `123` に変換され、先頭の「00」が消滅してしまいます!(商品コードや郵便番号などで大問題になります)。

コード内の以下のロジックでこれを防いでいます:

‘ 先頭が “0” で始まり、2文字以上ある数値は「文字列」として扱う
If Len(val) > 1 And Left(val, 1) = “0” And IsNumeric(val) And InStr(val, “.”) = 0 Then
DetectSingleType = TYPE_STRING

2. メモリリークを防ぐオブジェクトのライフサイクル

VBScript(WSH環境)では、`CreateObject` で生成したCOMオブジェクト(`FileSystemObject` やクラスインスタンス)のメモリ解放を怠ると、大量データ処理時にスクリプトが重くなります。

使い終わったオブジェクトは、必ず明示的に解放しましょう。

Set ts = Nothing
Set fso = Nothing

この「後始末の美学」を身につけると、一気にプロのコードらしくなりますよ!

3. クラス(`Class`)の活用

VBScriptで `Class`構文 を使ったことがありますか?
`ColumnInfo` というクラスを作って「列の状態(現在の型、最大文字数)」を閉じ込める(カプセル化する)ことで、メインの処理側が驚くほどスッキリ読みやすくなっています。

5. 実行方法と出力結果の例

例えば、以下のような `sample.csv` を用意してスクリプトを実行してみます。

【入力データ: sample.csv】

00981,100,2023/10/01,10.5,商品A
00982,200,2023-10-02,20.0,商品B
00983,3000000000,2023/10/03,30.25,非常に長い商品名のサンプルデータ

【実行コマンド】
コマンドプロンプトを開き、以下のコマンドを実行します。

cscript //NoLogo parse_schema.vbs

【出力結果】

>>> スキーマ解析を開始します… (最大 1000 行)

=== 解析完了: CREATE TABLE SQL ===

CREATE TABLE [ImportedTable] (
[Col_1] VARCHAR(16), — サンプル最大長: 5 文字
[Col_2] INTEGER, — サンプル最大長: 10 文字
[Col_3] DATETIME, — サンプル最大長: 10 文字
[Col_4] FLOAT, — サンプル最大長: 5 文字
[Col_5] VARCHAR(32) — サンプル最大長: 16 文字
);

見事に `Col_1`(先頭0埋めコード)は `VARCHAR`、`Col_2` は `INTEGER`、`Col_3` は `DATETIME` として自動認識されました!

6. まとめ:ここをクリアすればVBScriptの基礎はバッチリ!

今回は、VBScriptを使った動的スキーマ解析(CSV型推論エンジン)の構築にチャレンジしました。

この記事で学んだ重要ポイントを振り返ってみましょう:

  • ストリーム読み込み: 全件ではなく先頭サンプルだけを読み込んで高速処理
  • 型の昇格ロジック: 判定の優先度(Integer → Double → String)を設計する
  • エッジケースの考慮: 先頭の `0` 埋めデータなどを文字列として逃がす工夫
  • オブジェクトのライフサイクル: `Set obj = Nothing` による適切なメモリ解放

これらはVBScriptに限らず、PythonやC#、VBAなどあらゆるプログラミング言語に応用できる「データ処理の本質」です。

マクロの記録から一歩踏み出し、こうした構造化されたロジックが書けるようになれば、あなたのVBScript技術はもう初学者レベルを完全に脱しています。自信を持って、日々の業務自動化に役立ててくださいね!

何か分からない点があれば、いつでも質問してください。応援しています!

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