我对此处最近提出的另一个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
对于那个简单的代码行?
答案 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)])。