问题
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