0

我正在尝试使用一组文档以及一个表示基于其父级的文档路径的字段来填充 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 中。同样出于多重考虑,我已经删除了大部分列以及内容并替换为虚假内容。

4

2 回答 2

0

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

本质上,我创建了一个基于 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 的许多逻辑,以获得简单的答案。如果有人有更好,最好更快的方法,请告诉我!谢谢。

于 2017-08-07T09:55:57.157 回答
0

你得到你的错误是因为你不能在派生表中使用外部变量。派生表基本上是您必须使用别名的每个“子查询”,就像vars您的情况一样。尝试删除该别名,MySQL 会告诉您每个派生表都必须有一个别名。

解决此问题的一种方法是将整个查询移动到一个函数中,例如getpath(child_id int),您可以在其中随意使用此变量(假设您有一个工作查询可以获取一个特定孩子的路径,“某些东西GROUP_CONCAT()”) .

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

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

有关对层次结构建模的其他方法,请参见 Bill Karwins层次数据的演示模型。它们使得在没有递归的情况下查询路径变得更加容易。

于 2017-08-07T10:01:04.303 回答