简体   繁体   English

Slick:LIKE以列为参数的查询

[英]Slick: LIKE query with column as parameter

I have the following SQL query, which I'd like to map to Slick 我有以下SQL查询,我想将其映射到Slick

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

However the like symbol is not defined for String: 但是,没有为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
    }
  }
}

Obviously it's logical that something like this won't work: 显然,这样的事情不起作用是合乎逻辑的:

.filter(prefix like _.prefix)

And: 和:

.filter(_.prefix like prefix)

Will render incorrect SQL, and I'm not even considering the '%' right now (only WHERE clause concerning the prefix: 将呈现不正确的SQL,我现在甚至没有考虑'%'(只有关于前缀的WHERE子句:

(x2."prefix" like '3113212512')

Instead of: 代替:

'3113212512' LIKE (prefix || '%')

Of course I can solve this using a static query, but I'd like to know whether this would be possible at all? 当然我可以使用静态查询解决这个问题,但我想知道这是否可行?

For clarity, here are some prefixes in the database 为清楚起见,这里是数据库中的一些前缀

31
3113
312532623
31113212

And 31113212 is expected as a result 结果预计会有31113212

You could simply rewrite your query in a style that is supported. 您可以简单地以支持的样式重写查询。 I think this should be equivalent: 我认为这应该是等价的:

.filter(s => (s.prefix === "") || s.prefix.isEmpty || LiteralColumn(prefix) like s.prefix)

If the conditions are more complex and you need a Case expression, see http://slick.typesafe.com/doc/2.1.0/sql-to-slick.html#case 如果条件更复杂且需要Case表达式,请参阅http://slick.typesafe.com/doc/2.1.0/sql-to-slick.html#case

If you still want the || 如果你还想要|| operator you may be able to define it this way: 运算符您可以这样定义它:

/** SQL || for String (currently collides with Column[Boolean] || so other name) */
val thisOrThat = SimpleBinaryOperator[String]("||") 

... ...

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

I didn't try this though and I saw we don't have a test for SimpleBinaryOperator. 我没有试过这个,但我看到我们没有测试SimpleBinaryOperator。 Open a ticket on github.com/slick/slick if it doesn't work. 如果不起作用,请在github.com/slick/slick上打开一张票。 Probably we should also change it to return a typed function. 可能我们也应该改变它以返回一个类型化的函数。 I added a ticket for that https://github.com/slick/slick/issues/1073 . 我为https://github.com/slick/slick/issues/1073添加了一张票。

Also see http://slick.typesafe.com/doc/2.1.0/userdefined.html#scalar-database-functions 另请参阅http://slick.typesafe.com/doc/2.1.0/userdefined.html#scalar-database-functions

Luckily SimpleBinaryOperator is not even needed here. 幸运的是,这里甚至不需要SimpleBinaryOperator。

UPDATE 2 更新2

One actually does not require the concat, as it can be written as follows as well: 实际上一个不需要concat,因为它也可以写成如下:

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

UPDATE: 更新:

After hints by cvogt this is the final result without using internal Slick APIs: 在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

Which perfectly gives: 完美地给出了:

and ('3113212512' like (x2."prefix" || '%'))

Alternative: 替代方案:

This seems to possible. 这似乎是可能的。 Note however that the following method uses the Internal API of Slick and may not be compatible in the future due to API changes (see comment by cvogt) I used Slick 2.1.0. 但请注意,以下方法使用Slick的内部API,并且由于API更改,将来可能不兼容(请参阅cvogt的评论)我使用了Slick 2.1.0。

Basically I created an extension to Slick in which I define a custom scheme to implement the required result: 基本上我创建了一个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)
    }
  }
}

This can now be used as follows: 现在可以按如下方式使用:

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 will render correct result: 并将呈现正确的结果:

and ('3113212512' like (x2."prefix" || '%'))

Notes: 笔记:

  1. I could probably have some better naming 我可能有一些更好的命名
  2. Had to chain the || 不得不链接|| and Like using om.column 和使用om.column一样
  3. I introduced new SqlOperator since Or operator renders or and I need concatenation in Postgres || 我引入了新的SqlOperator,因为Or运算符渲染或者我需要在Postgres中进行连接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM