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

使用Python和PostgreSQL管理多个类别树,python,sql,python-3.x,postgresql,recursive-query,Python,Sql,Python 3.x,Postgresql,Recursive Query,我有多个类别,可以没有,也可以有一个或多个子类别 从理论上讲,这个过程可以是无限的。所以,这就像有多棵树 树示例 A - A1 - A11 - A12 -A2 B C - C1 我也有一些物品。一个项目可以有多个类别 此时,为了连接类别,我在数据库中使用了三个字段: 儿童(某一类别的儿童) 路径([1,4,8],基本上是祖父母、父母和类别本身的ID) 深度,表示树中每个类别的级别 使用此字段可以避免一些递归和使用更多查询 我通常检索以下数据: 顶级类别(深度0)

我有多个类别,可以没有,也可以有一个或多个子类别

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

树示例

A
 - A1
     - A11
     - A12
-A2
B
C
 - C1
我也有一些物品。一个项目可以有多个类别

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

  • 儿童(某一类别的儿童)

  • 路径([1,4,8],基本上是祖父母、父母和类别本身的ID)

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

使用此字段可以避免一些递归和使用更多查询

我通常检索以下数据:

  • 顶级类别(深度0)

  • 范畴的子范畴

  • 兄弟类别

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

现在我正在使用Django(希望转到FastAPI)和PostgreSQL,每次我对类别进行CRUD操作时,三个字段(路径、深度、子字段)都将被修改


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

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

如果需要,可以选择使用单独的顶级节点,或者让顶级节点具有空父节点。有几个像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;

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

在阵列中存储完整路径,因为您当前是其中之一。但使用此解决方案很难强制执行引用完整性(如何保证数组中的这些
id
s确实存在于表中?),简单的树操作也很繁琐(如何枚举给定节点的直接子节点?)

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

我建议采用闭合表方法。这是通过创建一个单独的表来实现的,该表存储树中所有可能的路径,如下所示:

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\u id=…
替换为
where child\u id=…

您可以使用
联接将主表带入

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

如果你打算坚持使用django来完成你的项目,并且想要一些“开箱即用”的东西,你应该看看。这在大型python项目中使用,这些项目需要像Wagtail这样的数据库中的树结构。

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