【问题标题】:Slick: Difficulties working with Column[Int] valuesSlick:使用 Column[Int] 值的困难
【发布时间】:2013-07-15 19:26:35
【问题描述】:

我最近在此处提出了另一个 Slick 问题 (Slick table Query: Trouble with recognizing values)。请多多包涵!!我是数据库新手,Slick 在文档方面似乎特别差。不管怎样,我有这张桌子:

object Users extends Table[(Int, String)]("Users") {

  def userId          = column[Int]("UserId", O.PrimaryKey, O.AutoInc)
  def userName        = column[String]("UserName")

  def * = userId ~ userName
}

第一部分

我正在尝试使用此函数进行查询:

def findByQuery(where: List[(String, String)]) = SlickInit.dbSlave withSession {    

  val q = for {
    x <- Users if foo((x.userId, x.userName), where)
           } yield x
      q.firstOption.map { case(userId, userName) =>
                    User(userId, userName)}
   }

其中“where”是搜索查询列表 //ex. ("userId", "1"),("userName", "Alex") “foo”是一个测试相等性的辅助函数。我遇到了类型错误。
x.userId 的类型为 Column[Int]。如何将其作为 Int 进行操作?我试过铸造,例如:

foo(x.userId.asInstanceOf[Int]...)

但我也遇到了麻烦。如何处理 Slick 返回类型?

第二部分 有谁熟悉铸造功能:

def * = userId ~ userName (User, User.unapply _)

?我知道这个问题有一些很好的答案,最值得注意的是:scala slick method I can not understand so far 和一个非常相似的问题:mapped projection with companion object in SLICK。但是任何人都可以解释为什么编译器会响应

<> method overloaded 

为了那一行简单的代码?

【问题讨论】:

    标签: database scala slick scalatra


    【解决方案1】:

    让我们从问题开始:

    val q = for {
        x <- Users if foo((x.userId, x.userName), where)
    } yield x
    

    请看,Slick 将 Scala 表达式转换为 SQL。为了能够根据需要将条件转换为 SQL 语句,Slick 需要使用一些特殊类型。这些类型的工作方式实际上是 Slick 执行的转换的一部分。

    例如,当您编写List(1,2,3) filter { x =&gt; x == 2 } 时,将对列表中的每个元素执行过滤谓词。但是 Slick 做不到!所以 Query[ATable] filter { arow => arow.id === 2 } 实际上意味着“使用条件 id = 2 进行选择”(我在这里跳过细节)。

    我为您的 foo 函数编写了一个模拟,并要求 Slick 为查询 q 生成 SQL:

    select x2."UserId", x2."UserName" from "Users" x2 where false
    

    看到false了吗?这是因为foo 是一个简单的谓词,Scala 将其计算为布尔值false。在查询中完成的类似谓词,而不是列表,评估为对 SQL 生成中需要完成的内容的描述。比较 List 和 Slick 中 filters 的区别:

    List[A].filter(A => Boolean):List[A]
    Query[E,U].filter[T](f: E => T)(implicit wt: CanBeQueryCondition[T]):Query[E,U]  
    

    列表过滤器评估为 As 列表,而 Query.filter 评估为新 Query!

    现在,迈向解决方案的一步。

    看来你要的其实是SQL的in操作符。如果列表中有元素,in 运算符返回 true,例如:4 in (1,2,3,4) 为 true。请注意,(1,2,3,4) 是一个 SQL 列表,而不是像 Scala 中的 Tuple

    对于in SQL 运算符的这个用例,Slick 使用运算符inSet

    现在是问题的第二部分。 (我将where变量重命名为list,因为where是一个Slick方法)

    你可以试试:

    val q = for {
      x <- Users if (x.userId,x.userName) inSet list
    } yield x
    

    但这不会编译!那是因为 SQL 不像 Scala 那样拥有元组。在 SQL 中你不能这样做 (1,"Alfred") in ((1,"Alfred"),(2, "Mary")) (请记住,(x,y,z) 是列表的 SQL 语法,我在这里滥用语法只是为了表明它是无效的——而且那里有很多 SQL 方言,它是可能其中一些确实以类似的方式支持元组和列表。)

    一种可能的解决方案是仅使用 userId 字段:

    val q = for {
      x <- Users if x.userId inSet list2
    } yield x
    

    这会生成select x2."UserId", x2."UserName" from "Users" x2 where x2."UserId" in (1, 2, 3)

    但由于您明确使用用户 ID 和用户名,因此可以合理地假设用户 ID 不能唯一标识用户。因此,要修改我们可以连接两个值。当然,我们需要在列表中做同样的事情。

    val list2 = list map { t => t._1 + t._2 }
    val q2 = for {
      x <- Users if (x.userId.asColumnOf[String] ++ x.userName) inSet list2
    } yield x
    

    查看生成的 SQL:

    select x2."UserId", x2."UserName" from "Users" x2 
    where (cast(x2."UserId" as VARCHAR)||x2."UserName") in ('1a', '3c', '2b')
    

    看到上面的||了吗?它是 H2Db 中使用的字符串连接运算符。 H2Db 是我用来运行您的示例的 Slick 驱动程序。此查询和其他查询可能会因您使用的数据库而略有不同。

    希望它能阐明 slick 的工作原理并解决您的问题。至少是第一个。 :)

    【讨论】:

    • 谢谢佩德罗弗拉!这是一个很好的答案。您是否碰巧知道任何接近描述您刚刚描述的内容的 Slick 文档?我发现大多数 Slick 文档都非常缺乏
    • 嗯,我从 ScalaQuery 开始就一直在关注它,所以即使在 Slick 出现之前,我也或多或少地熟悉它的概念。 Daniel Spiewak 论文的一个来源可能是 ScalaQL,他在其中描述了其背后的原理。
    • 我还要非常感谢您。对于其他任何被此抓住的人,请注意 ++ 运算符!如果您尝试使用(x.userId + x.userName).asColumnOf[String],您最终会得到类似于:(cast('(x Path @1009).userId(x Path @1009).userName' as VARCHAR) 的 SQL。不是你想要的。
    【解决方案2】:

    第一部分:

    Slick 使用 Column[...] 类型而不是普通的 Scala 类型。您还需要使用 Column 类型定义 Slick 辅​​助函数。你可以像这样实现 foo:

    def foo( columns: (Column[Int],Column[String]), values: List[(Int,String)] ) : Column[Boolean] = values.map( value =&gt; columns._1 === value._1 &amp;&amp; columns._2 === value._2 ).reduce( _ || _ )

    另请阅读 pedrofurla 的回答,以更好地了解 Slick 的工作原理。

    第二部分:

    方法 确实是重载的,当类型不正确时,Scala 编译器很容易不确定它应该使用哪个重载。 (我认为我们应该摆脱 Slick 中的重载。)写作

    def * = userId ~ userName &lt;&gt; (User.tupled _, User.unapply _)

    可能会稍微改善您收到的错误消息。要解决此问题,请确保 userId 和 userName 的 Column 类型与您的用户案例类的成员类型完全对应,应该类似于 case class User( id:Int, name:String )。还要确保在映射到 User 时扩展 Table[User](而不是 Table[(Int,String)])。

    【讨论】:

    • 嗨 cvogt,通过 User.tupled _ 方法,您是指描述如何使用元组创建新用户的自定义方法吗?
    • 他的意思是(User apply _).tupled _,它把这个方法变成了一个接受一个元组并返回一个用户的函数。但在这种情况下,我想只有User apply _ 就足够了。
    猜你喜欢
    • 2021-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-06
    相关资源
    最近更新 更多