元の質問は、「クエリをパラメータ化するにはどうすればよいですか...」でした。
ここで、これは元の質問に対する回答ではないことを述べさせてください。他の良い答えには、すでにいくつかのデモンストレーションがあります。
そうは言っても、先に進んでこの回答にフラグを立て、反対票を投じ、回答ではないとしてマークしてください...あなたが正しいと信じることは何でもしてください。
私 (および他の 231 人) が支持した好ましい回答については、Mark Brackett からの回答を参照してください。彼の回答で与えられたアプローチにより、1) バインド変数の効果的な使用、および 2) サージ可能な述語が可能になります。
選択した回答
ここで取り上げたいのは、Joel Spolsky の回答で与えられたアプローチであり、正解として「選択された」回答です。
Joel Spolsky のアプローチは巧妙です。そして、それは合理的に機能し、「通常の」値が与えられ、NULLや空の文字列などの規範的なエッジケースで、予測可能な動作と予測可能なパフォーマンスを示します. また、特定のアプリケーションには十分な場合があります。
Name
しかし、このアプローチを一般化するという点では、列にワイルドカード文字 (LIKE 述語によって認識される) が含まれている場合など、よりあいまいなコーナー ケースも考えてみましょう。最も一般的に使用されているワイルドカード文字は%
(パーセント記号) です。それでは、ここでそれを扱い、後で他のケースに進みましょう。
% 文字に関するいくつかの問題
の名前の値を検討してください'pe%ter'
。(ここの例では、列名の代わりにリテラル文字列値を使用しています。) Name 値が「pe%ter」の行は、次の形式のクエリによって返されます。
select ...
where '|peanut|butter|' like '%|' + 'pe%ter' + '|%'
ただし、検索語の順序が逆の場合、同じ行は返されません。
select ...
where '|butter|peanut|' like '%|' + 'pe%ter' + '|%'
私たちが観察する行動は、ちょっと変わっています。リスト内の検索語の順序を変更すると、結果セットが変更されます。
pe%ter
言うまでもなく、ピーナッツバターがどんなに好きであっても、私たちはピーナッツバターを合わせたくないかもしれません.
あいまいなコーナーケース
(はい、これはあいまいなケースであることに同意します。おそらくテストされる可能性が低いケースです。列の値にワイルドカードが含まれているとは思わないでしょう。アプリケーションがそのような値の保存を妨げていると想定するかもしれません。しかし私の経験では、LIKE
比較演算子の右側でワイルドカードと見なされる文字やパターンを明確に禁止するデータベース制約はほとんど見たことがありません。
穴のパッチ
この穴にパッチを当てる 1 つの方法は、%
ワイルドカード文字をエスケープすることです。(演算子のエスケープ句に慣れていない人のために、SQL Server のドキュメントへのリンクを次に示します。
select ...
where '|peanut|butter|'
like '%|' + 'pe\%ter' + '|%' escape '\'
これで、リテラル % に一致させることができます。もちろん、列名がある場合は、ワイルドカードを動的にエスケープする必要があります。この関数を使用して文字REPLACE
の出現箇所を見つけ%
、次のようにそれぞれの前にバックスラッシュ文字を挿入できます。
select ...
where '|pe%ter|'
like '%|' + REPLACE( 'pe%ter' ,'%','\%') + '|%' escape '\'
これにより、% ワイルドカードの問題が解決されます。ほとんど。
逃げ場を逃れ
私たちのソリューションが別の問題を引き起こしたことを認識しています。エスケープ文字。エスケープ文字自体もエスケープする必要があることがわかります。今回は ! を使用します。エスケープ文字として:
select ...
where '|pe%t!r|'
like '%|' + REPLACE(REPLACE( 'pe%t!r' ,'!','!!'),'%','!%') + '|%' escape '!'
アンダースコアも
REPLACE
順調に進んだので、アンダースコア ワイルドカードの別のハンドルを追加できます。念のため、今回はエスケープ文字として $ を使用します。
select ...
where '|p_%t!r|'
like '%|' + REPLACE(REPLACE(REPLACE( 'p_%t!r' ,'$','$$'),'%','$%'),'_','$_') + '|%' escape '$'
このアプローチは、SQL Server だけでなく Oracle や MySQL でも機能するため、エスケープするよりも好きです。(\ バックスラッシュは正規表現で使用する文字なので、通常はエスケープ文字として使用します。
それらの厄介なブラケット
SQL Server では、ワイルドカード文字を角かっこで囲むことにより、リテラルとして扱うこともできます[]
。したがって、少なくとも SQL Server については、まだ修正が完了していません。括弧のペアには特別な意味があるため、それらもエスケープする必要があります。括弧を適切にエスケープできれば、少なくとも括弧内のハイフン-
とカラットを気にする必要はありません^
。また、大括弧の特別な意味を基本的に無効にするため、大括弧内のすべての文字%
と文字をエスケープしたままにすることができます。_
一致するブラケットのペアを見つけることは、それほど難しくありません。シングルトン % と _ の発生を処理するよりも少し難しいです。(シングルトンブラケットはリテラルと見なされ、エスケープする必要がないため、すべてのブラケットをエスケープするだけでは十分ではないことに注意してください。ロジックは、テストケースをさらに実行しないと処理できないほどあいまいになっています。 .)
インライン式が乱雑になる
SQL のインライン式は長くなり、見苦しくなります。私たちはおそらくそれを機能させることができますが、天国は後になってそれを解読しなければならない貧しい魂を助けます. 私はインライン式のファンなので、ここではインライン式を使用しない傾向があります。主な理由は、混乱の理由を説明し、謝罪するコメントを残す必要がないためです。
関数どこ?
これを SQL のインライン式として処理しない場合、最も近い代替手段はユーザー定義関数です。そして、それでは速度が向上しないことはわかっています (Oracle の場合のようにインデックスを定義できない限り)。関数を作成する必要がある場合は、SQL を呼び出すコードでそれを行う方がよいでしょう。声明。
また、その関数は、DBMS とバージョンによって動作が異なる場合があります。(あらゆるデータベースエンジンを交換可能に使用できることに熱心なすべての Java 開発者に感謝します。)
領域知識
列のドメイン (つまり、列に適用される許容値のセット) に関する専門的な知識を持っている場合があります。列に格納されている値にはパーセント記号、アンダースコア、またはブラケットが含まれないことをアプリオリに知っている場合があります。その場合、それらのケースがカバーされているという簡単なコメントを含めます。
列に格納された値は % または _ 文字を許可する場合がありますが、制約により、値が LIKE 比較「安全」になるように、おそらく定義された文字を使用して、これらの値をエスケープする必要がある場合があります。繰り返しになりますが、許可されている値のセット、特にどの文字がエスケープ文字として使用されているかについて簡単にコメントし、Joel Spolsky のアプローチに従います。
しかし、専門的な知識と保証がなければ、少なくともそれらのあいまいなコーナーケースの処理を検討し、動作が合理的で「仕様どおり」であるかどうかを検討することが重要です.
その他の問題の要約
私は、他の人が、他の一般的に考慮されている懸念事項のいくつかをすでに十分に指摘していると信じています。
SQL インジェクション(ユーザーが提供したと思われる情報を取得し、それをバインド変数を介して提供するのではなく、SQL テキストに含めます。バインド変数を使用する必要はありません。SQL インジェクションを阻止するための便利なアプローチの 1 つに過ぎません。他の方法もあります。それに対処する方法:
インデックス シークではなくインデックス スキャンを使用するオプティマイザ プラン、ワイルドカードをエスケープするための式または関数が必要になる可能性 (式または関数のインデックスの可能性)
バインド変数の代わりにリテラル値を使用すると、スケーラビリティに影響します
結論
私は Joel Spolsky のアプローチが好きです。賢いです。そして、それは機能します。
しかし、それを見た途端、すぐに潜在的な問題に気づきました。それを滑らせるのは私の性質ではありません。他人の努力を批判するつもりはありません。多くの開発者が自分の仕事を非常に個人的に受け止めていることを私は知っています。個人攻撃ではないのでご了承ください。ここで特定しているのは、テストではなく本番環境で発生する問題のタイプです。
はい、私は元の質問から遠く離れています。しかし、質問に対する「選択された」回答に関する重要な問題であると私が考えるものに関して、このメモを他にどこに残すのでしょうか?