简体   繁体   English

Postgres无限自我加入

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM