How to preserve order of children to appear after their parents

不打扰是莪最后的温柔 提交于 2020-01-07 03:16:08

问题


Expected order by replyid: 55, 57, 58, 59, 60, 56 -- So that the entire 1st parent reply and all its children appear BEFORE the 2nd parent reply The following SQL query returns the wrong order of results

WITH RECURSIVE t(replyid, replypid, depth, path, reply, replied, reply_userid) AS (
    (SELECT replyid, replypid, 0, array[replyid], reply, replied, replies.userid, u.displayname, u.email_address,
        (SELECT COUNT(*) FROM reply_revs WHERE replyid = replies.replyid) AS reply_revs
        FROM replies
        LEFT OUTER JOIN users u ON (replies.userid = u.userid)
        WHERE replypid is NULL AND postid = 31 ORDER BY replied)
    UNION ALL
    (SELECT r.replyid, r.replypid, t.depth+1, t.path || r.replypid, r.reply, r.replied, r.userid, u.displayname, u.email_address,
        (SELECT COUNT(*) FROM reply_revs WHERE replyid = r.replyid)
        FROM replies r
        JOIN t ON (r.replypid = t.replyid)
        LEFT OUTER JOIN users u ON (r.userid = u.userid)
        ORDER BY replied)
) SELECT * FROM t

replyid     replypid    depth   path            reply                               replied 
55      NULL        0       {55}        1st parent reply                    2011-02-13 11:40:48.072148-05
56      NULL        0       {56}        2nd parent reply                    2011-02-13 11:41:00.610033-05
57      55          1       {55,55}     1st child to 1st parent reply           2011-02-13 11:41:26.541024-05
58      55          1       {55,55}     2nd child to 1st parent reply           2011-02-13 11:41:39.485405-05
59      55          1       {55,55}     3rd child to 1st parent reply           2011-02-13 11:41:51.35482-05
60      59          2       {55,55,59}  1st child to 3rd child of 1st parent reply  2011-02-13 11:42:14.866852-05

However, merely tacking on " ORDER BY path" to the end fixes this, but ONLY for ASCENDING order

WITH RECURSIVE t(replyid, replypid, depth, path, reply, replied, reply_userid) AS (
    (SELECT replyid, replypid, 0, array[replyid], reply, replied, replies.userid, u.displayname, u.email_address,
        (SELECT COUNT(*) FROM reply_revs WHERE replyid = replies.replyid) AS reply_revs
        FROM replies
        LEFT OUTER JOIN users u ON (replies.userid = u.userid)
        WHERE replypid is NULL AND postid = 31 ORDER BY replied)
    UNION ALL
    (SELECT r.replyid, r.replypid, t.depth+1, t.path || r.replypid, r.reply, r.replied, r.userid, u.displayname, u.email_address,
        (SELECT COUNT(*) FROM reply_revs WHERE replyid = r.replyid)
        FROM replies r
        JOIN t ON (r.replypid = t.replyid)
        LEFT OUTER JOIN users u ON (r.userid = u.userid)
        ORDER BY replied)
) SELECT * FROM t ORDER BY path

replyid replypid    depth   path            reply                               replied 
55      NULL    0       {55}        1st parent reply                    2011-02-13 11:40:48.072148-05
57      55      1       {55,55}     1st child to 1st parent reply           2011-02-13 11:41:26.541024-05
58      55      1       {55,55}     2nd child to 1st parent reply           2011-02-13 11:41:39.485405-05
59      55      1       {55,55}     3rd child to 1st parent reply           2011-02-13 11:41:51.35482-05
60      59      2       {55,55,59}  1st child to 3rd child of 1st parent reply  2011-02-13 11:42:14.866852-05
56      NULL    0       {56}        2nd parent reply                    2011-02-13 11:41:00.610033-05

So let's try DESCENDING now by instead appending " ORDER BY path DESC" Results are:

replyid replypid    depth   path            reply                               replied 
56      NULL    0       {56}        2nd parent reply                    2011-02-13 11:41:00.610033-05
60      59      2       {55,55,59}  1st child to 3rd child of 1st parent reply  2011-02-13 11:42:14.866852-05
57      55      1       {55,55}     1st child to 1st parent reply           2011-02-13 11:41:26.541024-05
58      55      1       {55,55}     2nd child to 1st parent reply           2011-02-13 11:41:39.485405-05
59      55      1       {55,55}     3rd child to 1st parent reply           2011-02-13 11:41:51.35482-05
55      NULL    0       {55}        1st parent reply                    2011-02-13 11:40:48.072148-05

Now it appears as if the children to the 1st parent reply are children of the 2nd parent reply.

My question is: How can I order the results so that the children or results with depth > 0 ALWAYS appear after their corresponding parents and not after other parent items?

The results I'd like to see:

replyid replypid    depth   path            reply                               replied 
56      NULL    0       {56}        2nd parent reply                    2011-02-13 11:41:00.610033-05
55      NULL    0       {55}        1st parent reply                    2011-02-13 11:40:48.072148-05
57      55      1       {55,55}     1st child to 1st parent reply           2011-02-13 11:41:26.541024-05
58      55      1       {55,55}     2nd child to 1st parent reply           2011-02-13 11:41:39.485405-05
59      55      1       {55,55}     3rd child to 1st parent reply           2011-02-13 11:41:51.35482-05
60      59      2       {55,55,59}  1st child to 3rd child of 1st parent reply  2011-02-13 11:42:14.866852-05

Thanks to RhodiumToad in #postgresql on Freenode I was able to come up with the following PHP and SQL query which works AMAZINGLY!

if (isset($_SESSION["userid"])) {
    $s_col1 = ", (SELECT COUNT(*) FROM votes WHERE replyid = replies.replyid AND userid = %d) AS reply_voted";
    $s_col2 = ", (SELECT COUNT(*) FROM votes WHERE replyid = r.replyid AND userid = %d)";
} else { $s_col1 = ""; $s_col2 = ""; }

if ($sort == "newest") { $s_arr1 = "-extract(epoch from replied)::integer"; $s_arr2 = " || -extract(epoch from r.replied)::integer"; }
else if ($sort == "oldest") { $s_arr1 = "extract(epoch from replied)::integer"; $s_arr2 = " || extract(epoch from r.replied)::integer"; }
else if ($sort == "topvotes") { $s_arr1 = "-votes"; $s_arr2 = " || -r.votes"; }
else { $s_arr1 = ""; $s_arr2 = ""; }

$sql = "WITH RECURSIVE t(replyid, replypid, depth, path, reply, replied, reply_userid) AS (
        (SELECT replyid, replypid, 0, array[$s_arr1,replyid], reply, replied, replies.userid, u.displayname, u.email_address,
            (SELECT COUNT(*) FROM reply_revs WHERE replyid = replies.replyid) AS reply_revs,
            (SELECT COUNT(*) FROM votes WHERE replyid = replies.replyid) AS reply_votes
            $s_col1
        FROM replies
        LEFT OUTER JOIN users u ON (replies.userid = u.userid)
        WHERE replypid is NULL AND postid = %d)
        UNION ALL
        (SELECT r.replyid, r.replypid, t.depth+1, t.path$s_arr2 || r.replyid, r.reply, r.replied, r.userid, u.displayname, u.email_address,
            (SELECT COUNT(*) FROM reply_revs WHERE replyid = r.replyid) AS reply_revs,
            (SELECT COUNT(*) FROM votes WHERE replyid = r.replyid) AS reply_votes
            $s_col2
        FROM replies r
        JOIN t ON (r.replypid = t.replyid)
        LEFT OUTER JOIN users u ON (r.userid = u.userid))
    ) SELECT * FROM t ORDER BY path";

回答1:


You really have two sorts in one on your last query. The parents are able to sort ascending or descending but the children can only sort ascending.

After looking at this I believe you can get a solution with something like this.

   order by case 
        when depth = 0
            then path
    /*
      secret function that always returns the
      right numbers regardless of whether or not the sort is ascending.
    */
        else XXX_function('DESC', path)
    end desc;

I believe the logic is sound but you have to figure out how to replace the numbers on descending sorts since things will be 'upside down'. (Maybe reverse the array positions)




回答2:


What makes a child the first child? If it's the replied date, you have to order by this value as well.



来源:https://stackoverflow.com/questions/4994716/how-to-preserve-order-of-children-to-appear-after-their-parents

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!