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。
第4回Kotlin勉強会 @ Sansanにいってきた
AltJava言語(特に手続き型のBetter Java言語)では3年前からCeylon推しだったんですが、Kotlinにはかなわなかったなーと負けを認めてKotlin勉強会に参加してきました。 ちなみに同時期、AltJS言語としてHaxeを推してたけどこれもその後の流れはTypeScriptの完全勝利っぽくて自分の目利きのできなさにウケるんですけど、CeylonとHaxeがこれまた雰囲気似てる言語で苦笑するしかないです。
Sansanさんのオフィスかっこいい
同じく渋谷宮益坂のビズリーチさんところも渋谷Javaで何度かお邪魔していますが、あそこもオフィスかっこいいし、渋谷すごいですね(小並
乾杯で勉強会スタートだ
メインゲストのたろうさんのご意向もあって🍺片手の勉強会となっていました。懇親会じゃなくて本体が乾杯で始める勉強会って初めて。Sansanさんごちそうさまでした。
KotlinでDSLを組む話
Sansanの山本さん(@boohbah)の発表。「KotlinでDSL」
DSLを作る話、ここでは自力でインタープリタを作る“外部DSL”ではなく、ホスト言語機能を応用してDSLを実現する“内部DSL”を作ろうという話です。Kotlinの豊富な糖衣構文機能は、こう言われてみるとまるでDSLを組むために用意されてきたのか、という気がしてきます。
使っている機能は
- 拡張関数 - 既存のクラスにメソッドを生やす。C#でいう拡張メソッド。
- invoke - この名前のメソッドを用意しておくと () 演算子で呼び出せる。C++でいう operator()。
- ()の省略 - 引数がラムダ式ひとつの場合は関数呼び出しの () を省略できる。
- レシーバ付き関数リテラル - ラムダ式の仲間なんだけど、ドット記法で呼び出すので拡張関数と同じくメソッドを生やしたような見た目になる。
- 中置記法 - 関数定義にinfixキーワードを付けると、
a.do(b)
をa do b
と書ける。拡張関数でも使える。要は、好きなクラスに演算子を生やせる。
これらを駆使した結果
buildState( "Start", { setAction( buildTransit( "TO_ZERO", "Zero"), { }) })
的なコードを
"Start" { "TO_ZERO" transitTo "Zero" action { } }
と見事にわかりやすいDSLに落とせましたねと。 なお、このためにString型に拡張関数を生やしていたことについてはツッコミが入っていました。
Android Studio(IntelliJ)のJava→Kotlin変換結果を人手で改善する話
Sansanの辰濱さん(Kenichi Tatsuhama, Sansan | SlideShare)の徳島からのリモート発表。「Java → Kotlin 変換 そのあとに。」
実戦で相当使い込んだんだとよくわかるTips集です。 ここでコメントするよりスライドを見てもらった方が早いですが、
apply
使って一時変数を追放するのは気付いていなかった人多そう。
そしてこの発表でも扱っているJavaの変数宣言のNullabilityを機械判定できない話が次の人の発表につながっていきます。
Javaとの相互運用で型がどうなるかって話
むろほしさん(@RyotoMurohoshi)の発表。「Mapped TypeとPlatform Typeの話」
ひとつめのトピックはプラットフォーム型。Kotlinでは型がnullを許容するかどうか明確だけどJavaではそうじゃない。nullを絶対に返さないメソッドもString
型のようにnullableな型で返すからKotlin側から扱いにくい。全部String?
型扱いにする? それも不要なnullチェックが必要になってノイジー。そこでString!
型。Kotlinの外から来たからnullableかnotNullかわかりませんよという型。それがプラットフォーム型。
これはややnull安全から一歩はみ出した言語仕様で、String!
型はString
にもString?
にも代入できてしまいます。特にString
に代入するときにはプログラマの注意でnullを渡さないようにする必要あり。実行時例外かっとびます。注意と。
もう一つが、int
, Integer
, List
なんかのJDK上の型がKotlinの型システム上でどう見えるか。型マッピングの仕様です。
KotlinではJavaのList
に当たるインターフェースが読み出しのみのList
とそれに書き込みを加えたMutableList
の2層に切り分けられていて、じゃあJavaのList
は相互運用でどう見えるか。Java側でアノテーションを付けることで区別させ、アノテーションなしだと(Mutable)List
というなんかすごい解釈がされるんだそうです。
Androidのビューコンポーネント組み立てをDSL化する話
k-kagurazakaさん(@kkagurazaka)の発表。「Ankoでコンポーネント指向」
XMLで画面を構築するつらみ
- XMLそのものがつらい
- アプリコードと行ったり来たりするのがつらい
- 型安全でないのがつらい
そこでJetBrainsが作っているDSLライブラリ、Ankoどうでしょうという話です。
linearLayout { checkBox = checkBox().lparams { gravity = Gravity.CENTER_VEDRTICAL } }
というコード、最初のDSLの話を聞いていると何が起こっているかわかりやすいですね。
Kotlin 1.1のbound call references機能の紹介がありました。この機能、Javaでは8ですでに導入されている機能の後追いになる、つまりインスタンスのメソッド参照のことなのですが、Kotlin 1.1のこの機能のちょっと恐ろしいところはJavaでgetter/setterの組として記述されたプロパティをひとつの参照対象とできるところ。執念を感じる。
ラムダ式禁止縛りに関数参照とカリー化で対抗しよう
トリはメインゲストたろうさん(@ngsw_taro)さんの発表。「ラムダ式禁止おじさん」
実際にはもちろん、ラムダ式禁止おじさんに張り合いたいのではなく、ラムダ式と同等の書き方をいろいろできるよという切り口からのKotlinの高階関数機能紹介です。
- 単なる一引数関数なら関数参照で単純に置き換え可能(
::println
) - 引数なしメソッドもメソッド参照で単純に置き換え可能(
String::toLowerCase
) - 二引数関数はカリー化すればいい。カリー化する仕組みは自作する。どうやって作るかというと関数に対してinvoke拡張関数を生やす(ラムダ式禁止おじさんを完全に殺す黒魔術)。(
(::minus)(5)
) - カリー化するにも、先頭でなく右側の引数を固定したい場合はどうする? プレースホルダ付きカリー化だ。そういうinvoke拡張関数を生やしてカリー化すればいい。プレースホルダはEnumで定義する。すると⋯ (
(::minus)(アレ, 3)
) - Java8のインスタンスメソッド参照(
"ABC"::toLowerCase
)みたいなのをKotlin 1.0でどうやるか(Kotlin 1.1ならできるようになるのは前の人の発表であった通り)。カリー化invokeが定義された状態で、こう。(String::toLowerCase)("ABC")
- nullableメソッドコール
?.
をするラムダ式はどうする?(引数型->戻り型)型を(引数型?->戻り型?)型に変換するような拡張関数を関数に生やしてしまえ。その拡張関数の名前をnullOk
とすると、(String::toLowerCase.nullOk()
)
そして結論はラムダ式使え。その理由はコードの読みやすさとかそういうのもあるものの、インライン展開というKotlinの利点が殺されてしまうからというのもあります。
懇親会
せっかくの懇親会、お二方としかおしゃべりしませんでしたチキンでごめんなさい⋯
お話しした方の話で印象に残ったのが、「JavaからKotlinへの移行は容易だがKotlinからJavaへは簡単に移行できないので、長期間続けるプロダクトに採用しにくい」という意見でした。そういえばAltJSではTypeScriptが猛烈な勢いで普及していますが、これの強みは言語そのものの良さだけでなく「JavaScriptにも容易に戻れる」という安心感はあるよなと思い起こしました。
全体的な所感
まだKotlinが普及初期であること、あとこの勉強会がまだ4回目と回数を重ねていないこともあり、マニアックな方面に偏ってしまわない誰でも理解できる内容の発表ばかりで、それでいて実のある話が聞けとても良いセッションでした。
LT持ち寄りの勉強会は全体としてのテーマというのが決められていないものなのですが、山本さんがDSL構築の話をしたあとにk-kagurazakaさんがその応用となる話をされていたのが見事にはまっていました。辰濱さんとむろほしさんの発表がnullabityでつながっていたのも一体感ありましたし、神の見えざる手を感じましたね。
発表者の皆様、Sansan様、ありがとうございました。
ちょまど問題の算数的解法を改善=14手
昨日の http://cs.hatenablog.jp/entry/2014/06/18/174157 には出題者の名前からちょまど問題という名前が付いたようです。
16手というのは算数的解法で、数学的にはまだまだ先があるのですが、ともかく算数的には16手が最短かと思っていましたが、まだまだ改善の余地がありましたね。14手になります。
解法のアイディア
- まず3回の試行で正解の分布を知る。正解を多く含む順に上位の選択肢2つ(A,Bと名付ける)と下位の選択肢2つが決まる。
- 9回の試行で、問9までについて正解がAかBか下位かがわかる。下位が正解の問題は最大4問。
- 2択問題が最大で4問なら、それらは2回の試行で正解がわかる。
- 問10の答は正解の分布から差し引きでわかる。
3 + 9 + 2 = 14手。
4択10問テストの正解探索回数=最大16回(破られた)
本日twitter上でエンジニアの間で話題になった問題。
4択問題10問のテストを全部埋めて提出すると正解数がわかります。 何回提出すればすべての正解を知ることができますか。
10問すべての正解は、最大16回の試行で知ることができます。
(2014-06-20追記:これは算数の範囲内での答なのですが、算数の範囲内でもさらに改善できました。14手です。 http://cs.hatenablog.jp/entry/2014/06/20/132605 )
PostgreSQLでは識別子に大文字を使ってはいけない
タイトルの通り、PostgreSQLではテーブル名、カラム名など識別子に英大文字を使うべきではありません。
続きを読むDBの自動連番がロールバックしても戻らない理由
DBの主キーカラムに自動連番(MySQLならAUTO_INCREMENT、SQL ServerならIDENTITY、OracleやPostgreSQLならシーケンス)を設定していると、基本的には連続した数字が付番されていくのですが、行削除もしていないのに抜け番が発生する場合があります。
などの場合です。
特にトランザクションのロールバックは、DB全体の変更をなかったことにするはずなのに連番だけは進んだまま戻らないということで、知らないと違和感があるものです。
続きを読む返す値を一時変数に受けるよりはtry-finally
余計な一時変数というのはなるたけ作りたくないものですが、例えばJavaでこんなコード書いちゃうことあるじゃないですか。
static T poll() { Integer result = array[top]; // (1) if (++top >= array.length) top -= array.length;// (2) return result; }
メソッドの戻り値が決まる(1)のが処理完了(2)より前というパターン。Delphiなら無縁な手間なんです*1が、C系の言語だとメソッド抜けるときまで戻り値を一時変数に受けておかないといけません。
こんな無意味な一時変数美しくありませんね。どこか行ってほしいですね。そして幸い、Javaにはそのための道具があります。try-finallyです。
*1:処理のどの段階ででも、関数名と同名の変数に代入しておいた値が抜けたときに戻り値になります。