【テクニカル・上級編】【ADODB.Recordset 活用】Dictionary を超える柔軟性を持つ「インメモリ・データベース」による複雑なデータ集計 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク
スポンサーリンク

【極限の知見】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` は「金庫」である。
単に値を放り込んで取り出すだけなら鍵で十分だが、情報を整理し、守り、高度に活用するためには、金庫という構造体が必要だ。

ツールを単なる「機能」として見るのではなく、その背後にある「メモリ構造」と「計算量」を見極めろ。それこそが、レガシーな環境においても揺るぎない価値を提供する、真のエンジニアへの道だ。

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