代码之家  ›  专栏  ›  技术社区  ›  Ross Anthony

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

  •  8
  • Ross Anthony  · 技术社区  · 9 年前

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

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

    6 回复  |  直到 9 年前
        1
  •  14
  •   ioleo    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)
    }
    

    更新: 你可以用 fold :

    val depLocOpt = Option[Long]
    val slickFlights = TableQuery[Flights]
    val query = slickFlights.filter { sf => 
      depLocOpt.fold(true.bind)(sf.departureLocation === _)
    }
    
        2
  •  13
  •   Andrii Abramov Mk.Sl.    5 年前

    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
  •  7
  •   Community CDub    8 年前

    为了让其他人在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)) 在映射的末尾,这会导致Slick按如下方式呈现SQL,例如,如果只设置了deparatureLocation。。。

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

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

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

    这显然意味着它回来时没有争吵。

        4
  •  1
  •   Alex Elkin    5 年前

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

    def search(departureLocation: Option[String], arrivalLocation: Option[String]) =
      slickFlights
        .filterOpt(departureLocation)(_.departureLocation === _)
        .filterOpt(arrivalLocation)(_.arrivalLocation === _)
    
        5
  •  1
  •   Andrii Abramov Mk.Sl.    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
    }
    

    这样,当引入另一个可选约束时,它可以简单地映射(如 pred1 pred2 )并添加到 preds Iterable,foldLeft将处理其余部分。

        6
  •  0
  •   tg44    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) .

    推荐文章