Slick:以列为参数的 LIKE 查询
Slick: LIKE query with column as parameter
我有以下 SQL 查询,我想将其映射到 Slick
SELECT * FROM rates
WHERE '3113212512' LIKE (prefix || '%') and present = true
ORDER BY prefix DESC
LIMIT 1;
但是没有为 String 定义 like 符号:
case class Rate(grid: String, prefix: String, present: Boolean)
class Rates(tag: Tag) extends Table[Rate](tag, "rates") {
def grid = column[String]("grid", O.PrimaryKey, O.NotNull)
def prefix = column[String]("prefix", O.NotNull)
def present = column[Boolean]("present", O.NotNull)
// Foreign keys
def ratePlan = foreignKey("rate_plan_fk", ratePlanId, RatePlans)(_.grid)
def ratePlanId = column[String]("rate_plan_id", O.NotNull)
def * = (grid, prefix, present) <>(Rate.tupled, Rate.unapply)
}
object Rates extends TableQuery(new Rates(_)) {
def findActiveRateByRatePlanAndPartialPrefix(ratePlan: String, prefix: String) = {
DB withSession {
implicit session: Session =>
Rates.filter(_.ratePlanId === ratePlan)
.filter(_.present === true)
.filter(prefix like _.prefix)
.sortBy(_.prefix.desc).firstOption
}
}
}
显然这样的事情是行不通的:
.filter(prefix like _.prefix)
并且:
.filter(_.prefix like prefix)
会呈现不正确的 SQL,我现在甚至都没有考虑“%”(只有与前缀有关的 WHERE 子句:
(x2."prefix" like '3113212512')
而不是:
'3113212512' LIKE (prefix || '%')
当然我可以使用静态查询来解决这个问题,但我想知道这是否可行?
为清楚起见,这里是数据库中的一些前缀
31
3113
312532623
31113212
预计结果为 31113212
更新 2
实际上不需要concat,也可以这样写:
filter(s => LiteralColumn(prefix) like (s.prefix ++ "%"))
更新:
经过 cvogt 的提示,这是没有使用内部 Slick 的最终结果 APIs:
val concat = SimpleBinaryOperator[String]("||")
Rates.filter(_.ratePlanId === ratePlan)
.filter(_.present === true)
.filter(s => LiteralColumn(prefix) like concat(s.prefix, "%"))
.sortBy(_.prefix.desc).firstOption
完美给出:
and ('3113212512' like (x2."prefix" || '%'))
选择:
这似乎是可能的。但是请注意,以下方法使用 Slick 的内部 API,由于 API 更改,将来可能不兼容(请参阅 cvogt 的评论)我使用 Slick 2.1.0.
基本上我创建了一个 Slick 扩展,我在其中定义了一个自定义方案来实现所需的结果:
package utils
import scala.language.{higherKinds, implicitConversions}
import scala.slick.ast.ScalaBaseType._
import scala.slick.ast._
import scala.slick.lifted.{Column, ExtensionMethods}
object Helpers {
implicit class PrefixExtensionMethods[P1](val c: scala.slick.lifted.Column[P1]) extends AnyVal with ExtensionMethods[String, P1] {
def subPrefixFor[P2, R](prefix: Column[P2])(implicit om: o#arg[String, P2]#to[Boolean, R]) = {
om.column(Library.Like, prefix.toNode, om.column(new Library.SqlOperator("||"), n, LiteralNode("%")).toNode)
}
}
}
现在可以按如下方式使用:
import utils.Helpers._
[...]
def findActiveRateByRatePlanAndPartialPrefix(ratePlan: String, prefix: String) = {
DB withSession {
implicit session: Session =>
Rates.filter(_.ratePlanId === ratePlan)
.filter(_.present === true)
.filter(_.prefix subPrefixFor prefix)
.sortBy(_.prefix.desc).firstOption
}
}
并且会呈现正确的结果:
and ('3113212512' like (x2."prefix" || '%'))
备注:
- 我可能有更好的命名方式
- 必须链接 ||喜欢使用 om.column
- 我引入了新的 SqlOperator,因为 Or 运算符呈现 or 并且我需要在 Postgres 中连接 ||
您可以简单地以受支持的样式重写您的查询。我认为这应该是等价的:
.filter(s => (s.prefix === "") || s.prefix.isEmpty || LiteralColumn(prefix) like s.prefix)
如果条件比较复杂,需要Case表达式,见http://slick.typesafe.com/doc/2.1.0/sql-to-slick.html#case
如果你还想要 ||运算符,您可以这样定义它:
/** SQL || for String (currently collides with Column[Boolean] || so other name) */
val thisOrThat = SimpleBinaryOperator[String]("||")
...
.filter(s => LiteralColumn(prefix) like thisOrThat(s.prefix,"%"))
虽然我没有尝试这个,但我看到我们没有针对 SimpleBinaryOperator 的测试。不行的话在github.com/slick/slick开工单。也许我们还应该将其更改为 return 类型化函数。我为那个 https://github.com/slick/slick/issues/1073 添加了一张票。
另见 http://slick.typesafe.com/doc/2.1.0/userdefined.html#scalar-database-functions
幸运的是,这里甚至不需要 SimpleBinaryOperator。
我有以下 SQL 查询,我想将其映射到 Slick
SELECT * FROM rates
WHERE '3113212512' LIKE (prefix || '%') and present = true
ORDER BY prefix DESC
LIMIT 1;
但是没有为 String 定义 like 符号:
case class Rate(grid: String, prefix: String, present: Boolean)
class Rates(tag: Tag) extends Table[Rate](tag, "rates") {
def grid = column[String]("grid", O.PrimaryKey, O.NotNull)
def prefix = column[String]("prefix", O.NotNull)
def present = column[Boolean]("present", O.NotNull)
// Foreign keys
def ratePlan = foreignKey("rate_plan_fk", ratePlanId, RatePlans)(_.grid)
def ratePlanId = column[String]("rate_plan_id", O.NotNull)
def * = (grid, prefix, present) <>(Rate.tupled, Rate.unapply)
}
object Rates extends TableQuery(new Rates(_)) {
def findActiveRateByRatePlanAndPartialPrefix(ratePlan: String, prefix: String) = {
DB withSession {
implicit session: Session =>
Rates.filter(_.ratePlanId === ratePlan)
.filter(_.present === true)
.filter(prefix like _.prefix)
.sortBy(_.prefix.desc).firstOption
}
}
}
显然这样的事情是行不通的:
.filter(prefix like _.prefix)
并且:
.filter(_.prefix like prefix)
会呈现不正确的 SQL,我现在甚至都没有考虑“%”(只有与前缀有关的 WHERE 子句:
(x2."prefix" like '3113212512')
而不是:
'3113212512' LIKE (prefix || '%')
当然我可以使用静态查询来解决这个问题,但我想知道这是否可行?
为清楚起见,这里是数据库中的一些前缀
31
3113
312532623
31113212
预计结果为 31113212
更新 2
实际上不需要concat,也可以这样写:
filter(s => LiteralColumn(prefix) like (s.prefix ++ "%"))
更新:
经过 cvogt 的提示,这是没有使用内部 Slick 的最终结果 APIs:
val concat = SimpleBinaryOperator[String]("||")
Rates.filter(_.ratePlanId === ratePlan)
.filter(_.present === true)
.filter(s => LiteralColumn(prefix) like concat(s.prefix, "%"))
.sortBy(_.prefix.desc).firstOption
完美给出:
and ('3113212512' like (x2."prefix" || '%'))
选择:
这似乎是可能的。但是请注意,以下方法使用 Slick 的内部 API,由于 API 更改,将来可能不兼容(请参阅 cvogt 的评论)我使用 Slick 2.1.0.
基本上我创建了一个 Slick 扩展,我在其中定义了一个自定义方案来实现所需的结果:
package utils
import scala.language.{higherKinds, implicitConversions}
import scala.slick.ast.ScalaBaseType._
import scala.slick.ast._
import scala.slick.lifted.{Column, ExtensionMethods}
object Helpers {
implicit class PrefixExtensionMethods[P1](val c: scala.slick.lifted.Column[P1]) extends AnyVal with ExtensionMethods[String, P1] {
def subPrefixFor[P2, R](prefix: Column[P2])(implicit om: o#arg[String, P2]#to[Boolean, R]) = {
om.column(Library.Like, prefix.toNode, om.column(new Library.SqlOperator("||"), n, LiteralNode("%")).toNode)
}
}
}
现在可以按如下方式使用:
import utils.Helpers._
[...]
def findActiveRateByRatePlanAndPartialPrefix(ratePlan: String, prefix: String) = {
DB withSession {
implicit session: Session =>
Rates.filter(_.ratePlanId === ratePlan)
.filter(_.present === true)
.filter(_.prefix subPrefixFor prefix)
.sortBy(_.prefix.desc).firstOption
}
}
并且会呈现正确的结果:
and ('3113212512' like (x2."prefix" || '%'))
备注:
- 我可能有更好的命名方式
- 必须链接 ||喜欢使用 om.column
- 我引入了新的 SqlOperator,因为 Or 运算符呈现 or 并且我需要在 Postgres 中连接 ||
您可以简单地以受支持的样式重写您的查询。我认为这应该是等价的:
.filter(s => (s.prefix === "") || s.prefix.isEmpty || LiteralColumn(prefix) like s.prefix)
如果条件比较复杂,需要Case表达式,见http://slick.typesafe.com/doc/2.1.0/sql-to-slick.html#case
如果你还想要 ||运算符,您可以这样定义它:
/** SQL || for String (currently collides with Column[Boolean] || so other name) */
val thisOrThat = SimpleBinaryOperator[String]("||")
...
.filter(s => LiteralColumn(prefix) like thisOrThat(s.prefix,"%"))
虽然我没有尝试这个,但我看到我们没有针对 SimpleBinaryOperator 的测试。不行的话在github.com/slick/slick开工单。也许我们还应该将其更改为 return 类型化函数。我为那个 https://github.com/slick/slick/issues/1073 添加了一张票。
另见 http://slick.typesafe.com/doc/2.1.0/userdefined.html#scalar-database-functions
幸运的是,这里甚至不需要 SimpleBinaryOperator。