【问题标题】:Slick: LIKE query with column as parameterSlick:以列为参数的 LIKE 查询
【发布时间】:2015-02-11 12:30:31
【问题描述】:

我有以下 SQL 查询,我想将其映射到 Slick

SELECT * FROM   rates
WHERE  '3113212512' LIKE (prefix || '%') and  present = true
ORDER  BY prefix DESC
LIMIT  1;

但是没有为String定义类似的符号:

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

【问题讨论】:

  • 可以添加生成的SQL吗?
  • 我已经更新了问题
  • 也许我没听懂,但是这两个有什么不同(除了第二个中的% 部分)?您是否将字符串与列匹配,或者结果的其他方式将是相同的,或者我错过了什么?或者您的 prefix 列值中可能已经有一个通配符,在这种情况下,这一切都是有意义的。

标签: postgresql scala slick


【解决方案1】:

您可以简单地以受支持的样式重写您的查询。我认为这应该是等效的:

.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 上打开一张票。也许我们也应该改变它以返回一个类型化的函数。我为那个https://github.com/slick/slick/issues/1073添加了一张票。

另见http://slick.typesafe.com/doc/2.1.0/userdefined.html#scalar-database-functions

幸运的是,这里甚至不需要 SimpleBinaryOperator。

【讨论】:

  • 好吧,String 仍然没有 'like',所以 'prefix like s.prefix' 或 '(s => prefix like' 仍然不起作用。
  • 您可能必须强制转换并编写 LiteralColumn(prefix).like(...)。如果这不起作用,我目前可能会误解我们 like 运算符的适用性。
  • 我真的需要 ||运算符在 postgres 这意味着连接,所以正如你提到的 thisOrThat,这有效: .filter(s => LiteralColumn(prefix) like thisOrThat(s.prefix, "%")) 产生和 ('3113212512' like (x2. "前缀" || '%'))
  • 我也是。是的,在某些情况下,隐式转换不会自动启动,您必须手动触发它
  • 其实我可以使用 ++ 运算符进行连接: .filter(s => LiteralColumn(prefix) like (s.prefix ++ "%"))
【解决方案2】:

更新 2

其实不需要concat,也可以这样写:

filter(s => LiteralColumn(prefix) like (s.prefix ++ "%"))

更新:

在 cvogt 提示后,这是不使用内部 Slick API 的最终结果:

        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" || '%'))

注意事项:

  1. 我可能有更好的命名方式
  2. 不得不链接||并且喜欢使用 om.column
  3. 我引入了新的 SqlOperator,因为 Or 运算符呈现 or 并且我需要在 Postgres 中连接 ||

【讨论】:

  • 这项工作,但需要深入了解光滑和不太稳定的内部 API 的使用。所以如果你能避免它,让我们尽量留在 slick 的公共 api 上。
猜你喜欢
  • 2023-02-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-02
  • 1970-01-01
  • 2014-05-13
  • 1970-01-01
  • 2010-10-14
相关资源
最近更新 更多