使用 Slick 3 的带有可选 where 子句的动态查询

Dynamic query with optional where clauses using Slick 3

我正在尝试根据一组可能设置也可能不设置的参数实现一种方法来 return 过滤结果。似乎不可能有条件地链接多个过滤器,即从一个过滤器开始...

val slickFlights = TableQuery[Flights]
val query = slickFlights.filter(_.departureLocation === params("departureLocation").toString)

有条件地向查询添加另一个过滤器(如果它存在于参数映射中)似乎不起作用...

if (params.contains("arrivalLocation")) {
      query.filter(_.arrivalLocation === params("arrivalLocation").toString)
}

这种条件过滤是否可以通过其他方式使用 Slick 完成?

我遇到了 MaybeFilter: https://gist.github.com/cvogt/9193220,这似乎是处理这个问题的一种不错的方法。但是它似乎不适用于 Slick 3.x

根据侯赛因的建议,我也尝试了以下方法:

def search(departureLocation: Option[String], arrivalLocation: Option[String]) = {
    val query = slickFlights.filter(flight =>
       departureLocation.map {
          param => param === flight.departureLocation
       })

其中 slickFlights 是一个 TableQuery 对象 val slickFlights = TableQuery[Flights]。但是,这会产生以下编译错误:

value === is not a member of String

Intellij 还抱怨 === 是一个未知符号。也不适用于 ==。

为了其他任何试图在 Slick 中使用可选过滤器的人的利益,请在此处查看答案:。我终于设法让它与以下项目一起工作:

def search(departureLocation: Option[String], arrivalLocation: Option[String]) = {
  val query = for {
    flight <- slickFlights.filter(f =>
       departureLocation.map(d => 
         f.departureLocation === d).getOrElse(slick.lifted.LiteralColumn(true)) && 
       arrivalLocation.map(a => 
         f.arrivalLocation === a).getOrElse(slick.lifted.LiteralColumn(true))
    )
  } yield flight

关键位是地图末尾的 .getOrElse(slick.lifted.LiteralColumn(true)),这会导致 Slick 呈现 SQL 如下,如果仅设置 departmentLocation...

select * from `flight` 
where (`departureLocation` = 'JFK') and true

而没有它 SQL 看起来像...

select * from `flight` 
where (`departureLocation` = 'JFK') and (`arrivalLocation` = '')

这显然意味着返回时没有任何行。

无需理解的更简单的方法:

import slick.lifted.LiteralColumn

val depLocOpt = Option[Long]
val slickFlights = TableQuery[Flights]
val query = slickFlights.filter { sf => 
  if (depLocOpt.isDefined) sf.departureLocation === depLocOpt.get
  else                     LiteralColumn(true)
}

更新: 您可以使用 fold

进一步缩短它
val depLocOpt = Option[Long]
val slickFlights = TableQuery[Flights]
val query = slickFlights.filter { sf => 
  depLocOpt.fold(true.bind)(sf.departureLocation === _)
}

Ross Anthony 的(优秀)回答可以通过使用 foldLeft 进一步推广:

slickFlights.filter { f =>
    val pred1 = departureLocation.map(f.departureLocation === _)
    val pred2 = arrivalLocation.map(f.arrivalLocation === _)
    val preds = pred1 ++ pred2  //Iterable
    val combinedPred = preds.foldLeft(slick.lifted.LiteralColumn(true))(_ && _)
    combinedPred
}

这种方式当引入另一个可选约束时,它可以简单地映射(如 pred1pred2)并添加到 preds Iterable,foldLeft 将负责剩下的

我刚刚想出了一个新的解决方案:

implicit class QueryExtender[E, U, C[_]](base: Query[E, U, C]) {
  def filterOption[T: BaseTypedType](option: Option[T], param: E => Rep[T]) = {
    option.fold {
      base
    } { t => base.filter(x => param(x) === valueToConstColumn(t)) }
  }
}

如果您有一个查询 (slickFlights) 一个选项值和一个生成器,您可以将它与 slickFights.filterOption(departureLocation, _.departureLocation) 一起使用。

2019 年 1 月

再也不用自己发明轮子了!

最后Slick 3.3.0包括以下助手:

因此,例如:

case class User(id: Long, name: String, age: Int)
case class UserFilter(name: Option[String], age: Option[Int])

val users = TableQuery[UsersTable]

def findUsers(filter: UserFilter): Future[Seq[User]] = db run {
  users
    .filterOpt(filter.name){ case (table, name) =>
      table.name === name
    }
    .filterOpt(filter.age){ case (table, age) =>
      table.age === age
    }
    .result
}

我正在使用 Slick 3。3.x。我的解决方案是:

def search(departureLocation: Option[String], arrivalLocation: Option[String]) =
  slickFlights
    .filterOpt(departureLocation)(_.departureLocation === _)
    .filterOpt(arrivalLocation)(_.arrivalLocation === _)