光滑:使用Column [Int]值的困难

时间:2013-07-15 19:26:36

标签: database scala slick scalatra

我对此处最近提出的另一个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&lt;&gt; (User,User.unapply _)

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

响应
<> method overloaded 

对于那个简单的代码行?

2 个答案:

答案 0 :(得分:6)

让我们从问题开始:

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 => x == 2 }时,将对列表中的每个元素执行过滤谓词。但是Slick不能那样做!所以查询[ATable]过滤器{arow =&gt; arow.id === 2}实际上意味着“使用条件id = 2进行选择”(我在此处跳过详细信息)。

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

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

请参阅false?这是因为foo是Scala评估为布尔false的简单谓词。在Query中而不是列表中执行的类似谓词评估为在SQL生成中需要完成的操作的描述。比较List和Slick中的filter之间的差异:

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)为真。请注意,(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驱动程序。此查询和其他查询可能会略有不同,具体取决于您使用的数据库。

希望它能说明光滑如何工作并解决您的问题。至少第一个。 :)

答案 1 :(得分:1)

第一部分:

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

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

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

第二部分:

方法&lt;&gt;确实是重载的,当类型不能解决时,Scala编译器很容易变得不确定它应该使用哪个重载。 (我认为应该摆脱Slick中的超载。)写作

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

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