172 похожих чатов

Привет. На stackoverflow был вопрос про то, как свернуть вложенное

дерево до корневых элементов. Типа, есть дерево категорий вида id, parent_id, name и мы хотим получить первый уровень, у которого в колонке children будут непосредственные предки, у тех в свою очередь их предки и т.д.
Там был предложен подход с рекурсивными cte, когда мы сначала строим дерево с уровнями, потом находим самый нижний уровень и начиная с нижнего тоже рекурсивным cte собираем дерево обратно. Но у этого способа есть проблема, когда разные деревья заканчиваются на разном уровне. В этом случае деревья, которые менее длинные, как-то криво разбиваются на части и получается в итоге дубли корневых категорий с разным набором детей. Может, есть более элегантный вариант? Вот пример такой ситуации: http://sqlfiddle.com/#!17/022f80/10

10 ответов

7 просмотров

Я, например, не понял, что вообще нужно. Вы бы хоть ссылку или более подробную постановку задачи дали... > у которого в колонке children будут непосредственные предки, у тех в свою очередь их предки и т.д. Предки в колонке children?!

Вот http://sqlfiddle.com/#!17/022f80/38/0 Задача сложная из-за того, что слой рекурсии нельзя использовать в OUTER JOIN. Интересно, это только в PostgreSQL такое ограничение?

And-Ve Автор вопроса
alex che
Вот http://sqlfiddle.com/#!17/022f80/38/0 Задача с...

О, решили уже. Но вот это жестоко, конечно ;) FROM ( SELECT branch_parent, branch_child FROM c_with_level branch_parent JOIN c_tree branch_child ON branch_child.lvl - 1 = branch_parent.lvl ) branch Это же, грубо говоря, примерно O(N²)... Я о том, что предыдущий уровень целиком соединяется столько раз, сколько узлов на текущем, нет? > Интересно, это только в PostgreSQL такое ограничение? Это много где, потому что все эти ограничения — из ISO SQL. И PostgreSQL ещё мало запрещает (т.е. далеко не всё, что предписывает стандарт). Насколько я помню, MS SQL запрещает ещё больше, но не всё (ну и нарушает стандарт в "недозапрещённных" местах, т.е. выдаёт по сути неправильные результаты); MariaDB (если я правильно понял) тоже запрещает почти всё (но как-то выполняет mutual recursion, запрещённую в стандарте); sqlite и DB2 запрещают всё прямо по стандарту.

Yaroslav Schekin
О, решили уже. Но вот это жестоко, конечно ;) F...

Можно соединять по двум условиям: равенству ид родителя и соответствию уровня, но тогда придётся начинать не с максимального уровня, а сразу со всех листьев дерева (но фиксировать максимальный уровень для всех листьев), а в рекурсивной части нужен будет left join по ИД и условию уровень родителя равен уровню строки cte - 1, соответственно, если не находим родителя, пробрасываем строки наверх с уменьшенным уровнем. Не придётся соединять весь уровень. Объяснил сумбурно, если совсем непонятно, могу завтра привести запрос.

Radist
Можно соединять по двум условиям: равенству ид род...

Да, я не понял (казалось бы, эти уровни нужны для правильной свёртки). Интересно было бы посмотреть на запрос.

Radist
http://sqlfiddle.com/#!17/b0d7a/6/0

Да, хорошее решение. :) Теперь понятно, спасибо! Вообще удивительно, сколько приходится мучиться с рекурсивными CTE, чтобы получить аналог того, что настоящей рекурсией решается очень просто: ;) -- Функция: CREATE OR REPLACE FUNCTION children_jsonb(_fromparent bigint) RETURNS jsonb LANGUAGE sql STABLE AS $function$ SELECT COALESCE(jsonb_agg(to_jsonb(can) || jsonb_build_object('children', children_jsonb(can.id))), '[]') AS res FROM customer_area_node AS can WHERE can.parent_id = _fromparent; $function$; -- Использование: SELECT id, jsonb_pretty(to_jsonb(can) || jsonb_build_object('children', children_jsonb(can.id))) AS res FROM customer_area_node AS can WHERE can.parent_id IS NULL; Кстати, к вопросу о соответствии / расширению стандарта — в PostgreSQL есть следующий трюк, с помощью которого "перебиваются" вообще все эти ограничения на рекурсивные CTE: WITH RECURSIVE ttree AS ( -- Walk a (sub)forest, top-down, get level (distance from a root) SELECT *, 0 AS level_num FROM customer_area_node AS t WHERE t.parent_id IS NULL UNION ALL SELECT t.*, ttree.level_num + 1 AS level_num FROM ttree JOIN customer_area_node AS t ON t.parent_id = ttree.id ), a_level AS ( -- Walk it bottom-up, level by level SELECT ttree.*, to_jsonb(ttree) || '{"children": []}'::jsonb AS children FROM ttree WHERE ttree.level_num = (SELECT MAX(m.level_num) FROM ttree AS m) UNION ALL ( -- This "trick" violates (or extends?) ISO SQL, but works in PostgreSQL WITH lower_level AS (SELECT * FROM a_level) SELECT ttree.*, to_jsonb(ttree) || jsonb_build_object('children', COALESCE((SELECT jsonb_agg(lower_level.children) FROM lower_level WHERE lower_level.parent_id = ttree.id ), '[]') ) AS children FROM ttree WHERE ttree.level_num = (SELECT MIN(l.level_num) - 1 FROM lower_level AS l) ) ) SELECT *, jsonb_pretty(children) FROM a_level WHERE level_num = 0;

Yaroslav Schekin
Да, хорошее решение. :) Теперь понятно, спасибо! ...

Интересный трюк, надо будет попробовать. По хорошему, можно было бы обойтись простым рекурсивным запросом внутри функции, которая пройдёт по датасету for-ом и соберёт рекурсивный запрос. Но не уверен, что это будет быстрее работать чем сборка рекурсивного json-а на cte. Рекурсивная функция рискует вывалиться в stackoverflow если вдруг дерево окажется слишком глубоким (например, дерево из 1000 элементов вложенных друг в друга)

Radist
Интересный трюк, надо будет попробовать. По хороше...

Да, c рекурсивной функцией в таком случае получится ERROR: stack depth limit exceeded. Я просто имел в виду, что записывается она тривиально, а rCTE-аналоги... как-то нет. ;)

Похожие вопросы

Обсуждают сегодня

лучше скажите, причём тут паскаль?
Alexey Kulakov
36
Добрый вечер. Хочу чтобы у меня в классе поле было функцией, которая возвращает строку. Делаю так: interface ... TGetOutPath = function : String of object; ... protec...
Kirill Filippenok
12
Это может быть все-таки не флудвейт? у меня ботфазер принимает изменения и отображает даже что они изменились, на видео видно что он прислал якобы уже измененное описание, н...
OVERLINK
13
Здравствуйте, хочу сделать HelloWorld в консоли Дельфи, но функция API ничего не выводит, что я делаю не так? program Hello; {$APPTYPE CONSOLE} uses System.SysUtils, WinAPI.Wi...
Sergey Vinogradov
20
Вопрос на перед, на следующую пятницу. Сколько строк кода можно вешать на одного программиста, понятно что если проект хорошо написан то можно и миллион. Но есть же где то пре...
AlekseyK Kluchnikov
31
Немного оффтопа: а кто на чем сидит для осдева в плане ide/редактора? Последнее время сидел на vscode, но я его прям не могу нормально воспринимать, перешел на сlion, но меня...
Evg Resh
29
#include <stdio.h> #include <stdlib.h> #include <time.h> int** generate_table(int size_matrix) { int** matrix = (int**)malloc(size_matrix * sizeof(int*)); for (int i ...
Чувак
1
Яндекс маршрутизатор кто-нибудь использовал с своих проектах, чтоб через него маршруты составить/посчитать? Насколько простой/муторный сервис?
Dreamer_0x01 VeseloV
7
У меня это всегда вопрос вызывало.. Нафига писать код так, чтобы потом ошибки вылавливать?
Nik
44
В связи с "политическим дембелем" ms office кто нибудь пробовал работать с чем то импортозамещенным? Например, Р7. Т. е. задача открыть "импортозамещенный" шаблон, что то в н...
Alex
5
Карта сайта