【问题标题】:How can I find all siblings to my node and its anchestors in a hierarchical category tree?如何在分层类别树中找到我的节点及其锚点的所有兄弟姐妹?
【发布时间】:2019-04-16 13:21:04
【问题描述】:

这是我的桌子:

CREATE TABLE IF NOT EXISTS  `Category` (
`Name` varchar(25) NOT NULL,
`lft` INT UNSIGNED NOT NULL,
`rgt` INT UNSIGNED NOT NULL,
`CategoryId` int UNSIGNED auto_increment NOT NULL,
PRIMARY KEY (`CategoryId`)
) Engine = InnoDb;

我有一个如下所示的网址:products.php?category=5

从类别 ID 中,我需要在层次结构的每个级别检索具有相同父级的所有类别。我找到了一种方法来做到这一点,但我认为它效率低下,有没有更好的方法来做到这一点?

CREATE VIEW category_tree AS
SELECT node.name as name,
    node.categoryId as categoryId,
    node.lft as lft,
    node.rgt as rgt,
    (COUNT(parent.categoryId) - 1) AS depth
FROM Category AS node,
Category AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.categoryId;

SELECT tree.name, tree.depth, tree.categoryId,
(node.lft BETWEEN tree.lft AND tree.rgt) AS is_selected
FROM category_tree as tree
JOIN category_tree AS node ON node.categoryId = :selectedCategory
JOIN (
    SELECT MAX(tree.lft) as lft
    FROM category_tree as tree
    JOIN category_tree AS node ON node.categoryId = :selectedCategory
    WHERE
    tree.depth = node.depth -1
    AND tree.lft < node.lft
) AS parent_finder
LEFT JOIN category_tree AS parent ON parent.lft = parent_finder.lft
WHERE tree.depth < node.depth
OR (
  tree.depth = node.depth
  AND tree.lft BETWEEN parent.lft AND parent.rgt
)
OR (
  tree.lft BETWEEN node.lft AND node.rgt
  AND tree.depth <= node.depth + 1
)
ORDER BY tree.depth, tree.name

例如,假设我的类别树如下所示:


(来自Managing Hierarchical Data in MySQL

假设用户选择了“cd 播放器”,我想检索以下信息:

name                 depth    is_selected
electronics           0            1
portable electronics  1            1
televisions           1            0
mp3 players           2            0
cd players            2            1
2 way radios          2            0

我想检索所有选定的类别和与选定类别处于同一级别的所有类别以及深度信息和选定的类别。

我需要这样做的原因是,产品页面可以包含一个下拉导航菜单,用于每个级别的类别,一直到所选类别。

【问题讨论】:

    标签: mysql sql


    【解决方案1】:

    我不确定我是否遵循所有这些,但听起来您想要所有类别 5 的直系子女。

    这是一种方法:

    SELECT child.*
    FROM Category parent
      JOIN Category child 
        ON (child.lft BETWEEN parent.lft AND parent.rgt)
      LEFT JOIN Category intermediate 
        ON (intermediate.lft > parent.lft AND intermediate.rgt < parent.rgt
          AND child.lft > intermediate.lft AND child.rgt < intermediate.rgt)
    WHERE intermediate.CategoryId IS NULL
      AND parent.CategoryId = ?;
    

    编辑:好的,我现在明白上面的解决方案只是你想要的一部分。你想要:

    • CD 播放器的直系祖先
    • CD 播放器的“叔叔”(祖先的兄弟姐妹)
    • CD 播放器的兄弟姐妹
    • CD 播放器的孩子

    让我花几分钟时间。


    这是我想出的:

    SELECT descendant.*,
      (current.lft BETWEEN descendant.lft AND descendant.rgt) AS is_selected,
      COUNT(DISTINCT c.CategoryId) AS depth
    FROM Category current
    JOIN Category selected 
      ON (current.lft BETWEEN selected.lft AND selected.rgt)
    JOIN Category descendant 
      ON (descendant.lft BETWEEN selected.lft AND selected.rgt)
    LEFT JOIN Category intermediate 
      ON (intermediate.lft > selected.lft AND intermediate.rgt < selected.rgt
        AND descendant.lft > intermediate.lft AND descendant.lft < intermediate.rgt)
    JOIN Category c
      ON (descendant.lft BETWEEN c.lft AND c.rgt)
    WHERE intermediate.CategoryId IS NULL
      AND current.CategoryId = ?
    GROUP BY descendant.CategoryId
    ORDER BY depth, descendant.name;
    
    • current是CD播放器
    • selected 是 CD 播放器的祖先(电子产品、便携式电子产品、CD 播放器)
    • descendant 是每个selected 祖先的任何子孙等
    • intermediate 是每个 selected 祖先的后代,同时也是 descendant 的父代——必须没有这些,因此 IS NULL 限制。
    • c 是从 descendant 到顶部的祖先链,用于确定深度。

    我刚刚意识到我的解决方案还会返回 current 节点的所有后代。因此,如果您当前正在查看“便携式电子产品”,则该查询将返回其子代,但也会返回孙代“flash”,这可能不是您想要的。

    【讨论】:

    • 谢谢,这正是我需要的。
    • 在我添加 DISTINCT: COUNT(DISTINCT c.CategoryId) AS depth 之前,深度信息似乎不正确
    • 谢谢,我没有测试查询。我已经编辑以包含该修复程序。
    【解决方案2】:

    对于手头的任务,嵌套集模型几乎没有用处。我的建议是您对表格进行预处理以添加具体化路径列,然后使用该列来回答问题。与任何依赖嵌套集表示的解决方案相比,这两个步骤加在一起可能需要更少的工作。

    查找具体化路径是一个相对“众所周知”的问题,但您的具体问题不是,所以这里有一个基于路径的查询来回答它。

    SELECT
      Name,
      len(MyPath)/4 AS Depth,
      CASE WHEN :PathSelected LIKE MyPath + '%' THEN 1 ELSE 0 END AS Selected
    FROM Category
    WHERE MyPath = ''
    OR (
      MyPath <> ''
      AND MyPath LIKE SUBSTRING(:PathSelected,1,ABS(LEN(MyPath) - 4)) + '____'
    );
    

    :PathSelected 是 (SELECT MyPath FROM Categories WHERE ID = :CategorySelected)),您可以预先计算或合并到查询中。此外,ABS 可以防止查询计划在其中计算 SUBSTRING,即使 MyPath = '' 并因此使用负长度参数调用 SUBSTRING。

    这里是您在问题中使用的特定数据的 SQL Server 语法的完整再现。我的查询不使用列 lft 和 rgt,所以我没有包括它们,但如果它们用于其他用途,你没有理由不能将它们放在表中。

    create table Category(
      Name varchar(25) NOT NULL,
      ID int NOT NULL PRIMARY KEY,
      MyPath varchar(200) NOT NULL
    );
    GO
    
    insert into Category values
      ('ELECTRONICS',1,''),
      ('TELEVISIONS',2,'/001'),
      ('PORTABLE ELECTRONICS',3,'/002'),
      ('FLASH',4,'/002/001/001'),
      ('MP3 PLAYERS',5,'/002/001'),
      ('2 WAY RADIOS',6,'/002/002'),
      ('CD PLAYERS',7,'/002/003'),
      ('PLASMA',8,'/001/003'),
      ('LCD',9,'/001/002'),
      ('TUBE',10,'/001/001');
    GO
    
    DECLARE @Selected int = 7;
    DECLARE @MyPath varchar(200) = (
      SELECT MyPath FROM Category
      WHERE ID = @Selected
    );
    
    SELECT
      Name,
      len(MyPath)/4 AS Depth,
      CASE WHEN @MyPath LIKE MyPath + '%' THEN 1 ELSE 0 END AS Selected
    FROM Category
    WHERE MyPath = ''
    OR (
      MyPath <> ''
      AND MyPath LIKE SUBSTRING(@MyPath,1,LEN(MyPath) - 4) + '____'
    );
    GO
    
    DROP TABLE Category;
    

    【讨论】:

      猜你喜欢
      • 2012-02-27
      • 1970-01-01
      • 1970-01-01
      • 2019-05-06
      • 1970-01-01
      • 2018-01-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多