【问题标题】:Managing multiple categories trees, using Python and PostgreSQL使用 Python 和 PostgreSQL 管理多个类别树
【发布时间】:2019-12-29 16:25:37
【问题描述】:

我有多个类别,可以有 None 或一个或多个子类别。

这个过程理论上可以无限。所以,这就像拥有多棵树。

树示例。

A
 - A1
     - A11
     - A12
-A2
B
C
 - C1

我也有物品。一个项目可以属于多个类别。

此时连接类别,在数据库中我使用三个字段:

  • children(一个类别的孩子),

  • path([1,4,8],基本上就是祖父母,父母,类别本身的id)

  • 深度,代表树中每个类别的层级

使用这些字段可以避免一些递归并使用更多查询。

我通常检索如下数据:

  • 顶级类别(深度 0)

  • 一个类别的子类别

  • 同级类别

  • 类别中的项目(例如祖父类别,将显示其直接项目、子项目和孙子项目)

目前我正在使用Django(想迁移到FastAPI)和PostgreSQL,每次对类别进行CRUD操作时,都会修改三个字段(路径,深度,子项)。

我在想也许是一种更好的方法来维护/检索类别树和相应的项目。

【问题讨论】:

    标签: python sql python-3.x postgresql recursive-query


    【解决方案1】:

    在数据库中存储树有多种可能的策略。

    按照您当前的方式将完整路径存储在数组中就是其中之一。但是使用这种解决方案很难强制执行引用完整性(如何保证数组中的这些ids 确实存在于表中?),并且简单的树操作很繁琐(如何枚举给定节点的直接子节点?)。

    @VesaKarjalainen 的回答建议使用 邻接列表 模型,这是一个单独的表,其中每个元素都引用其直接祖先。它可以工作,但也有缺点:通常,遍历层次结构很复杂(比如获取给定节点的所有子节点或父节点):您需要某种迭代或递归,这需要 SQL 引擎效率不高。

    我会推荐 closure table 方法。这通过创建一个单独的表来存储树中所有可能的路径来工作,如下所示:

    create table category_path (
        parent_id int,
        child_id int,
        level int,
        primary key(parent_id, child_id),
        foreign key(parent_id) references category(id),
        foreign key(parent_id) references category(id)
    );
    

    对于您提供的这个树结构:

            A       B     C 
           / \            |
         A1   A2          C1
         /\
      A11  A12
    

    您将存储以下数据:

    parent_id    child_id    level
    A            A           0
    A            A1          1
    A            A2          1
    A            A11         2
    A            A12         2
    A1           A11         1
    A1           A12         1
    B            B           0
    C            C           0
    C            C1          1
    

    现在,假设您要检索给定类别的所有子项,这很简单:

    select * from category_path where parent_id = 'A'
    

    要获取所有父母,您只需将 where parent_id = ... 替换为 where child_id = ...

    您可以使用join 引入主表:

    select c.*
    from category_path cp
    inner join categories c on c.id = cp.parent_id
    where cp.parent_id = 'A'
    

    【讨论】:

    • PostgreSQL 中的递归查询非常高效,并且在大多数情况下类别树会被缓存。无论如何,您建议的模型或任何其他预处理格式都可以从我提供的基本模型轻松创建 - 并且在编辑树时不存在损坏链接的风险。此外,手动制作这个扩展版本很容易出错 - 无论如何你需要一个程序。
    【解决方案2】:

    使用递归 CTE 查询创建层次结构树。根据您的层次结构大小和典型查询,索引和自动缓存可能足以使其足够快。否则,物化视图可能是一个好方法。

    如果需要,您可以选择使用单独的 TOP 节点,或者让顶级节点的父节点为 NULL。拥有几个像 TOP 这样的节点可以在同一个表中拥有几棵树。此外,查询单个下游节点及以上节点应该不难。

    DROP TABLE IF EXISTS category;
    
    CREATE TABLE category (
        id varchar PRIMARY KEY,
        parent varchar
    );
    
    COPY category (id,parent)
    FROM  stdin WITH DELIMITER ';';
    TOP;\N
    1;TOP
    2;TOP
    1A;1
    1B;1
    1A1;1A
    1A2;1A
    \.
    
    WITH RECURSIVE tree AS (
      SELECT
        id,
        parent,
        id  AS path
      FROM
        category
      WHERE
        parent IS NULL
    UNION
      SELECT
        c.id,
        c.parent,
        p.path || ' -> ' || c.id
      FROM
        category c
      INNER JOIN
        tree p
       ON c.parent = p.id
      )
    
    SELECT * FROM tree
    ORDER BY path;
    

    【讨论】:

      【解决方案3】:

      如果您打算在您的项目中坚持使用 django,并且想要更多“开箱即用”的东西,您应该看看 django-treebeard。这用于需要数据库中的树结构(如 Wagtail)的大型 Python 项目。

      【讨论】:

        猜你喜欢
        • 2019-05-06
        • 2012-08-03
        • 1970-01-01
        • 1970-01-01
        • 2013-07-06
        • 1970-01-01
        • 2021-02-25
        • 2018-01-13
        • 1970-01-01
        相关资源
        最近更新 更多