【问题标题】:Dynamic query with optional where clauses using Slick 3使用 Slick 3 的带有可选 where 子句的动态查询
【发布时间】:2016-03-27 11:23:25
【问题描述】:

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

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

根据 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


    【解决方案1】:

    没有理解的更简单的方法:

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

    【讨论】:

      【解决方案2】:

      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
      }
      

      【讨论】:

      • 现在这么简单!谢谢!
      【解决方案3】:

      为了让其他尝试在 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)),如果仅设置了departmentLocation,这将导致Slick 如下呈现SQL...

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

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

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

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

      【讨论】:

      • 感谢您,直到今天仍然有帮助
      【解决方案4】:

      我使用的是 Slick 3.3.x。我的解决方案是:

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

      【讨论】:

        【解决方案5】:

        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 将处理其余部分。

        【讨论】:

          【解决方案6】:

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

          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-08-27
            • 1970-01-01
            • 1970-01-01
            • 2011-07-05
            • 1970-01-01
            • 1970-01-01
            • 2012-05-05
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多