Create  Edit  Diff  FrontPage  Index  Search  Changes  Login

The Backyard - PreparedStatementAndNull Diff

  • Added parts are displayed like this.
  • Deleted parts are displayed like this.

http://pc5.2ch.net/test/read.cgi/db/1103113155/312-343n から転記

http://d.hatena.ne.jp/habuakihiro/comment?date=20050318#c 経由

!SQL質疑応答スレ Part 2

!!312 :1/3:05/03/12 03:17:26 ID:???
突然ですが,面白い SQL を思いついたので,ご意見を頂きたいです.興味を持たれた方は使ってみてくだちい。

一般的に,SQL の発行は,プリペアドステートメントを使う方がよいとされている.その理由は,
*DBMS のキャッシュが良く効く.
*SQL インジェクション等の危険が避けられる,等.

しかし,webアプリの検索フォームなどでは,プリペアドステートメントは使えない場合が多かった.検索条件項目が複数存在していて,いずれの項目も必須で無い場合,項目が入力されているかどうかをチェックし,それに合わせてSQL 文の WHERE 句の内容をツギハギしなければならないからだ.

SQL文そのものも,そしてそれにセットする引数の個数も可変なので,プリペアドステートメントを使うのは無理だったのである.

今回思いついたのは,このような条件で,プリペアドステートメントを使う方法である.


!!313 :2/3:05/03/12 03:19:15 ID:???

やることは大変簡単で,

#WHERE 句の内容全てを 括弧で囲み,最後に「IS NOT FALSE」という述語を付ける.
#空欄になっている,検索条件に含めない項目には,NULL をセットする.(空文字列や空白をセットしてはいけない)

この二つだけである.

    例)ユーザID,職業,年齢を条件として検索する.どれも必須項目でない.
    SELECT *
    FROM Table_Name
    WHERE
    (
          user_id = ?
      AND   profession = ?
      AND   age >= ?
      AND   age < ?
    )IS NOT FALSE
    ORDER BY user_id


!!314 :3/3:05/03/12 03:20:43 ID:???
このように書くと,null をセットした項目は,検索条件として使われないかのように振舞う.SQL を if 文でツギハギにする必要も無くなり,ぐっとコードが読みやすくなると思う.

なんでこういう動作になるのかというと,SQL が実は3値論理だからである.詳しくはこの辺を参照.http://www.geocities.jp/mickindex/database/db_3vl.html (私自身とは無関係のページです)

気になるパフォーマンスだが,PostgreSQL で測定したところ,(  ) IS NOT FALSE がつかない場合と比べて,全く差は無かった.実は既に,現在進行中のプロジェクトで使っている.今のところ,全く問題は無い.

以上です.質問スレにこういうことを書くのは,かなりスレ違いですみませんが,SQL 自体について一番詳しい方が来られるのはここだと思って,書かせていただきました.ご意見,ご批判などを伺いたいと思います.


!!315 :NAME IS NULL:05/03/12 10:30:54 ID:???
同じものを2回ずつ指定するのがスマートじゃないがベンダー依存の部分を排除するとこんな感じ。

    WHERE (user_id = ? OR ? IS NULL) AND (profession = ? OR ? IS NULL)
    AND (age >= ? OR ? IS NULL) AND (age < ? OR ? IS NULL)

!!316 :NAME IS NULL:05/03/12 11:11:54 ID:???
これはちょっと面白いねえ。PreparedStatement は面倒なんで試してないけど、普通のsqlで書いてみると、age < NULL みたいにNULLを指定した条件は確かに無視されるよ。仕組みは良くわかんないけど。

    >>315
    ベンダ依存なの?これってSQL92だと思うんだが。


!!317 :NAME IS NULL:05/03/12 11:12:02 ID:???
>>313

user_idが5であとがnullだったら全件ヒットしない?

!!318 :NAME IS NULL:05/03/12 11:25:55 ID:???
>>316

おまいの使ってるDBMSはなんだ?少なくともOracle9では動かんぞ.まあOracleは,結構特殊だったりするけど.


!!319 :NAME IS NULL:05/03/12 11:34:15 ID:???
BOOLEAN値はSQL99あたりからだったかな?

そのせいでまだサポートしてなかったりシノニムで済ませてる処理系が多い。

ただ IS [NOT] [FALSE|TRUE|UNKNOWN]はpostgreSQL独自だと思ったけど。

!!320 :316:05/03/12 11:36:49 ID:???
>>318

    俺は PostgreSQL7.3だけど、Oracleで動かないのかよ、これ。まいったな。どうみても基本的な構文じゃないか。


!!321 :NAME IS NULL:05/03/12 11:53:10 ID:???
>>319

Boolean 型は確かに,SQL99 からだ.http://www.atmarkit.co.jp/fnetwork/tokusyuu/01sql99/sql99_1a.html

しかし,IS [NOT] (TRUE | FALSE | UNKNOWN) は,それとは関係無く SQL92 だ.『プログラマのためのSQL(第2版)』第11章「評価述語」に,そう説明がある.PostgreSQL 独自拡張ではない.


!!322 :NAME IS NULL:05/03/12 12:09:15 ID:???
>>312-314

最初はチョト理解できなかったけど、こういうことか。

    user_id = NULL 等とすると NULL(UNKOWN)になるから、

    (TRUE and TRUE) -> TRUE (IS NOT FALSE)
    (TRUE and NULL) -> NULL (IS NOT FALSE)
    (FALSE and NULL) -> FALSE
    (FALSE and FALSE) -> FALSE

いけるかも。ありがたく使わせていただきま。

!!323 :NAME IS NULL:05/03/12 12:29:50 ID:???
>>321

了解、SQL92なんだけどサポートしてる処理系が少ないということかな。とりあえず OracleとMSSQLはだめっぽい。MySQLマニュアルを見た限り見当たらないけどどーなんでしょう?

>>322

IS NOT FALSEで真になるのはTRUEかUNKNOWNの場合に最終的にTRUEになるわけだ。ただそのままいただいちゃうとまずいかも次のようにすべき。

    WHERE (user_id = ?) IS NOT FALSE
    AND (profession = ?) IS NOT FALSE
    AND (age >= ?) IS NOT FALSE
    AND (age < ?) IS NOT FALSE

!!324 :322:05/03/12 12:38:27 ID:???
>>323

何故「そのままいただいちゃうとまずい」のかご教示いただきたく。

!!325 :NAME IS NULL:05/03/12 12:51:50 ID:???
>>324

すまん、俺が間違ってた。

false and unknown は falseだからいいのか。

false and unknown を unknownと勘違いしてました。

!!326 :312-314:05/03/12 14:02:59 ID:???
いろいろなご意見,ありがとうございます.見慣れない構文なので,受け入れていただけるかどうか不安でしたが,ご理解いただけたみたいで,大変ありがたいです.

私が今回の SQL に込めたかった主張は「もっと積極的に,NULL と3値論理を使おうよ」ということです.

NULL の危険性を理解せずに,カラムに NOT NULL 制約をつけないのは論外ですが,NULL を目の敵のように排除して,2値論理だけしか扱わないのも,もったいないと思うのです.3値論理はそんなに難しいものではありませんし,2値論理にはない便利さがあるのですから.


!!327 :312-314:05/03/12 14:05:37 ID:???
(   ) IS NOT FALSE は,例えばこんなふうにも使えます.

テーブル同士を JOIN するとき,カラム名が共通なら USING を使うの便利だが,一つでも共通でないカラム名がある場合は,ON TableA.id = TableB.id ... と,ON 句をズラズラ書かなければならなくなる.USING 句と ON 句は併用できないからである.

これには回避策があって,USING 句には共通のカラム名だけを並べ,残りのカラムはWHERE 句で結合することができる.しかし,これは外部結合(LEFT JOIN など)の場合は使えない.片方のカラムが NULLになる場合が,ぜんぶ除かれてしまうからである. [[resume help|http://perfectresume.org/]]

そこで,(  ) IS NOT FALSE である.これで,片方が NULL でも結合ができる.

    SELECT *
    FROM  TableA LEFT JOIN TableB USING(a, b, c)
    WHERE (TableA.hoge = TableB.fuga) IS NOT FALSE


!!328 :312-314:05/03/12 14:07:38 ID:???
上の SQL は,パフォーマンスは確かめてないので,自己責任で試してくだちい.

もう一つ,私の込めた主張は,「ベンダはちゃんと,IS [NOT] (TRUE | FALSE | UNKNOWN) をサポートしてよ」ということです.これがないと,3値論理をきちんと使えませんから.

私が試したのは Oracle9, MySQL4.1, PostgreSQL7.4 ですが,PostgreSQL しかサポートしていませんでした.こんなに便利なのに,なんとも残念です.

とくにOracle! 再帰的SQL まで用意しているくせに,IS TRUE どころか USINGすら満足に動かぬ.優先順位というものがあるのじゃないかと思うのだけど.

!!!他の DBMS はどうなんだろう?SQL-Server, Firebird なんかをお使いの方はおられませんか?

他の!!329 :312-314:05/03/12 14:30:01 ID:???
PostgreSQL と Struts をお使いの方にだけ補足.

!!!PreparedStateMent に setInt すると,NULL を代入できません.

だから,数値も setString してやる必要があります.この場合,比較が文字列比較になってしまいますから,きちんと数字で比較されるよう,セットされた値をキャストしてやる必要があります.

    age < ?::INTEGER

!!!空欄なら NULL をセットするようにするには,ActionFormBean の getter に細工をしてやるのが便利です.

    public String getAge() {
      return "".equals(this.age) ? null : this.age;
    }

しばらくこのスレを離れます.次に来るのは遅くなるかもしれません.


!!330 :NAME IS NULL:05/03/12 15:30:05 ID:???

>>329
>PreparedStateMent に setInt すると,NULL を代入できません.
>だから,数値も
DBMS はどうなんだろうsetString してやる必要があります.
>この場合,比較が文字列比較になってしまいますから,きちんと数字で比較
>されるよう,セットされた値をキャストしてやる必要があります.

確実に遅くなるじゃん

!!331 :NAME IS NULL:05/03/12 15:37:48 ID:???

まあ何だANSI SQLはエスペラント語みたいなもんだな。もっと下世話な例ではビデ倫みたいなのもで○○不可が売りになる。

>>329

PostgreSQLはよくわからなけどPreparedStatement#setNullじゃだめ
SQL-Server, Firebird なんかをお

!!332 :NAME IS NULL:05/03/12 18:11:45 ID:???
>>331

if 文を使いたくないから,この構文を導入したのに,値があるかどうかでsetInt() と setNull() をif文で切り替えるんなら,あんまり意味なくない?

!!333 :NAME IS NULL:05/03/12 19:16:02 ID:???
>>332

ActionFormはどっちにしろ文字型だからどうでもいいのですが、PreparedStatementで数値の項目にsetStringするのはどうかといいたいのです。

暗黙に文字から数値の変換はやってくれてるようだけど、これはベンダー依存ではないかといいたいわけです。JDBCが仕様的にこの手の変換をサポートしてるならかまわないだけれどね。

!!334 :NAME IS NULL:05/03/12 20:24:07 ID:???
>>332

それもあるけど、問題なのは条件によってSQLを動的生成するのではなくPreparedStatementを使いたいということらしいのでそこくらいはどうとでもなるだろう。

何ならnullかどうか判断してsetIntかsetNullか必要な方を使うメソッドでも作れば良いだけだし

!!335 :NAME IS NULL:05/03/13 03:03:25 ID:4M7LWvrN
ttp://www.ashisuto.co.jp/magazine/topic.php?A=17&B=183

この記事の中段下付近に CREATE VIEW と CREATE INDEX は置き換え可能みたいなことが書かれているのですが今ひとつ理解できません。どなたか解説してもらえないでしょうか?

!!336 :NAME IS NULL:05/03/13 03:52:20 ID:???
>>335

読んだけど全体的に何のことやらさっぱりだね。何度か読み直して一部がなんとなく理解できる程度。

「理解できないのはあなたがバカだから私の提案する方法にしなさい」

って言われている気がしてきた。まぁ、それはそれでいいんだけどさ◆本物のデータを集める◆の実例のところは一理あるかも知れんけど、ニワトリとタマゴ的な問題で、一方の考え方を押し付けた感じがする。
スレ違いな反応でスマソ


!!337 :NAME IS NULL:05/03/13 10:35:50 ID:???
>>335

「抜粋・収録したものです」ってあるからライターの力量の欠如と見た。アシストは汎用機向けのツールもつくってるから聴衆は汎用機の顧客で管理者クラスと思える。特にVIEWをはじめ幾つかの用語がこの聴衆にわかりやすくするためにある前提で語られていて一般的な用法ではないと思われるが、この要約ではその説明が省略されているように思える。内容をよく理解せず抜粋して飛び飛びで文章をつないだ感じだ。

!!338 :NAME IS NULL:05/03/13 12:13:21 ID:???
>>335

T字形のおっさんだな。ここを読むと理解の助けになるかも。

ttp://www.sdi-net.co.jp/news-index01.htm

もともと癖の強い主張をしているだけあって、読んでも理解できない部分もあるが。

「トラヴァーサル・テーブル」の項を読むと、最適化が弱かった時代の古いRDBMSを相手に話しているのがわかる。

!!339 :NAME IS NULL:05/03/13 21:33:07 ID:???
>古いRDBMSを相手に話しているのがわかる。

納得しました。

!!340 :NAME IS NULL:05/03/14 09:31:44 ID:???
>>335

この部分だけ読むと意味不明だよな。

>>338さんの書いてるT型ER図ってのをすこし齧らんとわからん。

で、一応は理想的に最適化されたテーブルでは、その通りだとは思う。あと、実装面でもクラスタ化されたIndexであれば実表参照しないので、=View実装とはなる。しかも、Viewとちがって扱いやすいし早い。(最近はViewに対するIndexとかあるからまた別な気もするが・・・。)

!!341 :312-314:05/03/14 11:03:36 ID:???

大変遅くなりました.少しだけ補足.

>>333

指摘はもっともですが,おっしゃっている内容は,実際と逆です.PostgreSQL は String をセットされた場合,数値型に型変換はしません.?::INTEGER は見慣れない構文ですが,これは Integer 型への明示的なキャストです.PostgreSQL 専用の構文ですので,他のDBをお
使いの方はおられませんか?方はわかりづらかったかもしれません.

>>334

私の今回のプロジェクトは,あまり速度を必要としなかったので記述の簡単さを考えてこのような書き方にしましたが,速度を考えた場合,おっしゃる通り新しい関数を作って,それを実装した PreparedStatement の子クラスを作るか,setBigDecimal() を使うかした方が良いでしょうね.

!!342 :312-314:05/03/14 11:56:56 ID:???
実は,私はあまり,実装の細かいところの話をするつもりはあまりなくて,NULL と UNKNOWN を積極的に使うことによって,従来出来なかったことがいろいろ出来るようになる,という話で盛り上がりたいと思っていたのです.

残念ながら,それは私の力不足でうまく行かなかったようです.どうも,私の出した例が,いずれもあまり説得的でなかったようですね.

私の例にとらわれず,今後どなたかがとても面白い利用法を思いつかれることを期待します.

では,失礼します.

!!343 :NAME IS NULL:05/03/14 12:25:05 ID:???
>>342

奇しくも>>338のT字形のおっさんによればnull値は使うなってことになってる。

↓ここの十戒の(7)

ttp://www.sdi-net.co.jp/waseda-ext-note-20041203.htm

!!その後の情報
# no-name 『皆さんありがとうございます.一つだけ補足を.ある行の検索対象のカラムが NULL だと,検索条件を指定しても,その行はヒットしてしまいます.気をつけてお使いください.』( http://d.hatena.ne.jp/habuakihiro/comment?date=20050318#c )