我使用PostgreSQL的ltree模块构建了这个物化路径树结构。
我当然可以轻松地使用ltree从整个树或特定路径/子路径获取所有节点,但是当我这样做时,我自然得到的是很多行(等于数组/切片最后的节点.. Golang /你使用的任何编程语言)
我之后的目标是获取树 - 理想情况是从某个开始和结束路径/点 - 作为一个像#等等的hieracical JSON树对象
{
"id": 1,
"path": "1",
"name": "root",
"children": [
{
"id": 2,
"path": "1.2",
"name": "Node 2",
"children": [
{
"id": 3,
"path": "1.2.3",
"name": "Node 3",
"children": [
{
"id": 4,
"path": "1.2.3.4",
"name": "Node 4",
"children": [
]
}
]
},
{
"id": 5,
"path": "1.2.5",
"name": "Node 5",
"children": [
]
}
]
}
]
}
我从线性(非层次)行/数组/切片结果集中知道我当然可以在Golang中爆炸路径并在那里创建必要的业务逻辑来创建这个json,但它肯定会有很多如果能够直接用PostgreSQL实现这一目标,那就更好了。
那么你如何在PostgreSQL中输出一个ltree树结构到json - 可能从一个开始到结束的路径?
如果你不了解ltree,我想这个问题可以更多地概括为" Materalized path tree to hierachical json"
此外,我还想在除了ltree路径之外的所有节点上添加一个parent_id,因为至少那时我可以使用该id的递归调用来获取json我猜...我也考虑过根据父ID发生变化的时间来触发那个parent_id来管理路径(保持更新) - 我知道这是另一个问题,但也许你可以告诉我你的意见,关于这个?
我希望有些天才可以帮助我。 :)
为方便起见,您可以使用示例创建脚本来节省时间:
CREATE TABLE node
(
id bigserial NOT NULL,
path ltree NOT NULL,
name character varying(255),
CONSTRAINT node_pkey PRIMARY KEY (id)
);
INSERT INTO node (path,name)
VALUES ('1','root');
INSERT INTO node (path,name)
VALUES ('1.2','Node 1');
INSERT INTO node (path,name)
VALUES ('1.2.3','Node 3');
INSERT INTO node (path,name)
VALUES ('1.2.3.4','Node 4');
INSERT INTO node (path,name)
VALUES ('1.2.5','Node 5');
答案 0 :(得分:2)
我能够找到并稍微更改它以使用ltree的物化路径,而不是像邻近树结构中经常使用的父ID。
虽然我仍然希望有更好的解决方案,但我想这将完成工作。
我觉得除了ltree路径之外我还要添加parent_id,因为这当然不会像引用父ID一样快。
这个人的solution得到了很好的信用,这是我使用ltree的子路径,ltree2text和nlevel稍微修改过的代码来实现完全相同:
WITH RECURSIVE c AS (
SELECT *, 1 as lvl
FROM node
WHERE id=1
UNION ALL
SELECT node.*, c.lvl + 1 as lvl
FROM node
JOIN c ON ltree2text(subpath(node.path,nlevel(node.path)-2 ,nlevel(node.path))) = CONCAT(subpath(c.path,nlevel(c.path)-1,nlevel(c.path)),'.',node.id)
),
maxlvl AS (
SELECT max(lvl) maxlvl FROM c
),
j AS (
SELECT c.*, json '[]' children
FROM c, maxlvl
WHERE lvl = maxlvl
UNION ALL
SELECT (c).*, json_agg(j) children FROM (
SELECT c, j
FROM j
JOIN c ON ltree2text(subpath(j.path,nlevel(j.path)-2,nlevel(j.path))) = CONCAT(subpath(c.path,nlevel(c.path)-1,nlevel(c.path)),'.',j.id)
) v
GROUP BY v.c
)
SELECT row_to_json(j)::text json_tree
FROM j
WHERE lvl = 1;
此解决方案存在一个很大的问题,到目前为止..请参阅下面的图片以查找错误(缺少节点5):
答案 1 :(得分:1)
未显示节点5的原因是因为它是不处于最大级别的叶节点,并且随后的加入条件排除了该节点。
通过树递归的真正基本情况是作为叶的节点。通过从最大级别开始,它将隐式选择所有叶节点,但是会错过出现在较低级别的叶节点。这是我们要用伪代码执行的操作:
for each node:
if node is leaf, then return empty array
else return the aggregated children
我发现这很难用SQL表示。相反,我使用了相同的策略,即从最高级别开始,然后一次提升一个级别。但是,当我超过最大级别时,我添加了一些代码来处理叶节点基本情况。
这是我想出的:
WITH RECURSIVE c AS (
SELECT
name,
path,
nlevel(path) AS lvl
FROM node
),
maxlvl AS (
SELECT max(lvl) maxlvl FROM c
),
j AS (
SELECT
c.*,
json '[]' AS children
FROM c, maxlvl
WHERE lvl = maxlvl
UNION ALL
SELECT
(c).*,
CASE
WHEN COUNT(j) > 0 -- check if returned record is null
THEN json_agg(j) -- if not null, aggregate
ELSE json '[]' -- if null, then we are a leaf, so return empty array
END AS children
FROM (
SELECT
c,
CASE
WHEN c.path = subpath(j.path, 0, nlevel(j.path) - 1) -- c is a parent of the child
THEN j
ELSE NULL -- if c is not a parent, return NULL to trigger base case
END AS j
FROM j
JOIN c ON c.lvl = j.lvl - 1
) AS v
GROUP BY v.c
)
SELECT row_to_json(j)::text AS json_tree
FROM j
WHERE lvl = 1;
我的解决方案仅使用path
(以及从路径派生的级别)。不需要name
或id
即可正确递归。
这是我得到的结果(我包括一个节点6,以确保我在同一级别上处理了多个叶节点):
{
"name": "root",
"path": "1",
"lvl": 1,
"children": [
{
"name": "Node 1",
"path": "1.2",
"lvl": 2,
"children": [
{
"name": "Node 5",
"path": "1.2.5",
"lvl": 3,
"children": []
},
{
"name": "Node 3",
"path": "1.2.3",
"lvl": 3,
"children": [
{
"name": "Node 6",
"path": "1.2.3.4",
"lvl": 4,
"children": []
},
{
"name": "Node 4",
"path": "1.2.3.4",
"lvl": 4,
"children": []
}
]
}
]
}
]
}