使用 Python 和 PostgreSQL 管理多个类别树

Managing multiple categories trees, using Python and PostgreSQL

我有多个类别,其中可以有None或一个或多个sub-categories。

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

树示例。

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

我也有物品。一个项目可以在多个类别中。

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

使用这个字段我避免了一些递归并使用更多查询。

我通常检索如下数据:

此时我正在使用Django(想转FastAPI)和PostgreSQL,每次对categories进行增删改查,都会修改三个字段(path,depth,children) .

我认为 maintain/retrieve 类别树和相应项目可能是更好的方法。

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

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

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;

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

将完整路径存储在一个数组中,因为您目前就是其中之一。但是这种方案很难实现引用完整性(如何保证数组中的这些id真的存在于table中?),简单的树操作很繁琐(你如何枚举给定节点的直接children?)。

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

我会推荐 闭包 table 方法。这是通过创建一个单独的 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

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

select * from category_path where parent_id = 'A'

要获得所有 parents,您只需将 where parent_id = ... 替换为 where child_id = ...

你可以用 join:

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

如果您计划在您的项目中坚持使用 django,并且想要更多的东西 "out of the box",您应该看看 django-treebeard。这用于大型 python 项目,这些项目需要数据库中的树结构,例如 Wagtail。