色んなSQLをSlickで書くと?

上記の記事のSlick版です。

groupByに関するSlick 1.0.0のバグを回避するため、1.0.1-RC1を使用します。

例によってテーブル名とかは弄りました。 テーブル定義を含む全てのコードは以下のGistを参照してください。

LEFT JOIN + IS NULL

SQL

SELECT *
  FROM Students s
  LEFT JOIN Clubs c on (s.club_id = c.id)
  WHERE c.id IS NULL

Slick

val q1 = for {
  (s, c) <- Students leftJoin Clubs.map(_.??) on (_.clubId === _._1)
  if c._1.isNull
} yield (s, c)

val r1: List[(Student, (Option[Long], Option[String]))] = q1.list()

いきなり苦労した。

??id.? ~ name.?のaliasとしてClubsに定義してあります。 普通にStudents leftJoin Clubs on ...とするとNULLだよボケみたいな実行時エラーになるので注意が必要です。

難点は元エントリの趣旨に反してSlickが生成するSQLが以下のように意図したものとは違うことでしょうか。

select x2.x3, x2.x4, x2.x5, x2.x6, x7.x8, x7.x9
  from (
    select x10."NAME" as x3, x10."CLASSROOM" as x4, x10."CLUB_ID" as x5, x10."ID" as x6
      from "STUDENTS" x10
  ) x2
  left outer join (
    select x11."ID" as x8, x11."NAME" as x9
      from "CLUBS" x11
  ) x7 on x2.x5 = x7.x8
  where x7.x8 is null

SlickのTabe定義が実際のDBテーブルのカラムを全て含まなくてもいいからこうなる? どうしても必要ならLifted EmbeddingじゃなくてPlain SQLを書かなければいけません。(前述のGist参照)

これ以外の問題では意図した通りのSQLが生成されました。(*は使われないけど)

相関サブクエリー

SQL

SELECT *
  FROM Students s
  WHERE EXISTS (
    SELECT c.id
      FROM Clubs c
      WHERE s.club_id = c.id
  )

Slick

val q2 = for {
  s <- Students
  if (for {
    c <- Clubs if s.clubId === c.id
  } yield c.id).exists
} yield s

val r2: List[Student] = q2.list()

割と素直な見た目。

複数のカラムでgroup by

SQL

SELECT s.club_id, s.classroom, count(*)
  FROM Sutudents s
  GROUP BY s.club_id, u.classroom

Slick

val q3 = for {
  ((ci, cr), ss) <- Students.groupBy(s => (s.clubId, s.classroom))
} yield (ci, cr, ss.length)

val r3: List[(Option[Long], String, Int)] = q3.list()

Slick 1.0.0は複数カラムのgroup byがバグってて実行時エラーになるので1.0.1-RC1を使ってください。

SQLのcountはlengthメソッドで取得することになってます。countメソッドもありますがdeprecatedです。