[英]Postgres infinite self join
So i have an article, and "comments" on the article.. 所以我有一篇文章,并对文章进行“评论”。
the comment allows people to reply.. and you could reply to the reply.. so on and so forth, meaning the deepest tree root would be N 评论允许人们回复。您可以回复回复。依此类推,依此类推,这意味着最深的树根是N
Quick mockup of what the tables look like 快速模拟表的外观
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)
Is there a way to query the Postgres DB to give me a result of something like 有没有一种方法可以查询Postgres数据库,让我得到类似以下结果
So anything inside the Replies
table that has null parent_id is a "main" comment (aka isn't a reply).. I would love if possible if the children
fields gets populated within itself (ie a reply of a reply) 因此,
Replies
表中任何具有null parent_id为null的东西都是“主要”注释(aka不是答复)。如果可能的话,如果children
字段填充在其自身内(即答复的答复),我将很乐意。
Is this even possible with Postgres? Postgres甚至有可能吗? Or Am i supposed to be fetching all
Replies
joining them with Comments
and then iterating through each one trying to find it's proper desitination? 还是我应该获取所有包含
Comments
Replies
,然后遍历每个尝试以找到适当的目的?
Btw, i'm using GoLang
for my backend and the Gorm
package to access my postgres db 顺便说一句,我正在使用
GoLang
作为我的后端,并使用Gorm
软件包访问我的postgres数据库
EDIT: I'm using this query 编辑:我正在使用此查询
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;
Which results to : 结果为:
Which is a step closer.. however what i need is to have a JSON object returned looking like 离这更近了..但是我需要的是返回一个看起来像的JSON对象
comments: [
{
comment_id: ...,
username: ...,
comment_body: ....,
comment_likes: ....,
children: [...]
},
{
.....
}
]
Where the first items inside the comments
object would be the comments that are NOT a reply, and the children
field should be populated with the replied comments.. and the comments inside the children
should also have their children
populated to replies to that reply 如果
comments
对象中的第一项是不是答复的注释,则children
字段应填充有已答复的注释..并且children
的注释还应填充其children
以对该答复进行答复
Hoping that this is your expected result. 希望这是您的预期结果。 (I did something similar here: https://stackoverflow.com/a/52076212/3984221 )
(我在这里做了类似的事情: https : //stackoverflow.com/a/52076212/3984221 )
demo: db<>fiddle 演示:db <> fiddle
Table comments
: 表
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
表格
replies
id parent_id
-- ---------
a (null)
b a
c b
d a
e (null)
f (null)
g f
h f
i (null)
Result : 结果 :
{
"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
}]
}
Query : 查询 :
Recursion : 递归 :
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
The only recursion part is within CTE parent_tree
. 唯一的递归部分在CTE
parent_tree
。 Here I am searching for the parents and build a path. 在这里,我正在寻找父母并建立一条道路。 This path is needed for inserting the json data later at the right position.
稍后在正确的位置插入json数据时需要此路径。
The second CTE ( json_objects
) builds a json object for each comment with an empty children array where the children can be inserted later. 第二个CTE(
json_objects
)为带有空子项数组的每个注释构建一个json对象,以后可以在其中插入子项。
The LATERAL
join searches the replies table for children of the current id and gives an array with their ids. LATERAL
在答复表中搜索当前ID的子代,并给出一个包含其ID的数组。
The ORDER BY
clause at the end is important. 最后的
ORDER BY
子句很重要。 With this it is ensured that all upper nodes come before the lower nodes (their children). 这样可以确保所有较高的节点都位于较低的节点(它们的子节点)之前。 Otherwise the input into the global json object could fail later because a necessary parent could not exist at the right moment.
否则,到全局json对象的输入可能稍后会失败,因为在适当的时候可能不存在必要的父对象。
Building the final JSON object : 构建最终的JSON对象 :
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;
It is not possible to build the json object within the recursion because within the query the jsondata
object is not a global variable. 无法在递归中构建json对象,因为在查询中
jsondata
对象不是全局变量。 So if I would add b
as child into a
in one recursion branch, it wouldn't exist in another branch where I would add c
as child. 所以,如果我想补充
b
为孩子变成a
在一个递归分支,它不会在另一个分支,其中我想补充存在c
作为孩子。
So it is necessary to generate a global variable. 因此有必要生成一个全局变量。 This could be done in a function.
这可以在一个函数中完成。 With the calculated path and child objects it is really simple to build the final json together: looping through the result set and add the json object into the path of the global object.
使用计算出的路径和子对象,将最终的json一起构建起来非常简单:遍历结果集并将json对象添加到全局对象的路径中。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.