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

问题描述 投票:4回答:5

我正在尝试基于一组可能设置或可能未设置的参数来实现返回过滤结果的方法。似乎没有条件地链接多个过滤器,即从一个过滤器开始......

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

有条件地为查询添加另一个过滤器(如果它存在于params的Map中)似乎不起作用......

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

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

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

根据Hüseyin的建议,我也尝试了以下方法:

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还抱怨= =是一个未知的符号。不适用于==。

scala playframework slick
5个回答
13
投票

一种更简单的方法,无需理解:

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 === _)
}

7
投票

为了其他任何试图在Slick中使用可选过滤器的人的利益,请看一下这里的答案:right usage of slick filter。我终于设法让它使用以下内容:

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)),如果仅设置了departureLocation,则会导致Slick如下呈现SQL ...

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

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

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

这显然意味着它没有任何行回来。


4
投票

January 2019

不再需要发明自己的车轮了!

最后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
}

0
投票

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
}

这种方式在引入另一个可选约束时,可以简单地映射(如pred1和pred2)并添加到preds Iterable,foldLeft将处理其余的。


0
投票

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

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)一起使用。

© www.soinside.com 2019 - 2024. All rights reserved.