使用 Slick 3 的带有可选 where 子句的动态查询
Dynamic query with optional where clauses using Slick 3
我正在尝试根据一组可能设置也可能不设置的参数实现一种方法来 return 过滤结果。似乎不可能有条件地链接多个过滤器,即从一个过滤器开始...
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 还抱怨 === 是一个未知符号。也不适用于 ==。
为了其他任何试图在 Slick 中使用可选过滤器的人的利益,请在此处查看答案:。我终于设法让它与以下项目一起工作:
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 如下,如果仅设置 departmentLocation...
select * from `flight`
where (`departureLocation` = 'JFK') and true
而没有它 SQL 看起来像...
select * from `flight`
where (`departureLocation` = 'JFK') and (`arrivalLocation` = '')
这显然意味着返回时没有任何行。
无需理解的更简单的方法:
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 === _)
}
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 将负责剩下的
我刚刚想出了一个新的解决方案:
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 年 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
}
我正在使用 Slick 3。3.x。我的解决方案是:
def search(departureLocation: Option[String], arrivalLocation: Option[String]) =
slickFlights
.filterOpt(departureLocation)(_.departureLocation === _)
.filterOpt(arrivalLocation)(_.arrivalLocation === _)
我正在尝试根据一组可能设置也可能不设置的参数实现一种方法来 return 过滤结果。似乎不可能有条件地链接多个过滤器,即从一个过滤器开始...
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 还抱怨 === 是一个未知符号。也不适用于 ==。
为了其他任何试图在 Slick 中使用可选过滤器的人的利益,请在此处查看答案:
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 如下,如果仅设置 departmentLocation...
select * from `flight`
where (`departureLocation` = 'JFK') and true
而没有它 SQL 看起来像...
select * from `flight`
where (`departureLocation` = 'JFK') and (`arrivalLocation` = '')
这显然意味着返回时没有任何行。
无需理解的更简单的方法:
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 === _)
}
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 将负责剩下的
我刚刚想出了一个新的解决方案:
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 年 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
}
我正在使用 Slick 3。3.x。我的解决方案是:
def search(departureLocation: Option[String], arrivalLocation: Option[String]) =
slickFlights
.filterOpt(departureLocation)(_.departureLocation === _)
.filterOpt(arrivalLocation)(_.arrivalLocation === _)