使用Option进行左/右/外连接的Slick

17
在Slick示例中,有一些关于连接操作的例子,其中一个结果列可能包含空值,这在执行左连接、右连接或外连接时可能会发生。例如:
val explicitLeftOuterJoin = for {
  (c, s) <- Coffees leftJoin Suppliers on (_.supID === _.id)
} yield (c.name, s.name.?)

但如果我想返回整个映射对象呢?我的意思是:

val explicitLeftOuterJoin = for {
  (c, s) <- Coffees leftJoin Suppliers on (_.supID === _.id)
} yield (c, s.?)

这似乎无法正常工作,因为它抱怨“找不到类型为scala.slick.lifted.TypeMapper [Suppliers]的隐式参数证明”。基本上我想要它返回一个由(Coffee,Option [Supplier])元组组成的列表。

为什么它不能正常工作,有什么解决方法?特别是,因为这个可以正常工作:

val q = for {
  c <- Coffees
  s <- Suppliers
} yield (c, s)

我知道那是一个内连接


与stackoverflow.com/questions/14990365/相关的内容。 - cvogt
4个回答

9

更新:这个问题将在2014年底Slick 3.0版本中得到解决,不再需要以下的解决方法。

目前Slick存在这样一个限制:您必须对每一列单独调用.?方法。但是,您可以在表类中放置一个名为?的函数来集中处理,从而获得完整行的.?方法。这个play-slick示例代码包含一个涉及生成代码的通用解决方案。我们还有一个PR,添加了自动生成?方法

长期来看,我们将在Slick中支持外连接的变体,其中Slick完全了解所涉及的类型,您无需在任何地方指定.?方法。现在,我们必须使用涉及代码生成的解决方法。


1
你能给出一个适合表类的 ? 实现的例子吗? - Matt R
看看 Markus 的下一个答案:def ? = (col1.?, col2.?, col3.?) - Leonard Ehrenfried
cvogt,Slick 2.2有什么更新吗?看起来下一个版本是3.0。 - lloydmeta
1
2.2被更名为3.0。 - cvogt

5
这并不是最干净的解决方案(使用scalaz 7.0.6和shapeless 2.0.1),但现在可以使用它(Slick 2.0.1):
通过上面的 ? 投影,可以创建一个 Slick 投影,将 Option 值的元组转换为 Option[TupleN],然后转换为 Option[YourClass]

添加 option 投影

注意:sequence 用于将 Option 值的元组转换为 Option[TupleN]sequence 的代码定义在本答案的底部。
添加到 Suppliers。 假设 Supplier 是一个 case class。
  import scalaz._, Scalaz._
  import SequenceTupleOption._

  def option = (id.?, name.?, street.?) <> (optionApply, optionUnapply)
  def optionApply(t: (Option[Int], Option[String], Option[String])): Option[Comment] = {
    sequence(t).map(Supplier.tupled)
  }

  def optionUnapply(oc: Option[Supplier]): Option[(Option[Int], Option[String], Option[String])] = None

使用 option 投影

val explicitLeftOuterJoin = for {
  (c, s) <- Coffees leftJoin Suppliers on (_.supID === _.id)
} yield (c, s.option)

高级: sequence,将Option值的元组转换为Option[TupleN]

这是Travis Brown编写的较难部分(参见此处)。sequence可以使用scalaz和shapeless从一个Option值的元组转换为Option[TupleN]

import scalaz._, Scalaz._
import shapeless._, ops.hlist.{ RightFolder, Tupler }

object SequenceTupleOption {

  object applicativeFolder extends Poly2 {
    implicit def caseApplicative[A, B <: HList, F[_]](implicit
      app: Applicative[F]
    ) = at[F[A], F[B]] {
      (a, b) => app.ap(a)(app.map(b)(bb => (_: A) :: bb))
    }
  }

  def sequence[T, EL <: HList, L <: HList, OL <: HList, OT](t: T)(implicit
    gen: Generic.Aux[T, EL],
    eq: EL =:= L,
    folder: RightFolder.Aux[L, Option[HNil], applicativeFolder.type, Option[OL]],
    tupler: Tupler.Aux[OL, OT]
  ): Option[OT] =
    eq(gen.to(t)).foldRight(some(HNil: HNil))(applicativeFolder).map(tupler(_))

}

sequence 的使用方法:

import scalaz._, Scalaz._
import SequenceTupleOption._

case class Person(id: Int, name: String, age: Int)

val t = (Option(1), Option("Bob"), Option(40))

val person: Option[Person] = sequence(t).map(Person.tupled) // Some(Person(1,Bob,40))

sequence的高级概述(不涉及正确类型):

  1. Option元组转换为shapeless HList[Option[_]]
  2. HList [Option [_]]上进行sequence操作,得到一个Option[HList[_]]
  3. HList转换回元组。

2
除了上面的答案之外: 如果你有一个继承自Table的类,并且你的*投影看起来像这样:
def * = (col1, col2, col3)

如果您的函数需要比您的参数更多的信息,那么它可能会像这样:
def ? = (col1.?, col2.?, col3.?)

如果您已经定义了这样一个函数,您可以这样编写代码:

如果您已经定义这样的函数,您可以这样编写:

for {
    (x,y) <- x leftJoin y on (...)
} yield (x, y.?)

0
在Slick 3.1.1中,正确的答案仅是(如某些评论中所述):
for {
  (c, s) <- coffees joinLeft suppliers on (_.supID === _.id)
} yield (c, s)

这将为s生成一个Rep [Option [suppliers]],而且它是无法使用的。 - LiMuBei

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接