【極限の知見】Scripting.Dictionaryの限界を超えろ:ADODB.Recordsetによる「インメモリ・データベース」構築術
エンジニアが「VBScriptにおけるデータ集計といえば `Scripting.Dictionary` だ」と口にする時、私はその者の設計思想の浅さに、静かな危惧を覚える。
`Scripting.Dictionary` は確かに強力だ。キー・バリュー型の単純なマッピング、高速な検索。しかし、実務における複雑なデータ構造――例えば「複数条件でのソート」「動的なフィルタリング」「リレーショナルな集計」を要求された瞬間、Dictionaryはただの「重たい連想配列の塊」へと成り下がる。ネストされたDictionaryを構築し、自前でソートアルゴリズムを実装し、ループを回してフィルタリングを行う……。そんなコードを書いている限り、君は「スクリプトを書く作業員」であって、「システムを設計するアーキテクト」ではない。
今日、私が伝授するのは、`ADODB.Recordset` を単なる「データベース接続の器」としてではなく、「超高速なインメモリ・データエンジン」として扱う極限の手法だ。
1. なぜ Dictionary では「不十分」なのか
シニアエンジニアであれば、計算量(Big O記法)の観点から、Dictionaryの限界は理解しているはずだ。
- ソートの不在: Dictionaryには順序という概念がない。ソートが必要になれば、一度すべてのキーを配列に書き出し、その配列をソートし、再度Dictionaryを走査するという、極めて非効率なプロセスが発生する。
- フィルタリングのコスト: 特定の条件(例:`Age > 30 AND Dept = ‘Sales’`)でデータを抽出する場合、Dictionaryでは全要素を走査する $O(n)$ のコストが避けられない。
- スキーマの欠如: Dictionaryは「型」を持たない。データの整合性を保つためには、アプリケーション層で膨大なバリデーションロジックを記述しなければならない。
これに対し、`ADODB.Recordset` は、メモリ上に「スキーマ(構造)」を定義し、内部的な検索エンジンを備えた、完全なる「インメモリ・データベース」として機能する。
2. ADODB.Recordset によるインメモリ・エンジン構築
外部のデータベース(SQL ServerやAccess)に接続する必要はない。`CursorType` を `adUseClient` に設定し、メモリ内でフィールドを定義するだけで、君の手元には強力なデータエンジンが降臨する。
以下に、その実装の真髄を示す。
' ==============================================================================
' Module: InMemoryDataEngine
' Purpose: ADODB.Recordset を用いた高度なインメモリ・データ処理の実装
' Author: Legendary Chief Architect
' ==============================================================================
Option Explicit
' ADODB 定数の定義 (参照設定なしの WSH 環境を想定)
Const adOpenStatic = 3
Const adLockOptimistic = 3
Const adUseClient = 3
Sub ExecuteAdvancedDataProcessing()
Dim rs, i
' 1. Recordset オブジェクトの生成
' 外部DBに接続せず、メモリ上のみで動作させる
Set rs = CreateObject("ADODB.Recordset")
' 2. スキーマの定義 (Fieldsの構築)
' これにより、データの型と構造を強制し、メモリ効率と整合性を確保する
rs.Fields.Append CreateObject("ADODB.Field"), 200, "EmployeeName" ' adVarChar
rs.Fields.Append CreateObject("ADODB.Field"), 20, "Department" ' adVarChar
rs.Fields.Append CreateObject("ADODB.Field"), 3, "Salary" ' adInteger
rs.Fields.Append CreateObject("ADODB.Field"), 200, "Status" ' adVarChar
' レコードセットを開く (Client-side cursor を指定)
rs.Open
' 3. データ・インジェクション (模擬データ投入)
' 実務では CSV や XML、API からのレスポンスをここに流し込む
Dim rawData
rawData = Array( _
Array("Tanaka", "Sales", 500000, "Active"), _
Array("Sato", "IT", 600000, "Active"), _
Array("Suzuki", "Sales", 450000, "Inactive"), _
Array("Takahashi", "IT", 700000, "Active"), _
Array("Ito", "HR", 400000, "Active") _
)
For i = 0 To UBound(rawData)
rs.AddNew
rs.Fields("EmployeeName").Value = rawData(i)(0)
rs.Fields("Department").Value = rawData(i)(1)
rs.Fields("Salary").Value = rawData(i)(2)
rs.Fields("Status").Value = rawData(i)(3)
rs.Update
Next
' --- ここからが Recordset の真骨頂 ---
' 4. 高度なソート (Multi-key Sort)
' Dictionary では不可能だった「部署別、かつ給与の高い順」のソートを一行で実現
WScript.Echo "--- [Sort: Department ASC, Salary DESC] ---"
rs.Sort = "Department ASC, Salary DESC"
PrintRecordset rs
' 5. 高度なフィルタリング (Predicate Filtering)
' 複雑な条件によるサブセットの抽出
WScript.Echo vbCrLf & "--- [Filter: Department = 'IT' AND Status = 'Active'] ---"
rs.Filter = "Department = 'IT' AND Status = 'Active'"
PrintRecordset rs
' 6. フィルタの解除とクリーンアップ
rs.Filter = ""
' 7. オブジェクトの明示的な解放 (メモリリーク防止の鉄則)
' WSH/VBAにおいて、COMオブジェクトのライフサイクル管理はエンジニアの義務である
rs.Close
Set rs = Nothing
WScript.Echo vbCrLf & "Processing Completed Successfully."
End Sub
' Recordset の内容を標準出力へ
Sub PrintRecordset(rs)
If rs.EOF Then
WScript.Echo "No records found."
Exit Sub
End If
rs.MoveFirst
Do Until rs.EOF
WScript.Echo String(15, "-")
WScript.Echo "Name: " & rs.Fields("EmployeeName").Value
WScript.Echo "Dept: " & rs.Fields("Department").Value
WScript.Echo "Salary: " & rs.Fields("Salary").Value
WScript.Echo "Status: " & rs.Fields("Status").Value
rs.MoveNext
Loop
End Sub
' 実行
ExecuteAdvancedDataProcessing
3. アーキテクトの視点:メモリ最適化とライフサイクル管理
この手法を実戦投入する際、君が肝に銘じるべきは「オブジェクトのライフサイクル」だ。
明示的な解放の重要性
VBScript (WSH) は、COM (Component Object Model) のランタイム上で動作している。`Scripting.Dictionary` は比較的軽量なオブジェクトだが、`ADODB.Recordset` は背後に高度なデータエンジン(OLE DB プロバイダー)を抱えた重量級のオブジェクトだ。
ループの中で Recordset を生成し、`Set rs = Nothing` を怠れば、メモリリークが発生し、長期間稼働する Windows サービスやスケジュールタスクにおいて、システム全体の不安定化を招く。「使い終わったら即座に、明示的に、破棄する」。これはレガシー環境における生存戦略である。
パフォーマンスのトレードオフ
もちろん、単純な 1対1 のマッピングであれば `Dictionary` の方がオーバーヘッドは少ない。しかし、以下の条件に当てはまる場合は、迷わず `Recordset` を選択せよ。
1. データ件数が数百〜数万件規模であり、かつ複雑な抽出ロジックが必要な場合。
2. データの整合性(型定義)を重視する場合。
3. ソート順序がビジネスロジックに直結している場合。
結論
`Scripting.Dictionary` は「鍵」であり、`ADODB.Recordset` は「金庫」である。
単に値を放り込んで取り出すだけなら鍵で十分だが、情報を整理し、守り、高度に活用するためには、金庫という構造体が必要だ。
ツールを単なる「機能」として見るのではなく、その背後にある「メモリ構造」と「計算量」を見極めろ。それこそが、レガシーな環境においても揺るぎない価値を提供する、真のエンジニアへの道だ。
