在自引用查询上使用MySQL创建Concatinated父列

时间:2017-08-07 06:34:19

标签: mysql elasticsearch group-concat self-referencing-table

我正在尝试使用一组文档填充ElasticSearch以及一个表示基于其父项的文档路径的字段。

这是我的表格布局:

+----+--------+-------+----------+
| 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

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中。同样为了多重性,我已经取出了大部分列和内容,并替换为虚假内容。

2 个答案:

答案 0 :(得分:0)

我创造了一个不错的解决方案。它的速度并不令人难以置信,但这种情况太过于预期,因为这只是一天一次的负载,现在是可以接受的。

基本上,我创建了一个基于id获取Path的函数,然后运行一个视图(在推送到生产时使用虚拟物化视图,以便更快地加载到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;

然后我只从logstash脚本运行SELECT * FROM documentswithpath;。对于简单的答案,这也排除了很多logstash的逻辑。如果有人有更好的,最好更快的方法,请告诉我!感谢。

答案 1 :(得分:0)

您收到错误是因为您无法在派生表中使用外部变量。派生表基本上是每个&#34;子查询&#34;您必须使用别名,例如vars。尝试删除该别名,MySQL将告诉您每个派生表必须有一个别名。

解决此问题的一种方法是将整个查询移动到一个函数中,例如getpath(child_id int),然后您可以随意使用此变量(假设您有一个可以获取特定子项路径的工作查询,&#34; GROUP_CONCAT()&#34;)

但在您的情况下,实际上可以重新组织您的代码,因此您不需要派生表:

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;

我使用你的内部文档,就像你一样,这实际上非常低效,仅用于支持无限的树深度。如果您知道自己的最高依赖关系级别,则可以使用lvls的备用代码作为评论(只是一个数字列表)或limit添加。

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

有一种更直接的方法可以解决您的问题。它要求你知道树的最大深度,但如果你这样做,你可以简单地将你的父母加入每个孩子。

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中不能很好地工作(即使其他数据库实际上支持递归查询的方式与您使用变量模拟的方式非常相似)。 / p>

有关建立层次结构建模的其他方法,请参阅例如Bill Karwins的演讲Models for hierarchical data。它们使得在没有递归的情况下查询路径变得容易得多。