嵌套Set Query以检索每个节点的所有祖先

时间:2014-01-08 10:54:33

标签: mysql sql select sql-order-by nested-sets

我有一个MySQL查询,我认为它可以很好地检索每个节点的所有祖先,从顶部节点开始,直到它的直接节点。但是当我在嵌套集中添加第5级时,它就破了。

下面是示例表,查询和SQL Fiddles:

四级嵌套:

CREATE TABLE Tree
(title varchar(20) PRIMARY KEY,
 `tree` int,
 `left` int,
 `right` int);

INSERT Tree
VALUES
("Food", 1, 1, 18),
('Fruit', 1, 2, 11),
('Red', 1, 3, 6),
('Cherry', 1, 4, 5),
('Yellow', 1, 7, 10),
('Banana', 1, 8, 9),
('Meat', 1, 12, 17),
('Beef', 1, 13, 14),
('Pork', 1, 15, 16);

查询:

SELECT t0.title node
      ,(SELECT GROUP_CONCAT(t2.title)
                    FROM Tree t2
                    WHERE t2.left<t0.left AND t2.right>t0.right
                    ORDER BY t2.left) ancestors
FROM Tree t0
GROUP BY t0.title;

节点Banana的返回结果为Food,Fruit,Yellow - 完美。你可以在这里看到SQL Fiddle - 4 Levels

当我在下面的5级表上运行相同的查询时,第5级节点以错误的顺序返回:

CREATE TABLE Tree
(title varchar(20) PRIMARY KEY,
 `tree` int,
 `left` int,
 `right` int);

INSERT Tree
VALUES
("Food", 1, 1, 24),
('Fruit', 1, 2, 13),
('Red', 1, 3, 8),
('Cherry', 1, 4, 7),
('Cherry_pie', 1, 5, 6),
('Yellow', 1, 9, 12),
('Banana', 1, 10, 11),
('Meat', 1, 14, 23),
('Beef', 1, 15, 16),
('Pork', 1, 17, 22),
('Bacon', 1, 18, 21),
('Bacon_Sandwich', 1, 19, 20);

Bacon_Sandwich的返回结果为Bacon,Food,Meat,Pork,这不是正确的顺序,应为Food,Meat,Pork,Bacon - 您可以在此处看到此SQL Fiddle - 5 Levels

我不确定发生了什么,因为我不太了解子查询。任何人都可以对此有所了解吗?

调查后编辑:

哇!!看起来写出所有这些并阅读有关GROUP_CONCAT订购的信息给了我一些启发。

ORDER BY添加到实际的GROUP_CONCAT函数并从子查询的末尾删除解决了该问题。我现在收到节点Food,Meat,Pork,Bacon

Bacon_Sandwich
SELECT t0.title node
      ,(SELECT GROUP_CONCAT(t2.title ORDER BY t2.left)
                    FROM Tree t2
                    WHERE t2.left<t0.left AND t2.right>t0.right
                    ) ancestors
FROM Tree t0
GROUP BY t0.title;

我仍然不知道为什么。子查询末尾的ORDER BY适用于4级但不适用于5级?!?!

如果有人能够解释问题是什么以及为什么要移动ORDER BY修复它,我将非常感激。

2 个答案:

答案 0 :(得分:6)

首先,了解您拥有implicit GROUP BY

非常重要
  

如果在不包含GROUP BY子句的语句中使用组函数,则它等同于对所有行进行分组。

为了使这一点更容易理解,我将省略子查询并将问题减少到香蕉。香蕉是集[10,11]。正确排序的祖先是那些:

SELECT "banana" as node, GROUP_CONCAT(title ORDER by `left`)
  FROM Tree WHERE `left` < 10 AND `right` > 11
  GROUP BY node;

ORDER BY必须位于GROUP_CONCAT(),因为您希望对聚合函数进行排序。 ORDER BY对聚合结果进行排序(即GROUP_CONCAT()的结果)。直到4级工作的事实才是运气。 ORDER BY对聚合函数没有影响。无论有没有ORDER BY

,您都会得到相同的结果
SELECT GROUP_CONCAT(title)
  FROM Tree WHERE `left` < 10 AND `right` > 11
  /* ORDER BY `left` */

理解什么可能会有所帮助 SELECT GROUP_CONCAT(title ORDER BY left) FROM Tree WHERE … ORDER BY left确实:

  1. 获取一个选择(WHERE),其结果是以未定义的顺序排列三行:

    ("Food")
    ("Yellow")
    ("Fruit")
    
  2. 将结果聚合成一行(隐式GROUP BY),以便能够使用聚合函数:

    (("Food","Yellow", "Fruit"))
    
  3. 在其上触发聚合函数(GROUP_CONCAT(title, ORDER BY link))。即按链接排序然后连接:

    ("Food,Fruit,Yellow")
    
  4. 现在终于对结果进行了排序(ORDER BY)。因为它只有一行,所以排序不会改变任何内容。

    ("Food,Fruit,Yellow")
    

答案 1 :(得分:3)

您可以使用JOINSUB-QUERY获取结果。

使用加入:

SELECT t0.title node, GROUP_CONCAT(t2.title ORDER BY t2.left) ancestors 
FROM Tree t0
LEFT JOIN Tree t2 ON t2.left < t0.left AND t2.right > t0.right
GROUP BY t0.title; 

选中此SQL FIDDLE DEMO

使用SUB-QUERY:

SELECT t0.title node, 
      (SELECT GROUP_CONCAT(t2.title ORDER BY t2.left)
       FROM Tree t2 WHERE t2.left<t0.left AND t2.right>t0.right) ancestors
FROM Tree t0
GROUP BY t0.title;

选中此SQL FIDDLE DEMO

<强>输出

|           NODE |             ANCESTORS |
|----------------|-----------------------|
|          Bacon |        Food,Meat,Pork |
| Bacon_Sandwich |  Food,Meat,Pork,Bacon |
|         Banana |     Food,Fruit,Yellow |
|           Beef |             Food,Meat |
|         Cherry |        Food,Fruit,Red |
|     Cherry_pie | Food,Fruit,Red,Cherry |
|           Food |                (null) |
|          Fruit |                  Food |
|           Meat |                  Food |
|           Pork |             Food,Meat |
|            Red |            Food,Fruit |
|         Yellow |            Food,Fruit |

在您的子查询中,您在ORDER BY子句之后使用了WHERE,这不会影响输出。默认情况下,GROUP_CONCAT()函数将按列值的升序对输出字符串进行排序。它不会考虑您显式的ORDER BY子句。

如果您检查第一个查询的输出,该输出以标题列的升序返回数据。因此,节点Banana的返回结果为Food,Fruit,Yellow

但是,Bacon_Sandwich的第二个结果是Bacon,Food,Meat,Pork,因为按升序排列的Bacon优先于Food

如果您想根据left列订购结果,则必须在ORDER BY函数中指定GROUP_CONCAT(),如上所述。检查我的两个查询。

我希望您使用JOIN代替SUB-QUERY来提高效果。