首页 文章

Slick:LIKE以列为参数的查询

提问于
浏览
4

我有以下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 回答

  • 2

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

    .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 .

  • 0

    UPDATE 2

    实际上一个不需要concat,因为它也可以写成如下:

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

    UPDATE:

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

    Alternative:

    这似乎是可能的 . 但请注意,以下方法使用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运算符渲染或者我需要在Postgres中连接||

相关问题