Postgres无限自我加入
Postgres infinite self join
所以我有一篇文章,并且 "comments" 在文章上..
评论允许人们回复..你可以回复回复..等等,这意味着最深的树根是N
table 外观的快速模型
Comments(id, news_id, user_id, body, likes)
Replies(id, parent_id) --> id here is = Comments.id
User(id, username, password)
News(id, title, body, image)
有没有办法查询 Postgres 数据库给我类似
的结果
所以 Replies
table 中任何有 null parent_id 的东西都是 "main" 评论(也不是回复).. 如果可能的话我会很高兴如果 children
字段在自身内部填充(即回复的回复)
这甚至可以用 Postgres 实现吗?或者我是否应该获取所有 Replies
并用 Comments
加入它们,然后遍历每个试图找到它的正确目的地?
顺便说一句,我正在使用 GoLang
作为我的后端和 Gorm
包来访问我的 postgres db
编辑:
我正在使用此查询
with recursive commentss as (
select r.id, r.parent, array[r.id] as all_parents,
c.body, u.username
from replies r
inner join comments c
on c.id = r.id
join users u
on u.id = c.user_refer
where (parent <> '') IS NOT TRUE
union all
select r.id, r.parent, c.all_parents || r.id,
co.body, u.username
from replies r
join comments co
on co.id = r.id
join users u
on u.id = co.user_refer
join commentss c
on r.parent = c.id
and r.id <> ALL (c.all_parents)
)
select * from commentss order by all_parents;
结果为:
这更近了一步..然而我需要的是让一个 JSON 对象返回看起来像
comments: [
{
comment_id: ...,
username: ...,
comment_body: ....,
comment_likes: ....,
children: [...]
},
{
.....
}
]
comments
对象中的第一个项目是不是回复的评论,children
字段应该填充回复的评论..和 children
也应该将他们的 children
填充到对该回复
的回复中
希望这是您的预期结果。 (我在这里做了类似的事情:)
Table comments
:
id body user_id likes
-- ------------ ------- -----
a foo 1 1
b foofoo 1 232
c foofoofoo 1 23232
d fooFOO 1 53
e cookies 1 864
f bar 1 44
g barbar 1 54
h barBAR 1 222
i more cookies 1 1
Table replies
id parent_id
-- ---------
a (null)
b a
c b
d a
e (null)
f (null)
g f
h f
i (null)
结果:
{
"comments": [{
"children": [],
"username": "Mike Tyson",
"comment_id": "i",
"comment_body": "more cookies",
"comment_likes": 1
},
{
"children": [{
"children": [],
"username": "Mike Tyson",
"comment_id": "b",
"comment_body": "foofoo",
"comment_likes": 232
},
{
"children": [{
"children": [],
"username": "Mike Tyson",
"comment_id": "c",
"comment_body": "foofoofoo",
"comment_likes": 23232
}],
"username": "Mike Tyson",
"comment_id": "d",
"comment_body": "fooFOO",
"comment_likes": 53
}],
"username": "Mike Tyson",
"comment_id": "a",
"comment_body": "foo",
"comment_likes": 1
},
{
"children": [],
"username": "Mike Tyson",
"comment_id": "e",
"comment_body": "cookies",
"comment_likes": 864
},
{
"children": [{
"children": [],
"username": "Mike Tyson",
"comment_id": "g",
"comment_body": "barbar",
"comment_likes": 54
},
{
"children": [],
"username": "Mike Tyson",
"comment_id": "h",
"comment_body": "barBAR",
"comment_likes": 222
}],
"username": "Mike Tyson",
"comment_id": "f",
"comment_body": "bar",
"comment_likes": 44
}]
}
查询:
递归:
WITH RECURSIVE parent_tree AS (
SELECT
id,
NULL::text[] as parent_id,
array_append('{comments}'::text[], (row_number() OVER ())::text) as path,
rc.children
FROM replies r
LEFT JOIN LATERAL (SELECT parent_id, ARRAY_AGG(id) as children FROM replies WHERE parent_id = r.id GROUP BY parent_id) rc ON rc.parent_id = r.id
WHERE r.parent_id IS NULL
UNION
SELECT
r.id,
array_append(pt.parent_id, r.parent_id),
array_append(array_append(pt.path, 'children'), (row_number() OVER (PARTITION BY pt.parent_id))::text),
rc.children
FROM parent_tree pt
JOIN replies r ON r.id = ANY(pt.children)
LEFT JOIN LATERAL (SELECT parent_id, ARRAY_AGG(id) as children FROM replies WHERE parent_id = r.id GROUP BY parent_id) rc ON rc.parent_id = r.id
), json_objects AS (
SELECT c.id, jsonb_build_object('children', '[]'::jsonb, 'comment_id', c.id, 'username', u.name, 'comment_body', c.body, 'comment_likes', c.likes) as jsondata
FROM comments c
LEFT JOIN users u ON u.id = c.user_id
)
SELECT
parent_id,
path,
jsondata
FROM parent_tree pt
LEFT JOIN json_objects jo ON pt.id = jo.id
ORDER BY parent_id NULLS FIRST
唯一的递归部分在 CTE parent_tree
中。在这里,我正在搜索 parents 并构建一条路径。稍后在正确位置插入 json 数据时需要此路径。
第二个 CTE (json_objects
) 为每个带有空 children 数组的评论构建 json object,其中 children 可以是稍后插入。
LATERAL
连接在回复table 中搜索当前 ID 的 children,并给出一个包含其 ID 的数组。
末尾的 ORDER BY
子句很重要。这样可以确保所有上层节点都在下层节点之前(它们的 children)。否则输入到全局 json object 之后可能会失败,因为必要的 parent 在正确的时刻不存在。
构建最终JSONobject:
CREATE OR REPLACE FUNCTION json_tree() RETURNS jsonb AS $$
DECLARE
_json_output jsonb;
_temprow record;
BEGIN
SELECT
jsonb_build_object('comments', '[]'::jsonb)
INTO _json_output;
FOR _temprow IN
-- <query above>
LOOP
SELECT jsonb_insert(_json_output, _temprow.path, _temprow.jsondata) INTO _json_output;
END LOOP;
RETURN _json_output;
END;
$$ LANGUAGE plpgsql;
无法在递归中构建 json object,因为在查询中 jsondata
object 不是全局变量。因此,如果我将 b
作为 child 添加到一个递归分支中的 a
中,它不会存在于我将添加 c
作为 child 的另一个分支中.
所以需要生成一个全局变量。这可以在函数中完成。使用计算出的路径和 child object 一起构建最终的 json 非常简单:遍历结果集并将 json object 添加到全局路径 object。
所以我有一篇文章,并且 "comments" 在文章上..
评论允许人们回复..你可以回复回复..等等,这意味着最深的树根是N
table 外观的快速模型
Comments(id, news_id, user_id, body, likes)
Replies(id, parent_id) --> id here is = Comments.id
User(id, username, password)
News(id, title, body, image)
有没有办法查询 Postgres 数据库给我类似
的结果所以 Replies
table 中任何有 null parent_id 的东西都是 "main" 评论(也不是回复).. 如果可能的话我会很高兴如果 children
字段在自身内部填充(即回复的回复)
这甚至可以用 Postgres 实现吗?或者我是否应该获取所有 Replies
并用 Comments
加入它们,然后遍历每个试图找到它的正确目的地?
顺便说一句,我正在使用 GoLang
作为我的后端和 Gorm
包来访问我的 postgres db
编辑: 我正在使用此查询
with recursive commentss as (
select r.id, r.parent, array[r.id] as all_parents,
c.body, u.username
from replies r
inner join comments c
on c.id = r.id
join users u
on u.id = c.user_refer
where (parent <> '') IS NOT TRUE
union all
select r.id, r.parent, c.all_parents || r.id,
co.body, u.username
from replies r
join comments co
on co.id = r.id
join users u
on u.id = co.user_refer
join commentss c
on r.parent = c.id
and r.id <> ALL (c.all_parents)
)
select * from commentss order by all_parents;
结果为:
这更近了一步..然而我需要的是让一个 JSON 对象返回看起来像
comments: [
{
comment_id: ...,
username: ...,
comment_body: ....,
comment_likes: ....,
children: [...]
},
{
.....
}
]
comments
对象中的第一个项目是不是回复的评论,children
字段应该填充回复的评论..和 children
也应该将他们的 children
填充到对该回复
希望这是您的预期结果。 (我在这里做了类似的事情:
Table comments
:
id body user_id likes
-- ------------ ------- -----
a foo 1 1
b foofoo 1 232
c foofoofoo 1 23232
d fooFOO 1 53
e cookies 1 864
f bar 1 44
g barbar 1 54
h barBAR 1 222
i more cookies 1 1
Table replies
id parent_id
-- ---------
a (null)
b a
c b
d a
e (null)
f (null)
g f
h f
i (null)
结果:
{
"comments": [{
"children": [],
"username": "Mike Tyson",
"comment_id": "i",
"comment_body": "more cookies",
"comment_likes": 1
},
{
"children": [{
"children": [],
"username": "Mike Tyson",
"comment_id": "b",
"comment_body": "foofoo",
"comment_likes": 232
},
{
"children": [{
"children": [],
"username": "Mike Tyson",
"comment_id": "c",
"comment_body": "foofoofoo",
"comment_likes": 23232
}],
"username": "Mike Tyson",
"comment_id": "d",
"comment_body": "fooFOO",
"comment_likes": 53
}],
"username": "Mike Tyson",
"comment_id": "a",
"comment_body": "foo",
"comment_likes": 1
},
{
"children": [],
"username": "Mike Tyson",
"comment_id": "e",
"comment_body": "cookies",
"comment_likes": 864
},
{
"children": [{
"children": [],
"username": "Mike Tyson",
"comment_id": "g",
"comment_body": "barbar",
"comment_likes": 54
},
{
"children": [],
"username": "Mike Tyson",
"comment_id": "h",
"comment_body": "barBAR",
"comment_likes": 222
}],
"username": "Mike Tyson",
"comment_id": "f",
"comment_body": "bar",
"comment_likes": 44
}]
}
查询:
递归:
WITH RECURSIVE parent_tree AS (
SELECT
id,
NULL::text[] as parent_id,
array_append('{comments}'::text[], (row_number() OVER ())::text) as path,
rc.children
FROM replies r
LEFT JOIN LATERAL (SELECT parent_id, ARRAY_AGG(id) as children FROM replies WHERE parent_id = r.id GROUP BY parent_id) rc ON rc.parent_id = r.id
WHERE r.parent_id IS NULL
UNION
SELECT
r.id,
array_append(pt.parent_id, r.parent_id),
array_append(array_append(pt.path, 'children'), (row_number() OVER (PARTITION BY pt.parent_id))::text),
rc.children
FROM parent_tree pt
JOIN replies r ON r.id = ANY(pt.children)
LEFT JOIN LATERAL (SELECT parent_id, ARRAY_AGG(id) as children FROM replies WHERE parent_id = r.id GROUP BY parent_id) rc ON rc.parent_id = r.id
), json_objects AS (
SELECT c.id, jsonb_build_object('children', '[]'::jsonb, 'comment_id', c.id, 'username', u.name, 'comment_body', c.body, 'comment_likes', c.likes) as jsondata
FROM comments c
LEFT JOIN users u ON u.id = c.user_id
)
SELECT
parent_id,
path,
jsondata
FROM parent_tree pt
LEFT JOIN json_objects jo ON pt.id = jo.id
ORDER BY parent_id NULLS FIRST
唯一的递归部分在 CTE parent_tree
中。在这里,我正在搜索 parents 并构建一条路径。稍后在正确位置插入 json 数据时需要此路径。
第二个 CTE (json_objects
) 为每个带有空 children 数组的评论构建 json object,其中 children 可以是稍后插入。
LATERAL
连接在回复table 中搜索当前 ID 的 children,并给出一个包含其 ID 的数组。
末尾的 ORDER BY
子句很重要。这样可以确保所有上层节点都在下层节点之前(它们的 children)。否则输入到全局 json object 之后可能会失败,因为必要的 parent 在正确的时刻不存在。
构建最终JSONobject:
CREATE OR REPLACE FUNCTION json_tree() RETURNS jsonb AS $$
DECLARE
_json_output jsonb;
_temprow record;
BEGIN
SELECT
jsonb_build_object('comments', '[]'::jsonb)
INTO _json_output;
FOR _temprow IN
-- <query above>
LOOP
SELECT jsonb_insert(_json_output, _temprow.path, _temprow.jsondata) INTO _json_output;
END LOOP;
RETURN _json_output;
END;
$$ LANGUAGE plpgsql;
无法在递归中构建 json object,因为在查询中 jsondata
object 不是全局变量。因此,如果我将 b
作为 child 添加到一个递归分支中的 a
中,它不会存在于我将添加 c
作为 child 的另一个分支中.
所以需要生成一个全局变量。这可以在函数中完成。使用计算出的路径和 child object 一起构建最终的 json 非常简单:遍历结果集并将 json object 添加到全局路径 object。