通过自连接表递归获取树

dus*_*ash 5 postgresql recursive many-to-many self-join

使用此处的其他问题和 Postgresql 文档,我设法构建了一个多对多自联接表。

但是添加一个WHERE条款给我带来了麻烦。

问题:

ACategory可以有许多子类别和许多父类别。给定 a category.Id,我想检索类别、类别儿童、儿童的儿童等。

示例:给定这个结构:

child_1
    child_11
        child_111
        child_112
            child_1121
    child_21
child_2
Run Code Online (Sandbox Code Playgroud)

给定:一个子句 id = child_11

预期结果: child_11, child_111, child_112, child_1121

实际结果: child_11, child_111, child_112

这是我的尝试:http : //sqlfiddle.com/#!17/3640f/2

如果 Sqlfiddle 关闭:https ://www.db-fiddle.com/#&togetherjs=LhDjxfPHo6

注意:我不在乎复制 where 子句,我的应用程序可以处理

表结构:

CREATE TABLE Category(id SERIAL PRIMARY KEY, name VARCHAR(255));
CREATE TABLE Categories(parent_id INTEGER, child_id INTEGER, PRIMARY KEY(parent_id, child_id));

ALTER TABLE Categories ADD FOREIGN KEY (parent_id) REFERENCES category (id);
ALTER TABLE Categories ADD FOREIGN KEY (child_id) REFERENCES category (id);
Run Code Online (Sandbox Code Playgroud)

表数据:

INSERT INTO Category(id, name) VALUES (1, 'parent_1');
INSERT INTO Category(id, name) VALUES (2, 'child_1');
INSERT INTO Category(id, name) VALUES (3, 'child_2');
INSERT INTO Category(id, name) VALUES (4, 'child_3');
INSERT INTO Category(id, name) VALUES (5, 'child_1_1');
INSERT INTO Category(id, name) VALUES (6, 'child_1_2');
INSERT INTO Category(id, name) VALUES (7, 'child_1_1_1');
INSERT INTO Category(id, name) VALUES (10, 'child_of_many');
INSERT INTO Category(id, name) VALUES (11, 'parent_1');
INSERT INTO Category(id, name) VALUES (12, 'parent_2');

INSERT INTO Categories(parent_id, child_id) VALUES (1, 2);
INSERT INTO Categories(parent_id, child_id) VALUES (1, 3);
INSERT INTO Categories(parent_id, child_id) VALUES (1, 4);
INSERT INTO Categories(parent_id, child_id) VALUES (2, 5);
INSERT INTO Categories(parent_id, child_id) VALUES (2, 6);
INSERT INTO Categories(parent_id, child_id) VALUES (5, 7);
Run Code Online (Sandbox Code Playgroud)

我的查询给了我孩子,但不是孩子的孩子等。如果我删除 WHERE 子句,我可以获得所有行:

WITH RECURSIVE categories_category AS (
  SELECT id, 'Category' AS COLUMN_TYPE, c1.name 
  FROM Category c1
  WHERE c1.id=2
UNION
  SELECT c2.id, 'Category' AS COLUMN_TYPE, c2.name
   FROM Category c1
   INNER JOIN categories cs1 ON c1.id = cs1.parent_id
   INNER JOIN Category c2 ON c2.id = cs1.child_id
   WHERE cs1.parent_id = 2
) SELECT * FROM categories_category
Run Code Online (Sandbox Code Playgroud)

编辑:更详细的例子:

鉴于以下类别行的,我希望能够在给定 WHERE 子句的情况下运行查询,该查询与 id 匹配warmStoreAlcohol并获得结果:

+---+------------------+
|id |name              |
+---+------------------+
|2  |warmStoredAlcohol |
|3  |vodka             |
|4  |beer              |
|5  |frozenBeer        |
+---+------------------+
Run Code Online (Sandbox Code Playgroud)

coldStoredAlcohol 将给出以下结果:

+---+------------------+
|id |name              |
+---+------------------+
|6  |coldStoredAlcohol |
|5  |frozenBeer        |
|7  |cooler            |
+---+------------------+
Run Code Online (Sandbox Code Playgroud)

数据库结构不会经常改变。在这个例子中 'frozenBeer' 有两个父对象,并且应该返回以查询warmStoredAlcoholcoldStoredAlcohol

我愿意更改表结构,添加新表,甚至升级 postgres 版本等。数据库将保存约 2,000 行,因此我更看重易于理解的表结构,而不是超级复杂的最佳结构。(但任何解决方案都比我破碎的更好) 在此处输入图片说明

Han*_*non 5

众所周知,递归 CTE 很难学习。看看这个小提琴来玩一个工作模型。

\n\n

CTE(或公共表表达式)中的第二个查询需要引用第一个查询。这就是 CTE 中“递归”名称的由来。

\n\n

我的小提琴有一个表,其中每只猫只能有一个父级,这可以说是更常见的树场景。在我看来,这使得它更容易理解。

\n\n

无论如何,DDL 包括:

\n\n
CREATE TABLE cats\n(\n    cat_id SERIAL\n      PRIMARY KEY\n  , cat_name VARCHAR(20) NOT NULL\n  , parent_cat_id int NULL\n);\n\nALTER TABLE cats ADD FOREIGN KEY (parent_cat_id) REFERENCES cats (cat_id);\n\nINSERT INTO cats (cat_name, parent_cat_id)\nVALUES (\'garfield\', NULL);\n\nINSERT INTO cats (cat_name, parent_cat_id)\nVALUES (\'bertina\', 1);\n\nINSERT INTO cats (cat_name, parent_cat_id)\nVALUES (\'oletha\', 2);\n
Run Code Online (Sandbox Code Playgroud)\n\n

递归 CTE 如下所示:

\n\n
;WITH RECURSIVE cats_hierarchy AS\n(\n  SELECT c1.cat_id\n      , c1.cat_name\n      , cast(\'\' as varchar(20)) as parent_cat_name\n      , c1.parent_cat_id\n      , 1 as level\n  FROM cats c1\n  WHERE c1.parent_cat_id IS NULL\n\n  UNION ALL\n\n  SELECT c2.cat_id\n      , c2.cat_name\n      , c1.cat_name AS parent_cat_name\n      , c2.parent_cat_id\n      , c1.level + 1 AS level\n  FROM cats c2\n      INNER JOIN cats_hierarchy c1 ON c2.parent_cat_id = c1.cat_id\n)\nSELECT *\nFROM cats_hierarchy;\n
Run Code Online (Sandbox Code Playgroud)\n\n

结果如下:

\n\n
\xe2\x95\x94\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2 \x95\x90\xe2\x95\x90\xe2\x95\xa6\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95 \x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xa6\xe2\x95\x90\xe2\x95\x90 \xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2 \x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95 \xa6\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90 \xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2 \x95\xa6\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95 \x97\n\xe2\x95\x91 猫_id \xe2\x95\x91 猫_名称 \xe2\x95\x91 父猫_名称 \xe2\x95\x91 父_猫_id \xe2\x95\x91 级别 \xe2\x95\x91\n\xe2\ x95\xa0\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\ x90\xe2\x95\x90\xe2\x95\xac\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\ xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xac\xe2\x95\x90\xe2\x95\x90\xe2\ x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\ x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xac\ xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\ x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\ xac\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xa3\ n\xe2\x95\x91 1 \xe2\x95\x91 加菲猫 \xe2\x95\x91 \xe2\x95\x91 (空) \xe2\x95\x91 1 \xe2\x95\x91\n\xe2\x95\ x91 2 \xe2\x95\x91 伯蒂娜 \xe2\x95\x91 加菲猫 \xe2\x95\x91 1 \xe2\x95\x91 2 \xe2\x95\x91\n\xe2\x95\x91 3 \xe2\x95\ x91 奥勒萨 \xe2\x95\x91 伯蒂娜 \xe2\x95\x91 2 \xe2\x95\x91 3 \xe2\x95\x91\n\xe2\x95\x9a\xe2\x95\x90\xe2\x95\x90\ xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xa9\xe2\ x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\ x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xa9\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\ xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\ x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xa9\xe2\x95\x90\xe2\x95\x90\xe2\x95\ x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\ xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\xa9\xe2\x95\x90\xe2\x95\x90\xe2\ x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x90\xe2\x95\x9d
\n\n

在 CTE 中,第一个查询(在 之前UNION ALL)显示位于根的 cats。CTE 的第二部分(在 后面UNION ALL)获取与父猫相关的猫。然后 CTE 再次查询这些返回的结果,生成一棵树。

\n\n

如果有帮助,或者您是否需要进一步说明,请告诉我。

\n