带有选项的光滑左/右/外连接

Posted

技术标签:

【中文标题】带有选项的光滑左/右/外连接【英文标题】:Slick left/right/outer joins with Option 【发布时间】:2013-12-21 14:27:12 【问题描述】:

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

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

【问题讨论】:

与***.com/questions/14990365/相关 【参考方案1】:

更新: 这将在 2014 年底得到解决并在 Slick 3.0 中正常工作,不再需要以下解决方法

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

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

【讨论】:

你能举一个在表类上合适的?实现的例子吗? 看看 Markus 的下一个答案:def ? = (col1.?, col2.?, col3.?) cvogt,Slick 2.2 是否有任何更新?看起来下一个版本是 3.0 2.2 重命名为 3.0【参考方案2】:

不是最干净的解决方案(使用 scalaz 7.0.6 和 shapeless 2.0.1),但目前可行(Slick 2.0.1):

使用上面的 ? 投影,可以创建一个转换 Option 值的元组的 Slick 投影 => Option[TupleN] => 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 和 shapeless)。

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 所做工作的高级概述(不是正确的类型):

    Option 的元组转换为无形的HList[Option[_]]sequenceHList[Option[_]]Option[HList[_]]HList 转换回元组。

【讨论】:

【参考方案3】:

除了上面的答案: 如果您有一个扩展 Table 的类并且您的 * 投影看起来像这样:

def * = (col1, col2, col3)

比你的?函数看起来像:

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

如果你定义了这样的函数,你可以写:

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

【讨论】:

【参考方案4】:

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

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

【讨论】:

这会为s 生成一个Rep[Option[suppliers]],而且无法使用。

以上是关于带有选项的光滑左/右/外连接的主要内容,如果未能解决你的问题,请参考以下文章

SQL中的左连接与右连接,内连接有啥区别

图解MySQL 内连接外连接左连接右连接全连接

sql左连接 右连接 内连接 外连接都是啥

左外连接和右外连接的区别

Oracle左连接右连接全外连接以及(+)号用法

Oracle左连接右连接全外连接以及(+)号用法