【入門編】SQLインジェクションを100%防ぐための「パラメータクエリ」強制適用コーディング規約 – Access VBA解析バイブル

スポンサーリンク

こんにちは!Access VBAでの開発、日々の業務お疲れ様です。
「マクロの記録から脱却して、本格的なシステムを作りたい!」そう思って一歩を踏み出したあなたへ、今日は非常に重要で、かつ避けて通れない「セキュリティとパフォーマンスの極意」をお伝えします。

世の中には「SQLインジェクションなんてAccessのローカル環境だから関係ないよ」なんて甘いことを言うエンジニアもいますが、とんでもない。プロとして誇りを持ってコードを書くなら、規模の大小に関わらず「100%安全なコード」を書くべきです。

今回は、文字列連結による危険なSQL生成を完全に封印し、DAOの真骨頂である「パラメータクエリ」を安全かつスマートに使いこなすためのコーディング規約と、その実装例を伝授します。ここをクリアすれば、あなたのVBAスキルは間違いなく一皮むけますよ。一緒にマスターしていきましょう!

—

なぜ「文字列連結」のSQLは悪なのか?

初学者がやりがちなのが、フォームのテキストボックスの値をそのままSQL文に組み込んでしまう方法です。

‘ 【絶対にしてはいけない危険なコード例】
Dim strSQL As String
strSQL = “SELECT FROM T_顧客 WHERE 顧客名 = ‘” & Me.txtSearch.Value & “‘”

一見、動いて便利に見えますよね。しかし、これには以下の致命的な欠点があります。

1. SQLインジェクションの脆弱性
もしユーザーが入力欄に `’ OR ‘1’=’1` なんて入力したら……想像しただけでも恐ろしいですね。意図しないデータが全件流出、あるいは書き換えられるリスクがあります。
2. シングルクォーテーション(’)のエラー地獄
検索ワードに「O’Brien」のようなアポストロフィが含まれていると、SQLの構文エラーで容赦なくシステムがクラッシュします。
3. Jet/ACEエンジンのキャッシュ効率低下
SQL文の文字列が変わるたびに、データベースエンジンは「新しいSQLだ!」と判断して実行プランを再コンパイルします。これがパフォーマンス低下の隠れた原因になります。

救世主:「パラメータクエリ」という概念

これらを一網打尽に解決するのがパラメータクエリです。
SQLの構造(骨組み)と、動的に変わる「値(パラメータ)」を完全に分離し、データベースエンジンに「値は後から安全に渡すから、型だけ保証してね」と指示する仕組みです。

—

【実践】100%安全を担保する専用クラスモジュールの設計

Access VBAでこれをエレガントに実現するために、今回は「パラメータ付きクエリ実行用クラスモジュール」を用意しました。

このクラスを使うことで、開発者は面倒な型の判定やエラーハンドリングを意識せず、安全にクエリを実行できるようになります。

クラスモジュール名:`clsSafeQuery`

以下のコードを、新規クラスモジュール(`clsSafeQuery`)にそのまま貼り付けてください。

‘ ==============================================================================
‘ クラス名: clsSafeQuery
‘ 概要: SQLインジェクションを完全に防止し、型安全なクエリ実行を提供するクラス
‘ ==============================================================================
Option Compare Database
Option Explicit

Private m_QueryName As String
Private m_Parameters As Object ‘ Scripting.Dictionaryを利用

‘ 初期化
Private Sub Class_Initialize()
‘ パラメータを格納するDictionaryを生成
Set m_Parameters = CreateObject(“Scripting.Dictionary”)
End Sub

‘ 実行対象のクエリ名(またはSQL)を設定
Public Property Let QueryName(ByVal Value As String)
m_QueryName = Value
End Property

‘ パラメータを追加する(型安全を強制)
Public Sub AddParam(ByVal ParamName As String, ByVal Value As Variant)
If Not m_Parameters.Exists(ParamName) Then
m_Parameters.Add ParamName, Value
Else
m_Parameters(ParamName) = Value
End If
End Sub

‘ SELECTクエリを実行し、Recordsetを返す
Public Function OpenRecordset() As DAO.Recordset
Dim qdf As DAO.QueryDef
Dim db As DAO.Database
Dim key As Variant

Set db = CurrentDb

On Error GoTo ErrorHandler

‘ クエリ定義を取得
Set qdf = db.QueryDefs(m_QueryName)

‘ パラメータをバインド
For Each key In m_Parameters.Keys
If qdf.Parameters.Item(key).Type >= 0 Then
qdf.Parameters(key).Value = m_Parameters(key)
End If
Next key

‘ レコードセットを開く
Set OpenRecordset = qdf.OpenRecordset(dbOpenSnapshot)

Exit Function

ErrorHandler:
MsgBox “クエリの実行中にエラーが発生しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“詳細: ” & Err.Description, vbCritical, “安全クエリ実行エラー”
Set OpenRecordset = Nothing
End Function

—

呼び出し側(標準モジュールやフォーム)の書き方

先ほど作成したクラスを、実際の業務画面(フォーム)からどのように呼び出すかを見てみましょう。ここが「マクロからの脱却」を実感する美しい瞬間です。

あらかじめ、Accessのクエリデザイナで以下のようなパラメータ定義を持ったクエリ(名前:`qryGetCustomer`)を作っておいてください。

【事前準備:クエリ `qryGetCustomer` のSQL】

PARAMETERS [p_CustomerName] Text ( 255 );
SELECT FROM T_顧客 WHERE 顧客名 LIKE [p_CustomerName] & “%”;

このクエリに対して、VBAから安全に値を渡します。

‘ ==============================================================================
‘ 呼び出しサンプル
‘ ==============================================================================
Sub SearchCustomerSafely()
Dim safeQry As clsSafeQuery
Dim rs As DAO.Recordset
Dim searchWord As String

‘ ユーザーの入力(仮にテキストボックスから取得したとする)
searchWord = “株式会社” ‘ ここに悪意ある文字や特殊文字が入っても100%安全!

‘ クラスのインスタンス化
Set safeQry = New clsSafeQuery

‘ 実行するクエリ名を指定
safeQry.QueryName = “qryGetCustomer”

‘ パラメータを安全に追加(値の型はDAOが厳格にチェックします)
safeQry.AddParam “p_CustomerName”, searchWord

‘ レコードセットを取得
Set rs = safeQry.OpenRecordset()

‘ 結果の処理
If Not rs.BOF Then
rs.MoveFirst
Do Until rs.EOF
Debug.Print “顧客名: ” & rs!顧客名
rs.MoveNext
Loop
Else
Debug.Print “該当するデータはありませんでした。”
End If

‘ クリーンアップ(メモリリークを防ぐプロの作法)
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If
Set safeQry = Nothing
End Sub

—

陥りやすい罠とエンジニアの知見

ここで、Access VBA特有の「ハマりポイント」をいくつか共有しておきます。実務で必ず直面する壁です。

1. パラメータの名前が一致していないエラー
`qdf.Parameters` コレクションは非常に厳格です。VBA側で指定したパラメータ名(`p_CustomerName`など)が、クエリ側の定義と1文字でも違ったり、そもそもクエリ側に `PARAMETERS` 宣言が抜けていると、容赦なく実行時エラーになります。
2. Variant型の暗黙の型変換に注意
VBAの `Variant` は非常に便利ですが、パラメータとして渡す際に、データベース側のフィールド型(数値型なのに文字列で渡すなど)と不一致を起こすと、Jet/ACEエンジンが暗黙の型変換を試みて予期せぬパフォーマンス低下を招きます。極力、渡す値の型を意識しましょう。
3. メモリの解放(クリーンアップ)の徹底
DAOの `QueryDef` や `Recordset` は、使い終わったら明示的に閉じてメモリ(ハンドル)を解放するべきです。今回のクラス設計では `OpenRecordset` 内で完結させていますが、大規模なループ処理の中ではインスタンスの乱用に注意してください。

—

まとめ:ここをクリアすれば、Access VBAの基本はバッチリ!

いかがでしたでしょうか?
文字列を結合してSQLを作るコードは、一見すると簡単で書くのが楽に見えます。しかし、それは「技術的な借金」をその場で膨らませているに過ぎません。

今回ご紹介した「クラスモジュールによるパラメータクエリの強制適用」を取り入れれば:

  • SQLインジェクションの不安から完全に解放される
  • 特殊文字によるエラー(シングルクォーテーション問題)が起きなくなる
  • コードの保守性が劇的に向上し、プロフェッショナルなアーキテクチャに近づく

という大きなメリットを手に入れることができます。

「動けばいいや」のコードから、「美しく、安全で、堅牢な」コードへ。
あなたのAccess開発ライフが、この知見によって一段上のステージへ引き上げられることを、先輩エンジニアとして心から応援しています!

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