【问题标题】:scala slick one-to-many collectionsscala 光滑的一对多集合
【发布时间】:2014-08-28 11:02:44
【问题描述】:

我有一个数据库,其中包含具有一对多注册关系的活动。 目标是获取所有活动及其注册列表。

通过创建带有注册的活动的笛卡尔积,获取该数据所需的所有数据都在那里。 但我似乎找不到一个很好的方法将它正确地放入 scala 集合中; 让我们输入:Seq[(Activity, Seq[Registration])]

case class Registration(
  id: Option[Int],
  user: Int,
  activity: Int
)

case class Activity(
  id: Option[Int],
  what: String,
  when: DateTime,
  where: String,
  description: String,
  price: Double
)

假设存在适当的光滑表和表查询,我会写:

val acts_regs = (for {
   a <- Activities
   r <- Registrations if r.activityId === a.id
} yield (a, r))
  .groupBy(_._1.id)
  .map { case (actid, acts) => ??? }
}

但我似乎无法做出适当的映射。这样做的惯用方式是什么?我希望它比使用原始笛卡尔积更好...

在 Scala 中

在 scala 代码中很简单,看起来像这样:

  val activities = db withSession { implicit sess =>
    (for {
      a <- Activities leftJoin Registrations on (_.id === _.activityId)
    } yield a).list
  }

  activities
    .groupBy(_._1.id)
    .map { case (id, set) => (set(0)._1, set.map(_._2)) }

但这似乎相当低效,因为表映射器将为您创建不必要的 Activity 实例化。 看起来也不是很优雅……

获取注册计数

当只对这样的注册计数感兴趣时,in scala 方法更糟糕:

val result: Seq[Activity, Int] = ???

在光滑

我对 slick 的最佳尝试如下所示:

  val activities = db withSession { implicit sess =>
    (for {
      a <- Activities leftJoin Registrations on (_.id === _.activityId)
    } yield a)
      .groupBy(_._1.id)
      .map { case (id, results) => (results.map(_._1), results.length) }
  }

但这会导致 slick 无法在“map”行中映射给定类型的错误。

【问题讨论】:

  • count版本至少有简单路径described in the docs
  • 与文档的不同之处在于我对整个活动以及对其注册的聚合感兴趣。而且我不知道如何映射。你能帮忙吗?
  • 明确一点:我想要类似的东西:SELECT l.*, count(r.id) from activities l join registrations r where l.id = r.activityId;并将其映射到Seq[(Activity, Int)]
  • 刚刚阅读您的最后一条评论.. 不确定这是否可行,但您是否尝试过groupBy(_._1),然后应该将整个活动实体作为键吐出。
  • 另外.. 您最初的问题是询问活动和相关注册列表。我之前遇到过类似的问题,并通过对数据库进行两次调用来解决此要求,一次使用.head 进行投影,另一次使用.list 进行投影。我知道这会对数据库进行两次“调用”,但是因为我们有一个打开的会话/连接,所以实际开销非常小。如果您不介意将问题表达为两个调用(即更多代码),那么我认为您可以通过这种方式实现目标。

标签: scala slick


【解决方案1】:

我建议:

  val activities = db withSession { implicit sess =>
    (for {
      a <- Activities leftJoin Registrations on (_.id === _.activityId)
    } yield a)
      .groupBy(_._1)
      .map { case (activity, results) => (activity, results.length) }
  }

问题

  val activities = db withSession { implicit sess =>
    (for {
      a <- Activities leftJoin Registrations on (_.id === _.activityId)
    } yield a)
      .groupBy(_._1.id)
      .map { case (id, results) => (results.map(_._1), results.length) }
  }

是你不能在分组中产生嵌套结果。 results.map(_._1) 是项目的集合。在某些情况下,SQL 会进行从集合到单行的隐式转换,但 Slick 的类型安全却不会。您想在 Slick 中执行的操作类似于 results.map(_._1).head,但目前不支持。你能得到的最接近的是(results.map(_.id).max, results.map(_.what).max, ...),这很乏味。因此,按整个活动行分组可能是目前最可行的解决方法。

【讨论】:

  • 我真的以为我试过了。但它现在似乎确实有效。我认为多列上的 groupBy 给我带来了问题,因为我收到一个 SQLException 说它只需要一个操作数。但我想那是为了别的东西。
  • 我会接受这个答案(也感谢您对“为什么”它不起作用的洞察力),但它仅适用于您对聚合感兴趣的情况。如果我对整个注册序列感兴趣,groupBy 不起作用,那么您有什么建议?
  • 顺便说一句:我怀疑 SQL 所做的“隐式转换”正是这种情况令人讨厌的原因。 SQL 使它看起来如此简单......但我建议您在此处解释的行中的内容应该添加到文档中;文档涵盖了一个典型案例,但我必须进行大量实验才能找出可能的边界是什么;然后我的结论是错误的,因为其他地方有错误。
  • SQL 和 Slick 的 groupBy 不允许您返回嵌套集合。如果您需要,则需要进行外部联接或两个单独的查询,并使用普通的旧 scala 集合将它们带入客户端所需的形状。
  • 感谢您的完整回答。我的回答涵盖了在 2 个查询中获取每个活动的完整注册列表的一种可能方法。
【解决方案2】:

获取每个活动的所有注册的解决方案:

    // list of all activities
    val activities = Activities
    // map of registrations belonging to those activities
    val registrations = Registrations
      .filter(_.activityId in activities.map(_.id))
      .list
      .groupBy(_.activityId)
      .map { case (aid, group) => (aid, group.map(_._2)) }
      .toMap

    // combine them
    activities
      .list
      .map { a => (a, registrations.getOrElse(a.id.get, List()))

这可以在 2 个查询中完成工作。将这种类型的“分组”函数抽象为 scala 函数应该是可行的。

【讨论】:

  • 对于总体情况(注册数量),cvogt 的答案更可取。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-02-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多