Postgres с рекурсивным CTE: сортировка / упорядочивание детей по популярности с сохранением древовидной структуры (родители всегда выше детей)
Я строю форум, очень похожий на Reddit / Slashdot, т. е.
- неограниченные уровни вложенности ответов
- популярные комментарии (упорядоченные по лайкам / голосам) поднимутся на вершину (в пределах их собственного уровня вложенности / глубины), но древовидная структура должна быть сохранена (родитель всегда показан непосредственно над дочерними)
Вот пример таблицы и данных:
DROP TABLE IF EXISTS "comments";
CREATE TABLE comments (
id BIGINT PRIMARY KEY,
parent_id BIGINT,
body TEXT NOT NULL,
like_score BIGINT,
depth BIGINT
);
INSERT INTO comments VALUES ( 0, NULL, 'Main top of thread post', 5 , 0 );
INSERT INTO comments VALUES ( 1, 0, 'comment A', 5 , 1 );
INSERT INTO comments VALUES ( 2, 1, 'comment A.A', 3, 2 );
INSERT INTO comments VALUES ( 3, 1, 'comment A.B', 1, 2 );
INSERT INTO comments VALUES ( 9, 3, 'comment A.B.A', 10, 3 );
INSERT INTO comments VALUES ( 10, 3, 'comment A.B.B', 5, 3 );
INSERT INTO comments VALUES ( 11, 3, 'comment A.B.C', 8, 3 );
INSERT INTO comments VALUES ( 4, 1, 'comment A.C', 5, 2 );
INSERT INTO comments VALUES ( 5, 0, 'comment B', 10, 1 );
INSERT INTO comments VALUES ( 6, 5, 'comment B.A', 7, 2 );
INSERT INTO comments VALUES ( 7, 5, 'comment B.B', 5, 2 );
INSERT INTO comments VALUES ( 8, 5, 'comment B.C', 2, 2 );
Вот рекурсивный запрос, который я придумал до сих пор, но я не могу понять, как упорядочить детей, но сохранить древовидная структура (родитель всегда должен быть выше детей)...
WITH RECURSIVE tree AS (
SELECT
ARRAY[]::BIGINT[] AS sortable,
id,
body,
like_score,
depth
FROM "comments"
WHERE parent_id IS NULL
UNION ALL
SELECT
tree.sortable || "comments".like_score || "comments".id,
"comments".id,
"comments".body,
"comments".like_score,
"comments".depth
FROM "comments", tree
WHERE "comments".parent_id = tree.id
)
SELECT * FROM tree
ORDER BY sortable DESC
Это выводит...
+----------------------------------------------------------+
|sortable |id|body |like_score|depth|
+----------------------------------------------------------+
|{10,5,7,6} |6 |comment B.A |7 |2 |
|{10,5,5,7} |7 |comment B.B |5 |2 |
|{10,5,2,8} |8 |comment B.C |2 |2 |
|{10,5} |5 |comment B |10 |1 |
|{5,1,5,4} |4 |comment A.C |5 |2 |
|{5,1,3,2} |2 |comment A.A |3 |2 |
|{5,1,1,3,10,9}|9 |comment A.B.A |10 |3 |
|{5,1,1,3,8,11}|11|comment A.B.C |8 |3 |
|{5,1,1,3,5,10}|10|comment A.B.B |5 |3 |
|{5,1,1,3} |3 |comment A.B |1 |2 |
|{5,1} |1 |comment A |5 |1 |
| |0 |Main top of thread post|5 |0 |
+----------------------------------------------------------+
...однако обратите внимание, что "комментарий B", "комментарий A" и "главная вершина сообщения потока" находятся ниже своих детей? Как сохранить контекстуальный порядок? то есть выход, который я хочу:
+----------------------------------------------------------+
|sortable |id|body |like_score|depth|
+----------------------------------------------------------+
| |0 |Main top of thread post|5 |0 |
|{10,5} |5 |comment B |10 |1 |
|{10,5,7,6} |6 |comment B.A |7 |2 |
|{10,5,5,7} |7 |comment B.B |5 |2 |
|{10,5,2,8} |8 |comment B.C |2 |2 |
|{5,1} |1 |comment A |5 |1 |
|{5,1,5,4} |4 |comment A.C |5 |2 |
|{5,1,3,2} |2 |comment A.A |3 |2 |
|{5,1,1,3} |3 |comment A.B |1 |2 |
|{5,1,1,3,10,9}|9 |comment A.B.A |10 |3 |
|{5,1,1,3,8,11}|11|comment A.B.C |8 |3 |
|{5,1,1,3,5,10}|10|comment A.B.B |5 |3 |
+----------------------------------------------------------+
Я действительно хочу, чтобы пользователи могли Сортировать по нескольким методам:
- Самый популярный первый
- наименее популярный первый
- Самый новый первый
- старейший первый
- etc
...но во всех случаях родители должны быть показаны выше своих детей. Но я просто использую "like_score" здесь в качестве примера, и я должен быть в состоянии выяснить остальное оттуда.
Я провел много часов, исследуя интернет и пробуя вещи сам, и чувствую, что я приближаюсь, но не могу понять эту последнюю часть.2 ответа:
1.
tree.sortable || -"comments".like_score || "comments".id ^ /|\ | |
2.
ORDER BY sortable
WITH RECURSIVE tree AS ( SELECT ARRAY[]::BIGINT[] AS sortable, id, body, like_score, depth FROM "comments" WHERE parent_id IS NULL UNION ALL SELECT tree.sortable || -"comments".like_score || "comments".id, "comments".id, "comments".body, "comments".like_score, "comments".depth FROM "comments", tree WHERE "comments".parent_id = tree.id ) SELECT * FROM tree ORDER BY sortable
+-------------------+----+-------------------------+------------+-------+ | sortable | id | body | like_score | depth | +-------------------+----+-------------------------+------------+-------+ | (null) | 0 | Main top of thread post | 5 | 0 | +-------------------+----+-------------------------+------------+-------+ | {-10,5} | 5 | comment B | 10 | 1 | +-------------------+----+-------------------------+------------+-------+ | {-10,5,-7,6} | 6 | comment B.A | 7 | 2 | +-------------------+----+-------------------------+------------+-------+ | {-10,5,-5,7} | 7 | comment B.B | 5 | 2 | +-------------------+----+-------------------------+------------+-------+ | {-10,5,-2,8} | 8 | comment B.C | 2 | 2 | +-------------------+----+-------------------------+------------+-------+ | {-5,1} | 1 | comment A | 5 | 1 | +-------------------+----+-------------------------+------------+-------+ | {-5,1,-5,4} | 4 | comment A.C | 5 | 2 | +-------------------+----+-------------------------+------------+-------+ | {-5,1,-3,2} | 2 | comment A.A | 3 | 2 | +-------------------+----+-------------------------+------------+-------+ | {-5,1,-1,3} | 3 | comment A.B | 1 | 2 | +-------------------+----+-------------------------+------------+-------+ | {-5,1,-1,3,-10,9} | 9 | comment A.B.A | 10 | 3 | +-------------------+----+-------------------------+------------+-------+ | {-5,1,-1,3,-8,11} | 11 | comment A.B.C | 8 | 3 | +-------------------+----+-------------------------+------------+-------+ | {-5,1,-1,3,-5,10} | 10 | comment A.B.B | 5 | 3 | +-------------------+----+-------------------------+------------+-------+
Проверьте это:
Обратите внимание, что вы можете заменить параметрWITH RECURSIVE tree AS ( SELECT ARRAY[]::BIGINT[] AS sortable, id, body, like_score, depth, lpad(id::text, 2, '0') as path FROM "comments" WHERE parent_id IS NULL UNION ALL SELECT tree.sortable || "comments".like_score || "comments".id, "comments".id, "comments".body, "comments".like_score, "comments".depth, tree.path || '/' || lpad("comments".id::text, 2, '0') as path FROM "comments", tree WHERE "comments".parent_id = tree.id ) SELECT * FROM tree ORDER BY path
2
наlpad
любым количеством цифр, которое вы хотите.