使用 Python 和 PostgreSQL 管理多个类别树
Managing multiple categories trees, using Python and PostgreSQL
我有多个类别,其中可以有None或一个或多个sub-categories。
进程理论上可以无限大。所以,这就像拥有多棵树。
树示例。
A
- A1
- A11
- A12
-A2
B
C
- C1
我也有物品。一个项目可以在多个类别中。
此时连接类别,在数据库中我使用了三个字段:
children(一个类别的children),
path([1,4,8],基本上是grandparent,parent,和category本身的id)
深度,表示每个类别在树中的级别
使用这个字段我避免了一些递归并使用更多查询。
我通常检索如下数据:
热门类别(深度 0)
类别的子类别
兄弟类别
类别中的项目(例如grandparent类别,将显示其直接项目、children的项目和grandchildren的项目])
此时我正在使用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。
我有多个类别,其中可以有None或一个或多个sub-categories。
进程理论上可以无限大。所以,这就像拥有多棵树。
树示例。
A
- A1
- A11
- A12
-A2
B
C
- C1
我也有物品。一个项目可以在多个类别中。
此时连接类别,在数据库中我使用了三个字段:
children(一个类别的children),
path([1,4,8],基本上是grandparent,parent,和category本身的id)
深度,表示每个类别在树中的级别
使用这个字段我避免了一些递归并使用更多查询。
我通常检索如下数据:
热门类别(深度 0)
类别的子类别
兄弟类别
类别中的项目(例如grandparent类别,将显示其直接项目、children的项目和grandchildren的项目])
此时我正在使用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
:
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。