最初に
SQLって嫌ですよね。何が嫌かって
- コーディングスタイルのバラツキ
- キーワードを大文字で書くか小文字で書くか流派が分かれている
- , を先頭に書くのか末尾に書くのかで流派が分かれている
- and or を 先頭に書くのか末尾に書くのかで流派が分かれている
- join の書き方に流派がある
- TABか空白か・・・
- 文法の違い
- DB製品によって文法に細かな違いがある
- 文字コードはDBの設定による
- その他
- row_number() over (partition by ... order by ...)って長すぎ・・
- ビューを定義しないと、一文でSQL文を完結しなければならなくなる。結果、複雑なSQLになる(withなどを使っても同じで1文の中でしか使い回しできない)
特にコーディングスタイルの揺れについて不満を持っていて、何かスタンダードなコーディングスタイルがないかと探してました。
しかし、やっぱり自分の考えとは微妙に違ったりして、いまいち受け入れられません。そんなおりに、ふと思いました。これってAltSQLでっち上げてトランスパイラを作れば多くの問題が解決するのではないかと。
そこで思考実験でAltSQLの文法を考えたいと思います。
思いつきで小1時間程度でつらつらと考えただけです。実装する予定はありません。
基本方針
- キーワードは小文字とし、キーワードの大文字小文字は厳密に区別(大文字派閥の撲滅)
- インデントによる文法解釈をメインにする(, や and の排除)
- 簡単なSQLは簡単に書けるようにしたい(インデントによる構文とは別にメソッド呼び出しのような式で表現できるようにしたい)
- 書きやすさよりは読みやすさを重視する
トランスパイラの動作についてメモ
- SQLの落とし穴を静的検査で回避できるなら積極的に型チェックする(文字列と数値の比較→暗黙の数値変換によりインデックスが効かなくなるため、暗黙な型変換を期待する演算はエラーにするなど)
- トランスパイル時にテーブル構造は参照できるものとする
制限事項
- select 文だけを対象とする
- まだつらつらと考えているだけなので厳密な文法構文は後回しにし、通常のSQLをAltSQLではこう書くというアイデアをまとめていきたい
文法例1: select 〜 where 〜
select
column1
,column2
,column3
from
tableA
where column1 = 'a'
and (column2 = 'b' or column3 = 'c')
;
select tableA
where
column1 = 'a'
column2 = 'b' or column3 = 'c'
column
column1
column2
column3
ここでのポイントは以下
- select の後の改行は禁止
- インデント位置とキーワードで構文を判断(
,
やand
を削除) - where句の条件式は縦に連ねるとandで繋げたことになる。or は明示的に記入する必要がある(条件式の1行は
()
で囲んだかのように解釈することでand,orの優先順位に悩まなくて済むようにする)
インデントについては()
で囲むことでインデントに依存しないようにすることも可能とする(サブクエリや複雑な条件式を書くために)
select tableA
where
column1 = 'a'
(column2 = 'b'
or column3 = 'c')
column
column1
column2
column3
select tableA
where
column1 = 'a'
column2, column3 in (
select tableB b
column
b.column2
b.column3
)
column
column1
column2
column3
ここでサブクエリを使ってますが、サブクエリは別途定義できるものとします
defquery subqueryB
select tableB b
column
b.column2
b.column3
そうすると、先のSQLは以下のように記述できます。
select tableA
where
column1 = 'a'
column2, column3 in subqueryB
column
column1
column2
column3
ポイント
- クエリ定義文はそれだけで完結しており、定義以降に現れたクエリ名はサブクエリとして解釈される
- クエリ定義文は標準SQLのwith句のようにクエリの使い回しができ、かつ1文に繋げる必要がない
また、この場合、サブクエリを使うときに列名を指定したい場合があるでしょう。その時は以下のように書きます。
select tableA
where
column1 = 'a'
column2, column3 in subqueryB.select(column2, column3)
column
column1
column2
column3
このメソッド呼び出し形式のselectを使うと、そもそも以下のようにサブクエリを定義する必要がありませんでした。
select tableA
where
column1 = 'a'
column2, column3 in tableB.select(column2, column3)
column
column1
column2
column3
エイリアス
- テーブル名のエイリアスはテーブル名の右に別名を記述します(
as
は書きません) - where句等で利用するための、カラム名のエイリアスは別途エイリアス定義句を使います
- column句の列名の右に書いたエイリアスは出力のカラム名になります
select
column1 as c1
,column2 as c2
,column3 as c3
from
tableA a
-- DB製品によると思いますが、ここでc1とは普通書けません
where a.c1 = 'a'
and (a.c2 = 'b' or a.c3 = 'c')
;
select tableA a
alias
c1: column1
c2: column2
c3: column3
where
a.c1 = 'a'
a.c2 = 'b' or a.c3 = 'c'
column
c1 a
c2 b
c3 c
ポイント
- 先にエリアス定義を書かせることで、エディタの補完機能を実装しやすくする
- column 句の項目名の右に別名を書いた場合、その別名はそのクエリの項目名になる(その項目名をwhere句で使うことはできない)
別案
column 句の別名を左に書いた方が、その定義内容をインデントして書くことができて良いかもしれない(矛盾なく書けるのかの評価は必要)
その場合、テーブルの別名の書き方も揃えた方が良いでしょう
select tableA
column
a: c1
b: c2
rn:
row_number()
partition
c1, c2
order
c3
cnt
count(*)
join
select tableA a
alias
c1: column1
c2: column2
c3: column3
join tableB b
a.c1 = b.c1
ljoin tableC c
c2
column
a.c1 a
b.c2 b
c.c3 c
ポイント
- エイリアス定義(c1,c2,c3)は複数のテーブルに跨って適用される
- 各種 join は全て一単語で表す(これもキーワードの補完をしやすくするため)
- inner join => join
- left join => ljoin
- right join => rjoin
- cross join => cjoin
- full join => fjoin
- など
- join の下の行に項目名だけを書いた場合はusing句として扱う
group by 〜 order by 〜
select tableA a
alias
c1: column1
c2: column2
c3: column3
column
a.c1 a
a.c2 b
a.c3 c
count(*) cnt
group
a.c1 a
a.c2 b
a.c3 c
order
a.c1 a
a.c2 b
a.c3 c
ポイント
- by は書かない(キーワードは基本1単語)
- column句と同じ別名を書いても良い(単に無視される。column句に書いた内容を単純にコピーペーストしたい)
例のように、column句と同じ場合は省略できるようなのも検討したい
column句をwhere区の後にする理由
ところで、通常のSQLでは、select column table where group order の順ですが。
AltSQLではcolumnを後にしています。これには以下の意図があります
-
alias の参照など下から上を参照する形にしたい
column が上にあるとその別名をwhere句で参照できそうに感じます。
-
column と group, order は近くにあって欲しい
column を変更すると group, order も変更する必要があるので記述箇所は近い方が良い
-
普通、where句やjoinの結合条件を先に考えて、何を出力するかは最後になる。
通常、試行錯誤しながらSQLを書くときは
sqlselect * from tableA where ...
と、とりあえず、選択リストを
*
にしておいて、where句や結合条件があっているかを確認しながら書きます。そうして、条件があっていることが確認できた後に、何を出力するかを決定します。そのような思考順序を反映しています。(私だけかも知れませんが)
limit, for update, SQLヒント などの特殊用途な構文(草案)
これらの構文は以下のように meta 区の後にオプション指定として記述します。
(limit が meta なのか?は検討が必要)
select tableA
meta
limit 10
for_update -- 無理やり1語にするのかは要検討
result_cache
use_index index_name
ポイント
- 様々な予約語をmeta区内に閉じ込めることでDB製品毎に異なる予約語のバッティングを避ける
- 特定DB固有の機能を指定するなどの拡張を記述する余地を残す