C Sharpens you up

http://qiita.com/yuba に移しつつあります

SQLの文法がこう拡張されるとうれしい

SQLを書いていていろんな場面で「こんな文法であってくれたら!」と歯がゆい思いをすることは多々あります。
これまでに、こう拡張されてくれたらと思った内容をまとめてみました。
なお、この拡張は各社DBの“方言”とはまた別のものです。各社方言とは直交した拡張です。
また、純粋なテキスト変換処理で現行のSQLに変換できるようにというのも意図しています。

dangling comma, その他,

カンマ区切りでカラムを列挙するときに、リストの最初と最後に無意味なカンマを許します。

SELECT
  col1,
  col2,  -- ← これがdangling comma
FROM
  some_table

こういう書き方が許されるというもの。カラム構成を編集するときにコピペ操作がしやすくなります。また、gitの変更履歴が見やすくなります。

SELECT, ORDER BY, GROUP BYなどのカラム定義リストのほか、IN句の値リスト、関数呼び出しの引数リスト、WITH句のCTEリストも対象です。

カンマのほか、AND, OR, UNION [ALL], INTERSECT [ALL] についても同様の扱いをします。

空のWHERE, その他

WHERE, HAVING, ON句に何も条件式を書かないことを許します。
これは、句ごと省略されたものと解釈されます。

字句解析がちょっと面倒なことになるので、()は書いてもらうことにしましょう。
すると、

SELECT *
FROM some_table
WHERE (
    ${conditions}
)

のように動的に絞込条件を組み上げる際に、conditionsが空であれば

SELECT *
FROM some_table

だと解釈してもらえるということです。

ここでいう動的な組み上げというのは必ずしもSQLインジェクション脆弱性を生む組み上げ操作というわけではありません。
プリペアドステートメントを組み上げるような場合とお考えいただければ。

空のIN句

col1 IN ()

という書き方を許し、この式は常にFALSEとなるというもの。左辺がNULLであってもFALSE。
もちろん目的は動的なIN句の生成に空リストを渡せるように。

IN, ANY, SOME, ALL(テーブル)

ある値が指定テーブル内に見つかるかを調べるとき

col1 IN (SELECT col2 FROM other_table)

と書くわけですが、other_tableに1カラムしかないことがわかっているのなら

col1 IN (SELECT * from other_table)

でもOKです。 これを

col1 IN other_table

と書いても良いという拡張。特にother_tableがCTEである場合に使いやすいです。1カラムしかないことはこのSQL内で保証できますからね。

EXISTS(テーブル)

上記とほぼ同様。

EXISTS(SELECT 1 FROM some_table)

EXISTS some_table

と書かせてくれよという拡張。

クロスIN

それマルチカラムアトリビュートやってるのを解消すべきじゃないのと怒られることが多そうではありそうですが

(col1 IN (v1, v2, v3) OR col2 IN (v1, v2, v3))

みたいな式を

SOME col1, col2 IN (v1, v2, v3)

-- または

ANY col1, col2 IN (v1, v2, v3)

と書けるようにという拡張。 同様に、

ALL col1, col2 IN (v1, v2, v3)

(col1 IN (v1, v2, v3) AND col2 IN (v1, v2, v3))

と解釈されます。

JOIN USING, JOIN ON

結合条件はSQLだと

table1 JOIN table2 ON 結合条件

-- または

table1 JOIN table2 USING (結合カラム)

と指定するものですが、次のように書けた方が編集しやすいです。

table1 JOIN ON (結合条件) table2

-- または

table1 JOIN USING (結合カラム) table2

だって、結合段数が多いとき、

FROM
  users u
    JOIN USING(user_id)
  blog b
    JOIN USING(blog_id)
  articles a
    JOIN ON(a.article_id = c.article_id)
  comments c

となりますから、行単位で入れ替えをしても式を壊してしまいにくくなりますし見通しも良くなります。

ON句の末尾の判定が難しくなるので、ON句の条件部分を括弧でくくるのは必須になりますね。
(現行のSQLでもON句の条件部分を括弧でくくるのは良い書き方だと思います)

FROM 〜 TO 〜

BETWEEN 〜 AND 〜演算子の使い勝手、悪いと思いません? 「A以上B以下」なので時刻指定の問い合わせでよく使う「A以上B未満」に使えないのです。

そこでFROM 〜 TO 〜演算子。条件式を書くコンテキストに限っては

x FROM a TO b

(a <= x AND x < b)

と解釈されます。

「条件式を書くコンテキスト」ってのが微妙なんですけどね。 PostgreSQLみたいにbool型があって条件式とbool型の計算式の区別がないDBにとっては、WHERE句内とかCASE WHEN句内ではこのFROM-TOが書けるのにSELECT句内でbool型の計算式を書いているときにはFROM-TOが使えませんってなって統一性を欠くことに。

SELECT句を最後に書ける

SELECTがクエリの最初に来るの、ほんとおかしくありません?
だって、クエリを頭から読んでいってまずカラム名のリストを読まされるんだけど何のテーブルだか分からないからそれがカラム名であるかさえよくわからないし型も当然わからない。
先を読まないと意味が確定しない記述なんてプログラミング言語として最低です。

そのくせORDER BY句とかGROUP BY句では、SELECT句で定義して命名したカラムが使えないんですよ。すでに定義が明かなキーワードが逆に使ってもらえないという本末転倒ぶり。

SELECT句が最後に来ればすべてはすっきりするのです。(CTEのことはおいておいて)FROMから書き始め、最後の最後、GROUP BYよりもORDER BYよりもHAVINGよりもLIMITよりも後にSELECTが来る。

そう書かせてくださいよと。おっとそれどこのLINQ