【问题标题】:Recursive tree-like table query with Slick使用 Slick 的递归树状表查询
【发布时间】:2013-02-25 15:38:17
【问题描述】:

我的表数据形成了一个树形结构,其中一行可以引用同一个表中的父行。

我想要使用 Slick 实现的目标是编写一个查询,该查询将返回一行及其所有子项。另外,我也想做同样的事情,但要编写一个查询,该查询将返回一个孩子及其所有祖先。

换句话说:

findDown(1) 应该返回

List(Group(1, 0, "1"), Group(3, 1, "3 (Child of 1)"))

findUp(5) 应该返回

List(Group(5, 2, "5 (Child of 2)"), Group(2, 0, "2"))

这是一个功能齐全的工作表(缺少的解决方案除外 ;-)。

package com.exp.worksheets

import scala.slick.driver.H2Driver.simple._

object ParentChildTreeLookup {

  implicit val session = Database.forURL("jdbc:h2:mem:test1;", driver = "org.h2.Driver").createSession()

  session.withTransaction {
    Groups.ddl.create
  }

  Groups.insertAll(
    Group(1, 0, "1"),
    Group(2, 0, "2"),
    Group(3, 1, "3 (Child of 1)"),
    Group(4, 3, "4 (Child of 3)"),
    Group(5, 2, "5 (Child of 2)"),
    Group(6, 2, "6 (Child of 2)"))

  case class Group(
    id: Long = -1,
    id_parent: Long = -1,
    label: String = "")

  object Groups extends Table[Group]("GROUPS") {
    def id = column[Long]("ID", O.PrimaryKey, O.AutoInc)
    def id_parent = column[Long]("ID_PARENT")
    def label = column[String]("LABEL")
    def * = id ~ id_parent ~ label <> (Group, Group.unapply _)
    def autoInc = id_parent ~ label returning id into {
      case ((_, _), id) => id
    }

    def findDown(groupId: Long)(implicit session: Session) = { ??? }

    def findUp(groupId: Long)(implicit session: Session) = { ??? }
  }

}

findDown 的一个非常糟糕的静态尝试可能类似于:

private def groupsById = for {
  group_id <- Parameters[Long]
  g <- Groups; if g.id === group_id
} yield g

private def childrenByParentId = for {
  parent_id <- Parameters[Long]
  g <- Groups; if g.id_parent === parent_id
} yield g


def findDown(groupId: Long)(implicit session: Session) = { groupsById(groupId).list union childrenByParentId(groupId).list }

但是,我正在寻找一种让 Slick 使用 id 和 id_parent 链接递归搜索同一个表的方法。任何其他解决问题的好方法都非常受欢迎。但请记住,最好尽量减少数据库往返次数。

【问题讨论】:

  • 你将如何在 SQL 中做到这一点?

标签: scala slick


【解决方案1】:

您可以尝试从 slick 调用 SQL。用于向上层次结构的 SQL 调用看起来像这样(这是用于 SQL Server):

WITH org_name AS 
(
    SELECT DISTINCT
        parent.id AS parent_id,
        parentname.label as parent_label,
        child.id AS child_id,
        childname.ConceptName as child_label
    FROM
        Group parent RIGHT OUTER JOIN 
        Group child ON child.parent_id = parent.id
), 
jn AS 
(   
    SELECT
        parent_id,
        parent_label,
        child_id,
        child_label
    FROM
        org_name 
    WHERE
        parent_id = 5
    UNION ALL 
        SELECT
            C.parent_id,
            C.parent_label,
            C.child_id,
            C.child_label 
        FROM
            jn AS p JOIN 
            org_name AS C ON C.child_id = p.parent_id
) 
SELECT DISTINCT
    jn.parent_id,
    jn.parent_label,
    jn.child_id,
    jn.child_label
FROM
    jn 
ORDER BY
    1;

如果你想向下层级更改行:

org_name AS C ON C.child_id = p.parent_id

到

org_name AS C ON C.parent_id = p.child_id

【讨论】:

    【解决方案2】:

    在普通的 SQL 中,这会很棘手。您将有多种选择:

    1. 使用存储过程收集正确的记录(递归)。然后使用代码将这些记录转换为树
    2. 选择所有记录并使用代码将它们转换为树
    3. 使用here(来自Optimized SQL for tree structures)和here 中描述的更高级的技术。然后使用代码将这些记录转换为树

    根据您希望在 SQL 中执行此操作的方式,您需要构建一个 Slick 查询。 Leaky Abstractions 的概念在这里非常明显。

    所以得到树形结构需要两步:

    1. 获取正确的(或所有记录)
    2. 根据这些记录构建(使用常规代码)一棵树

    由于您使用的是 Slick,我认为这不是一个选项,但另一种数据库类型可能更适合您的数据模型。查看NoSQL 了解不同类型之间的差异。

    【讨论】:

    • 谢谢。过去我在数据库端使用存储过程完成了这种类型的事情,但是我这里的树的深度通常很浅,尽管记录集可能会变得非常大。您猜测它最终将成为代码端解决方案可能是正确的,并且 NoSQL 不是该项目的选项。我只是希望 Slick 用户找到了解决此问题的最佳方式或良好模式,并且与其他 Slick 查询(考虑可组合性等)非常吻合。
    • 递归 SQL / 分层查询并不是 SQL 数据库无法做到的。例如,H2 支持带有递归 CTE 的 SQL 标准,所以问题实际上是关于如何在 Slick 中表达递归 CTE。
    • 感谢 Lukas - CTE 似乎是要走的路,它也在 PosgreSQL postgresql.org/docs/current/interactive/queries-with.html 中得到支持。太好了,谢谢。我试试看。
    猜你喜欢
    • 2018-08-08
    • 1970-01-01
    • 2016-09-29
    • 2012-05-25
    • 1970-01-01
    • 1970-01-01
    • 2021-05-10
    • 1970-01-01
    相关资源
    最近更新 更多