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

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

【極限の知見】Dictionaryを超えろ。ADODB.Recordsetを「インメモリ・エンジン」として掌握する技術

業務自動化の現場において、多くのエンジニアが「データの保持」という壁にぶち当たる。
その際、真っ先に手に取る武器が `Scripting.Dictionary` だ。

確かに、Dictionaryは高速で、キーと値のペアを扱うには極めて優秀な道具だ。しかし、君が作ろうとしているツールが「単なるデータの詰め替え」を超え、「複雑な集計」や「多角的な分析」を必要とした瞬間、Dictionaryは急激にその牙を抜かれ、使いにくい「ただの配列の集合体」へと成り下がる。

「特定の条件でデータを絞り込みたい(Filter)」
「日付順、あるいは金額順に並べ替えたい(Sort)」
「複数の項目を組み合わせた複雑な集計を行いたい」

これらをDictionaryで行おうとすれば、君は泥臭い `For…Next` ループと、管理の行き届かない一時的な配列の迷宮に足を踏み入れることになるだろう。それは設計ではなく、単なる「力技」だ。

本稿では、プロフェッショナルが実践する、ADODB.Recordsetを「インメモリ・データベース」として活用する高度な設計手法を伝授する。Dictionaryの限界を突破し、堅牢かつスケーラブルな自動化ツールを構築するための思考プロセスを、その魂に刻み込んでほしい。

1. なぜ Dictionary では「不十分」なのか

Dictionaryは、ハッシュテーブルに基づく「1対1」の対応関係を管理するのには最適だ。しかし、ビジネスロジックにおけるデータは、もっと多次元的で、流動的だ。

Dictionaryの限界点

1. ソートの欠如: Dictionary自体には順序を制御する機能がない。ソートが必要になれば、一度全てのキーを配列に取り出し、独自のソートアルゴリズムを実装するか、外部ライブラリを頼る必要がある。
2. フィルタリングの非効率: 特定の条件(例:売上 > 100,000)でデータを抽出する場合、全要素を走査するループが不可避となる。これはデータ量が増えた際、計算量 $O(n)$ の重みとして顕著に現れる。
3. データ型の脆弱性: Dictionaryは「何でも入る」がゆえに、数値として計算したい値が文字列として混入するリスクを常に孕んでいる。

これに対し、ADODB.Recordset は、たとえ背後にSQL ServerやAccessが存在していなくても、メモリ上だけで動作する「強力なリレーショナル・エンジン」として振る舞うことができる。

2. アーキテクチャの核心:Disconnected Recordset

我々が目指すのは、データベースに接続しない 「Disconnected Recordset(切断型レコードセット)」 の活用だ。

メモリ上に仮想的なテーブル構造(スキーマ)を定義し、そこにデータを流し込む。すると、あたかもSQLを操るかのような感覚で、`Filter` プロパティによる高速な絞り込みや、`Sort` プロパティによる瞬時の並べ替えが可能になる。

設計の黄金律

  • Schema-First: データを投入する前に、必ずフィールド名とデータ型を厳密に定義せよ。
  • Late Bindingの徹底: 配布用ツールにおいては、参照設定に依存しない `CreateObject` によるレイトバインディングを採用し、実行環境の差異を吸収せよ。
  • Lifecycle Management: Recordsetオブジェクトは、使い終わったら必ず `Close` し、`Nothing` を代入してメモリから解放せよ。これを怠ることは、メモリリークという名の時限爆弾を仕掛けることと同義だ。

3. 実践:プロダクション・グレードのコード実装

以下に、Dictionaryでは困難な「データの型定義」「ソート」「フィルタリング」を完璧にこなす、実戦的なクラス設計の雛形を示す。

' ==============================================================================
' Module: InMemoryEngine
' Description: ADODB.Recordsetを利用したインメモリ・データ処理エンジン
' ==============================================================================

Option Explicit

' 定数の定義 (Late Binding用)
' 本来は参照設定で取得できるが、配布の堅牢性を考慮し、直接定義する
Const adInteger = 3
Const adVarChar = 200
Const adDouble = 5
Const adDate = 7

''' <summary>
''' メモリ上に仮想テーブルを作成し、データを操作するデモンストレーション
''' </summary>
Sub ExecuteAdvancedDataProcessing()
    Dim rs, err
    
    On Error Resume Next ' エラーハンドリングの開始

    ' 1. Recordsetオブジェクトの生成 (Disconnected Mode)
    Set rs = CreateObject("ADODB.Recordset")

    ' 2. スキーマ(構造)の定義
    ' ここで型を厳密に定義することが、バグを防ぐ最大の防御策である
    rs.Fields.Append "ID", adInteger
    rs.Fields.Append "UserName", adVarChar, 50
    rs.Fields.Append "SalesAmount", adDouble
    rs.Fields.Append "TransactionDate", adDate
    rs.Open

    ' 3. データの投入
    ' 本来はファイルやDBから読み込むが、ここではサンプルデータを流し込む
    Call AddData(rs, 1, "Tanaka", 150000, "2023/10/01")
    Call AddData(rs, 2, "Sato", 80000, "2023/10/02")
    Call AddData(rs, 3, "Suzuki", 250000, "2023/10/01")
    Call AddData(rs, 4, "Takahashi", 45000, "2023/10/03")
    Call AddData(rs, 5, "Ito", 300000, "2023/10/02")

    If Err.Number <> 0 Then
        WScript.Echo "Error during initialization: " & Err.Description
        Exit Sub
    End If

    ' --- ここからがRecordsetの真骨頂 ---

    ' 4. 高度なソート (SalesAmountの降順)
    ' Dictionaryでは数行かかる処理が、一行で完結する
    rs.Sort = "SalesAmount DESC"
    WScript.Echo "--- [All Data: Sorted by Sales DESC] ---"
    Call PrintRecordset(rs)

    ' 5. 高度なフィルタリング (売上が100,000以上の者のみ)
    ' SQLのWHERE句に近い直感的な操作が可能
    rs.Filter = "SalesAmount >= 100000"
    WScript.Echo "--- [Filtered Data: Sales >= 100,000] ---"
    Call PrintRecordset(rs)

    ' 6. フィルタ解除
    rs.Filter = ""

    ' 7. クリーンアップ (極めて重要)
    rs.Close
    Set rs = Nothing

    If Err.Number <> 0 Then
        WScript.Echo "An error occurred: " & Err.Description
    Else
        WScript.Echo "Processing completed successfully."
    End If
    
    On Error GoTo 0
End Sub

''' <summary>
''' Recordsetへデータを追加するヘルパー関数
''' </summary>
Sub AddData(rs, id, name, amount, dt)
    rs.AddNew
    rs.Fields("ID").Value = id
    rs.Fields("UserName").Value = name
    rs.Fields("SalesAmount").Value = amount
    rs.Fields("TransactionDate").Value = dt
    rs.Update
End Sub

''' <summary>
''' Recordsetの内容をコンソールに出力するデバッグ用関数
''' </summary>
Sub PrintRecordset(rs)
    If rs.EOF Then
        WScript.Echo "No records found."
        Exit Sub
    End If

    Dim currentPos
    currentPos = rs.AbsolutePosition
    rs.MoveFirst

    Do Until rs.EOF
        WScript.Echo String(5, "-")
        WScript.Echo "ID: " & rs.Fields("ID").Value
        WScript.Echo "Name: " & rs.Fields("UserName").Value
        WScript.Echo "Sales: " & FormatNumber(rs.Fields("SalesAmount").Value, 0)
        WScript.Echo "Date: " & rs.Fields("TransactionDate").Value
        rs.MoveNext
    Loop

    ' カーソル位置を元に戻す(保守性のための作法)
    rs.AbsolutePosition = currentPos
End Sub

' 実行
ExecuteAdvancedDataProcessing

4. プロフェッショナルのための注意点

この手法を導入する際、君が守るべき「鉄則」が3つある。

① データ型の不一致を許容するな

Recordsetの `Fields.Append` で定義した型と、投入するデータの型が異なると、実行時に致命的なエラーが発生するか、意図しない型変換(暗黙の型変換)による計算ミスを招く。特に日付型 (`adDate`) や数値型 (`adDouble`) は厳格に扱え。

② カーソル位置の管理

`PrintRecordset` のような関数で Recordset を走査する場合、関数を抜けた後に呼び出し元が「どこにいるか」を意識しなければならない。サンプルコードに示した `AbsolutePosition` による位置保持は、大規模なシステムにおいて副作用を防ぐための必須テクニックだ。

③ エラーハンドリングの設計

`On Error Resume Next` は、単なる「エラー隠し」として使ってはならない。エラーが発生した際、その原因(`Err.Number`, `Err.Description`)を特定し、適切にリソースを解放(`Close`)した上で、安全にプロセスを終了させる設計にせよ。

結論:道具を使い分けるのが、真のエンジニアだ

`Scripting.Dictionary` は、単純な「名簿」を作るには最適だ。
しかし、`ADODB.Recordset` は、データの「分析」を行うための「エンジン」である。

君が直面している問題が、単なるデータの保持なのか、それともデータの加工・集計なのか。その境界線を明確に引き、適切なオブジェクトを選択すること。それが、バグを減らし、保守性の高い、プロフェッショナルな自動化ツールを生み出す唯一の道である。

さあ、Dictionaryのループ地獄から脱出し、リレーショナルな思考を手に入れろ。

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