【问题标题】:Hierarchical Data in MySQLMySQL 中的分层数据
【发布时间】:2009-07-06 03:26:09
【问题描述】:

我的 MySQL 数据库中有一种类似树的东西。

我有一个包含类别的数据库,每个类别都有一个子目录。我将所有类别保存在一个表中,所以列是这样的:

*categories table*
id | name  | parent_id
1  | Toys  | 0
2  | Dolls | 1
3  | Bikes | 1

我数据库中的每个项目都被分配到这些类别之一:

*items table*
item   | category_id
barbie | 2
schwinn| 3

问题是如果有人想查看所有玩具(父类别),从项目数据库中获取信息的最佳方法是什么?我知道的唯一方法是做类似的事情

SELECT * 
FROM items 
WHERE category_id = 2 
JOIN SELECT * 
     FROM items 
     WHERE category_id = 3
     etc... 

但如果我在 Toys 下有 10 个类别,那么我必须执行此连接和查询 10 次。

有没有更好的方法来处理这个问题?

【问题讨论】:

  • 那些连接是无效的 SQL 语法(如果你修复语法,例如通过第二个选择周围的括号,你会得到一个空的结果集);也许你的意思是 UNION?

标签: mysql join hierarchical-data


【解决方案1】:

您希望获得父 ID:

所以假设你得到了

set @parentId = 1 /*toys*/

select 
  *
from
  Items i
inner join Categories c on c.id = i.categoryId
where
  c.parentId = @parentId

这将为您提供您想要的项目 - 有一个主要的设计缺陷:它不处理多个层次的分层类别。

假设您有这个类别表:

*Categories table*
id | name    | parentId
1  | Toys    | 0
2  | Dolls   | 1
3  | Bikes   | 1
4  | Models  | 2
5  | Act.Fig.| 2
6  | Mountain| 3
7  | BMX     | 3

还有物品:

*items table*
item   | category_id
Barbie | 4
GIJoe  | 5
Schwinn| 6
Huffy  | 7

获取所有相关项目的唯一方法是进行自连接:

select 
  *
from
  Items i 
inner join Categories c on c.id = i.categoryId
inner join Categories c2 on c.parentId = c2.id
where
  c2.parentId = @parentId

这种模式是不可扩展的——因为你可以有多个层次的层次结构。

处理层次结构的一种常见方法是构建一个“扁平化”表:将每个节点与其所有后代链接起来的行。

除了类别表之外,您还构建了第二个表:

*CategoriesFlat table*  The Name column is here only for readability
id | name    | parentId
1  | Toys    | 1
-----------------
2  | Dolls   | 1
2  | Dolls   | 2
-----------------
4  | Models  | 1
4  | Models  | 2
4  | Models  | 4
5  | Act.Fig.| 1
5  | Act.Fig.| 2
5  | Act.Fig.| 5
-----------------
3  | Bikes   | 1
3  | Bikes   | 3
-----------------
6  | Mountain| 1
6  | Mountain| 3
6  | Mountain| 6
7  | BMX     | 1
7  | BMX     | 3
7  | BMX     | 7

所以你可以写:

select 
  *
from
  Items i
inner join CategoriesFlat c on c.id = i.categoryId
where
  c.parentId = @parentId

并获取所有相关的类别和项目。

这是great slideshow about SQL anti-patterns 及其解决方案。 (SQL 中的分层数据是一种反模式,但不要灰心——我们都会遇到这种情况)

【讨论】:

  • 绝妙的答案!如果可以的话,我会更多地支持你。真的好东西要知道。资源也很好,谢谢分享。
【解决方案2】:

但如果我在 Toys 下有 10 个类别,那么我必须执行此连接和查询 10 次。有没有更好的方法来处理这个问题?

是的,有一种存储数据的方法称为“nested sets”。插入数据有点困难,但使用单个 select 语句选择整个多级分支很简单。

此外,Celko 还写了 a book 关于这个主题的文章,其中有一章是关于嵌套集的,另一章是关于其他方法的。

【讨论】:

【解决方案3】:

我假设您知道如何获取 ID 号,这不是问题的重点。此外,parent_id 也应该是引用 id 的 FK,我会在最顶层使用 NULL,而不是 0。

如果您的顶级类别最多有一个级别的子类别,您可以使用此查询来获取所有玩具:

SELECT *
FROM items
WHERE items.category_id IN (SELECT id FROM categories
                            WHERE categories.parent_id = 1
                            OR categories.id = 1);

如果您的类别可以有嵌套的子类别,则您必须使用存储过程并递归调用它。伪代码:

Procedure getItemsInCategory
Input: @category_id integer
Output: items rows
{
    For each item in (SELECT *
                      FROM items
                      WHERE items.category_id = @category_id):
        return the row;

    For each id in (SELECT id 
                    FROM categories
                    WHERE categories.parent_id = @category_id):
        return the rows in getItemsInCategory(id);
}

【讨论】:

    【解决方案4】:

    假设您知道 Toys 类别的 id,并且顶级 Toys 类别中没有任何内容:

    SELECT * FROM items WHERE category_id IN (SELECT id FROM categories WHERE parent_id = 1)
    

    【讨论】:

    • 顺便说一句,任何东西都不应该属于非叶子类别——原因与 Haahr 给出的“从不继承具体类”的原因完全相同;如果您需要其他/杂项玩具子类别,请将其作为玩具的子类别,因此它与其他子类别处于同一级别。
    【解决方案5】:
    1. 使用IN operator
    2. 使用stored procedure
    3. 优化您的表格以更好地反映它们的使用习惯。

    【讨论】:

      【解决方案6】:

      这个帖子可能会有所帮助:http://forums.mysql.com/read.php?10,32818,32818#msg-32818

      您真正想要的是 START WITH 和 CONNECT BY 语法,但这仅在 Oracle 中支持,MySQL 不支持。

      【讨论】:

        【解决方案7】:

        我不熟悉 MySQL,但这是我在 TSQL (SQL SERVER) 中的方式,也许尝试在 MySQL 中找到一种等效的方式?

        1) 遍历所有类别以获取特定项目的子项,在本例中,类别 id = 1

        2) 在层次结构 CTE(Common Table Expression) 中过滤与子项相关的项。

        With Hierarchy As
        (
        
        SELECT    id, name, parent_id
        from         categories
        where        id = 1
        UNION ALL
        SELECT      child.id, child.name, child.parent_id
        from           categories child
        inner join  Hierarchy parent on child.parent_id = parent.id
        )
        SELECT * FROM items
        WHERE category_id IN 
        (
           Select id
           from Hierarchy 
         )
        

        【讨论】:

          【解决方案8】:

          我想和你分享我的想法。

          邻接模型的局限性: 关注Managing Hierarchical Data in MySQL 正如您已经描述的那样,邻接模型有限制,您必须在检索路径之前知道级别。

          使用嵌套模型: 但是如果您将数据结构转换为嵌套集模型,那么您仍然可以使用自连接来获取树。

          将分层模型转换为嵌套模型: 现在我们需要一个树旅行算法来索引嵌套模型。这可以在mysql函数中实现(抱歉转换需要一些算法实现:树遍历算法。不确定哪个最适合)。

          谢谢:)

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-01-24
            • 2023-03-29
            • 1970-01-01
            • 1970-01-01
            • 2013-03-28
            • 2013-06-12
            • 2012-01-31
            • 1970-01-01
            相关资源
            最近更新 更多