Screaming Loud

日々是精進

slickにおける動的にwhere句を作る

Slickにおける動的where句の作り方

現在最新は3系ですが環境としては、Slick2.1での書き方での紹介です。
Documentation | Slick

Slickにおいて動的にwhere句を作るのは、案外Scalaを始めたばかりだとうまくいきません。

以下の様なクラスに対して複数の検索をかけるとき、

type Address = (Int,String,String)
class Addresses(tag: Tag) extends Table[Address](tag, "ADDRESS") {
  def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
  def street = column[String]("STREET")
  def city = column[String]("CITY")
  def * = (id,street,city)
}
lazy val addresses = TableQuery[Addresses]

多分一番最初に思い浮かぶ方法は以下だと思います。

def find(streetOp: Option[String], cityOp: Option[String])(implicit session: Session) = {
  val q = streetOp.map(s => addresses.filter(_.street === s).getOrElse(addresses)
  val q1 = cityOp.map(c => q.filter(_.city === c).getOrElse(q)
  q1.list
}

しかし、実はfilter()の中をColumn[Option[Boolean]]を返すことで条件として満たします。
なので、以下の様な書き方ができます。

def makeCondition(row: Addresses, streetOp: Option[String], cityOp: Option[String]): Column[Option[Boolean]] = {

    val True: Column[Option[Boolean]] = Some(true)
    val a: Option[Boolean] = (if (streetOp.nonEmpty) row.street === streetOp.get else True) &&
      (if (citiOp.isDefined) row.city === cityOp.get else True)
    a   
}
addresses.filter(q => makeCondition(q, Some("東海道"), None)).list

この書き方の場合、検索条件を関数化できるので、複数の部品による組み合わせができます。

例えば、

addresses.filter(q => makeCondition(q, Some("東海道"), None) || q.id === 1).list

みたいなことができます。

使う機会は多いのに、ドキュメントからではなかなかわからないのですが、ぜひ活用してみてください。