首页 文章

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

提问于
浏览
3

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

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

5 回答

  • 7

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

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

    UPDATE: 你可以用 fold 缩短它:

    val depLocOpt = Option[Long]
    val slickFlights = TableQuery[Flights]
    val query = slickFlights.filter { sf => 
      depLocOpt.fold(true.bind)(sf.departureLocation === _)
    }
    
  • 0

    为了其他任何试图在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
    

    关键位是 Map 末尾的 .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` = '')
    

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

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

  • 13

    2019年1月

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

    最后Slick 3.3.0包括以下帮助者:

    • filterOpt

    • filterIf

    所以,例如:

    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
    }
    

相关问题