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

时间:2013-12-04 21:40:25

标签: scala slick

在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.?)

这似乎不起作用,因为它抱怨&#34;找不到scala.slick.lifted.TypeMapper [供应商]&#34;类型的证据参数的隐含值。基本上我希望它返回一个元组列表(咖啡,选项[供应商])

为什么这不起作用以及它的修复方法是什么?特别是,因为这很好:

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

(我知道这是一个内部联接)

4 个答案:

答案 0 :(得分:8)

更新: 这将解决,只需在2014年底的Slick 3.0中使用,不再需要以下解决方法

目前这是Slick的限制。你得打电话。?在每一列上单独。但是,您可以在表类中放置一个名为?的函数,在一个中心位置执行此操作,从而获得。在完整的行上。此play-slick example code包含涉及某些生成代码的通用解决方案。我们还有一个PR that adds auto-generation of a ? method排队。

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

答案 1 :(得分:5)

不是最干净的解决方案(使用scalaz 7.0.6和无形2.0.1),但现在可以使用(Slick 2.0.1):

使用上面的?投影,可以创建一个转换Option值=&gt;元组的Slick投影。 Option[TupleN] =&gt; Option[YourClass]

添加option投影

注意: sequence用于将Option值的元组转换为Option[TupleN]sequence的代码定义在此答案的底部。

添加到Suppliers。假设Supplier是一个案例类。

  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 wrote的难点。 sequenceOption元组的值转换为Option[TupleN](使用scalaz和无形)。

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的元组转换为无形HList[Option[_]]
  2. sequence越过HList[Option[_]]Option[HList[_]]
  3. HList转换回元组。

答案 2 :(得分:2)

除上述答案外: 如果你有一个扩展Table的类,你的*投影看起来像这样:

def * = (col1, col2, col3)
比你的?功能看起来像:

def ? = (col1.?, col2.?, col3.?)

如果您已经定义了这样的功能,您可以写:

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

答案 3 :(得分:0)

在Slick 3.1.1中,正确的答案很简单(如某些评论所述):

for {
  (c, s) <- coffees joinLeft suppliers on (_.supID === _.id)
} yield (c, s)