在自引用查询中使用 MySQL 创建串联父列

Creating Concatinated parent column with MySQL on self referencing query

我正在尝试使用文档集合以及表示基于其父文档的路径的字段来填充 ElasticSearch。

这是我的 table 布局:

+----+--------+-------+----------+
| Id | Parent | Alias | Contents |
+----+--------+-------+----------+
| 1  | null   | Doc1  | Admin    |
| 2  | 1      | Doc2  | Use      |
| 3  | 2      | Doc3  | Test     |
| 4  | 3      | Doc4  | Ask      |
| 5  | null   | PDF1  | Intro    |
| 6  | 5      | PDF2  | Managers |
+----+--------+-------+----------+

这是所需的输出

+----+--------+-------+----------+---------------------+
| Id | Parent | Alias | Contents | Path                |
+----+--------+-------+----------+---------------------+
| 1  | null   | Doc1  | Admin    | Doc1                |
| 2  | 1      | Doc2  | Use      | Doc1\Doc2           | 
| 3  | 2      | Doc3  | Test     | Doc1\Doc2\Doc3      |
| 4  | 3      | Doc4  | Ask      | Doc1\Doc2\Doc3\Doc4 |
| 5  | null   | PDF1  | Intro    | PDF1                |
| 6  | 5      | PDF2  | Managers | PDF1\PDF2           |
+----+--------+-------+----------+---------------------+

我有这个查询获取由参数@child 指定的一个文档的路径; (又名 SET @child = 5;

SELECT 
    T2.*
FROM
    (SELECT 
        @r AS _id,
            (SELECT 
                    @r:=Parent
                FROM
                    documents
                WHERE
                    id = _id) AS ParentId,
            @l:=@l + 1 AS lvl
    FROM
        (SELECT @r:=@child, @l:=@parent) vars, documents
    WHERE
        @r <> 0) T1
        JOIN
    documents T2 ON T1._id = T2.Id
ORDER BY T2.Parent

问题是,如果我将它放入子查询中,我该如何设置@child?我试过 GROUP_CONCAT() 但它总是最终成为每一行的相同路径。我尝试将当前行的 ID 放入子查询中,但它会引发错误:ErrorCode: 1109. Unknown table 'doc' in field list in the following query

SELECT doc.*, (
    SELECT GROUP_CONCAT(a.Alias) FROM (SELECT 
        T2.*
    FROM
        (SELECT 
            @r AS _id,
                (SELECT 
                        @r:=Parent
                    FROM
                        documents
                    WHERE
                        id = _id) AS ParentId,
                @l:=@l + 1 AS lvl
        FROM
            (SELECT @r:= doc.Id, @l:=@parent) vars, documents
        WHERE
            @r <> 0) T1
            JOIN
        documents T2 ON T1._id = T2.Id
    ORDER BY T1.lvl DESC) a
) as Path FROM documents doc

我做错了什么?有没有我没有看到的更好的方法?

虽然这并不完全相关,但我要指出的是,我正在使用 logstash 脚本按计划将文档从我的数据库加载到 ElasticSearch 中。同样出于多样性的考虑,我删除了大部分栏目和内容,并替换为虚假内容。

我创建了一个不错的解决方案。它不是非常快,但这也是意料之中的,因为这只是一天一次的负载,目前是可以接受的。

本质上,我创建了一个基于 id 获取路径的函数,然后只是 运行 一个视图(在推送到生产时使用人造物化视图以更快地加载到 logstash(基本上避免超时)) 选择所有值,然后选择相应行的路径。

CREATE FUNCTION `get_parent_path` (child int)
RETURNS VARCHAR(1024)
BEGIN
    DECLARE path varchar(1024);
    SELECT 
        GROUP_CONCAT(a.Alias)
    INTO
        path
    FROM (
        SELECT 
            T2.*
        FROM
            (
                SELECT 
                    @r AS _id
                    (
                        SELECT 
                            @r := Parent
                        FROM
                            documents
                        WHERE
                            id = _id
                    ) as ParentId,
                    @l: = @l + 1 as lvl
                FROM
                    (SELECT @r := child, @l := @parent) vars, documents
                WHERE
                    @r <> 0
                ) T1
        JOIN
            documents T2
        ON
            T1._id = T2.Id
        ORDER BY T2.Id
    ) a;

RETURN COALESCE(path, 'invalid child');
END

然后是我创建的视图:

CREATE VIEW 'documentswithpath' AS
SELECT *, get_parent_path(Id) FROM documents;

然后我只是 运行 SELECT * FROM documentswithpath; 来自 logstash 脚本。这也排除了 logstash 的许多逻辑以获得简单的答案。如果有人有更好的,最好更快的方法,请告诉我!谢谢

您收到错误消息是因为您不能在派生 table 中使用外部变量。派生的 table 基本上是每个 "subquery",您 必须 使用别名,例如 vars 在您的情况下。尝试删除该别名,MySQL 会告诉您每个 派生的 table 都必须有一个别名。

解决这个问题的一种方法是将整个查询移动到一个函数中,例如getpath(child_id int),然后您可以在任何地方自由使用此变量(假设您有一个工作查询可以获取特定 child、"something with GROUP_CONCAT()" 的路径)。

但在你的情况下,实际上可以重新组织你的代码,这样你就不需要派生的 table:

select d.*, t3.path 
from (
  SELECT t1.id, 
    group_concat(t2.alias order by t1.rownum desc separator '\' ) as path
  from (
    SELECT 
      current_child.id, 
      lvls.rownum, 
      @r := if(lvls.rownum = 1, current_child.id, @r) AS _id,
      (SELECT @r:=Parent
       FROM documents
       WHERE id = _id) AS ParentId
    FROM (select @rownum:= @rownum+1 as rownum 
       from documents, -- maybe add limit 5
       (select @rownum := 0) vars
      ) as lvls 
      -- or use: 
      -- (select 1 as rownum union select 2 union select 3 
      -- union select 4 union select 5) as lvls
      straight_join documents as current_child 
  ) as t1
  join documents t2
  on t2.id = t1._id
  group by t1.id
) t3
join documents d
on d.id = t3.id;

我用了你里面的documents和你一样,其实效率很低,只用来支持无限树深。如果你知道你的最大依赖级别,你可以使用替代代码 lvls 我添加为评论(这只是一个数字列表)或 limit.

确保将 group_concat_max_len 设置设置为适当的值(例如 set session group_concat_max_len = 20000;)。默认情况下,它支持 1024 的长度,这通常就足够了,但是对于长别名或非常深的树,您可能会达到它 - 由于它既不会给您错误也不会给您警告,因此有时很难诊断,所以请注意。

有更直接的方法可以解决您的问题。虽然它要求您知道树的最大深度,但如果您知道,您只需将 parents 加入每个 child.

select child.*, 
  concat_ws('\',p4.Alias,p3.Alias,p2.Alias,p1.Alias,child.Alias) as path
from documents child
left join documents p1 on child.parent = p1.id
left join documents p2 on p1.parent = p2.id
left join documents p3 on p2.parent = p3.id
left join documents p4 on p3.parent = p4.id;

一般来说,由于模型的递归性质,您用于层次结构的树在 sql 中效果不佳(即使其他数据库实际上以您模拟的非常相似的方式支持递归查询与变量)。

有关为层次结构建模的其他方法,请参见例如Bill Karwins 的演讲 Models for hierarchical data。它们使无需递归查询路径变得容易得多。