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

时间:2016-03-27 11:23:26

标签: scala playframework slick

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

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

6 个答案:

答案 0 :(得分: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 === _)
}

答案 1 :(得分: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` = '')

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

答案 2 :(得分: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 :(得分:0)

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

shinyServer(
  function(input, output, session) {
    # init reactiveValues
    selectedData <- reactiveValues()

    observeEvent(input$selectData, {
      if (input$selectData == "Universities") {
        selectedData$orgdata <- read.csv("universities.csv")
      } else if (input$selectData == "Hospitals") {
        selectedData$orgdata <- read.csv("hospitals.csv")
      }

      updateSelectizeInput(session, "Organisations",
                           choices = as.character(unique(selectedData$orgdata$name)),
                           server = TRUE)
    })

    ... # rest of your code

  }
)

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

答案 4 :(得分: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)一起使用。

答案 5 :(得分:0)

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

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