元の質問は「クエリをパラメータ化するにはどうすればよいですか...」でした。
ここで、これは元の質問に対する回答ではないことを述べておきます。他の良い答えには、すでにいくつかの実証があります。
そうは言っても、先に進んでこの回答にフラグを付け、反対票を投じ、回答ではないことをマークしてください。正しいと思われることは何でもしてください。
私(および他の231人)が賛成した好ましい回答については、Mark Brackettの回答を参照してください。彼の回答で与えられたアプローチは、1)バインド変数の効果的な使用、および2)検索可能な述語を可能にします。
選択した回答
ここで取り上げたいのは、ジョエル・スポルスキーの答えで与えられたアプローチです。正しい答えとして「選択された」答えです。
Joel Spolskyのアプローチは賢いです。そして、それは合理的に機能し、「通常の」値が与えられ、NULLや空の文字列などの規範的なエッジケースで、予測可能な動作と予測可能なパフォーマンスを発揮します。そして、それは特定のアプリケーションには十分かもしれません。
ただし、このアプローチを一般化する観点から、Name
列にワイルドカード文字(LIKE述語で認識される)が含まれている場合など、よりあいまいなコーナーケースについても考えてみましょう%
。それでは、ここでそれを扱い、後で他のケースに進みましょう。
%文字に関するいくつかの問題
Nameの値を考えます'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 '$'
OracleとMySQLだけでなくSQL Serverでも機能するため、このアプローチはエスケープよりも優先します。(通常、エスケープ文字として\バックスラッシュを使用します。これは、エスケープ文字が正規表現で使用される文字だからです。しかし、なぜ慣例によって制約されているのですか!
それらの厄介な括弧
SQL Serverでは、ワイルドカード文字を角かっこで囲むことにより、それらをリテラルとして扱うこともできます[]
。したがって、少なくともSQL Serverでは、まだ修正は完了していません。ブラケットのペアには特別な意味があるため、それらもエスケープする必要があります。かっこを適切にエスケープできれば、少なくともかっこ内のハイフン-
とカラットを気にする必要はありません^
。また、基本的に角括弧の特別な意味を無効にしているため、角括弧内の任意の%
and _
文字をエスケープしたままにすることができます。
大括弧の一致するペアを見つけることはそれほど難しくありません。シングルトン%および_の発生を処理するよりも少し難しいです。(シングルトンブラケットはリテラルと見なされ、エスケープする必要がないため、ブラケットのすべての出現をエスケープするだけでは不十分であることに注意してください。ロジックは、テストケースを実行せずに処理できるよりも少しあいまいになっています。 。)
インライン式が乱れる
SQLのそのインライン式は、ますます長く醜くなってきています。私たちはおそらくそれを機能させることができますが、天国は後ろに来てそれを解読しなければならない貧しい魂を助けます。私はインライン表現が好きなので、ここでは使用しないことにしました。主に、混乱の理由を説明するコメントを残したくないためです。
どこで関数?
さて、SQLでインライン式として処理しない場合、最も近い代替手段はユーザー定義関数です。(Oracleのようにインデックスを定義できる場合を除いて)高速化しないことはわかっています。関数を作成する必要がある場合は、SQLを呼び出すコードでそれを行う方がよいでしょう。ステートメント。
また、DBMSとバージョンによって、その機能の動作にいくつかの違いがある場合があります。(Java開発者の皆さんに、データベースエンジンを交換可能に使用できるように熱心に叫んでください。)
領域知識
列のドメイン(つまり、列に適用される許容値のセット)の専門知識がある場合があります。列に格納された値にパーセント記号、下線、または角括弧が含まれないことを先験的に知っている場合があります。その場合、それらのケースがカバーされているという簡単なコメントを含めるだけです。
列に格納された値は%または_文字を許可しますが、制約は、値がLIKE比較 "安全"になるように、おそらく定義された文字を使用して、それらの値をエスケープする必要がある場合があります。繰り返しになりますが、許可された値のセット、特にエスケープ文字として使用されている文字について簡単にコメントし、Joel Spolskyのアプローチを使用してください。
ただし、専門知識と保証がない場合は、少なくともこれらのあいまいなコーナーケースの処理を検討し、動作が合理的で「仕様どおり」であるかどうかを検討することが重要です。
その他の問題の要約
私は他の人がすでに一般的に考慮されている他の懸念事項のいくつかを十分に指摘していると思います:
SQLインジェクション(ユーザーが入力した情報のように見えるものを取得し、バインド変数を介して提供するのではなく、SQLテキストにその情報を含めます。バインド変数を使用する必要はなく、SQLインジェクションを阻止する便利な方法の1つにすぎません。それを処理する方法:
インデックスシークではなくインデックススキャンを使用するオプティマイザ計画、ワイルドカードをエスケープするための式または関数の必要性(式または関数の可能なインデックス)
バインド変数の代わりにリテラル値を使用すると、スケーラビリティに影響します
結論
ジョエル・スポルスキーのアプローチが好きです。それは賢いです。そしてそれは機能します。
しかし、それを見るとすぐに、潜在的な問題がすぐにわかりました。スライドさせるのは私の性質ではありません。他人の努力を批判するつもりはありません。多くの開発者が自分の仕事を非常に個人的に取っていることを知っています。したがって、これは個人的な攻撃ではありません。ここで私が識別しているのは、テストではなく生産で発生する問題のタイプです。
はい、元の質問から遠く離れています。しかし、質問の「選択された」回答で私が重要な問題であると考えるものに関して、このノートをどこに残すべきですか?