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

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

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

10 ответов

8 просмотров

Я, например, не понял, что вообще нужно. Вы бы хоть ссылку или более подробную постановку задачи дали... > у которого в колонке 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-аналоги... как-то нет. ;)

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

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

30500 за редактор? )
Владимир
47
а через ESC-код ?
Alexey Kulakov
29
Чёт не понял, я ж правильной функцией воспользовался чтобы вывести отладочную информацию? но что-то она не ловится
notme
18
Добрый день! Скажите пожалуйста, а какие программы вы бы рекомендовали написать для того, чтобы научиться управлять памятью? Можно написать динамический массив, можно связный ...
Филипп
7
У меня есть функция где происходит это: write_bit(buffer, 1); write_bit(buffer, 0); write_bit(buffer, 1); write_bit(buffer, 1); write_bit(buffer, 1); w...
~
14
Недавно Google Project Zero нашёл багу в SQLite с помощью LLM, о чём достаточно было шумно в определённых интернетах, которые сопровождались рассказами, что скоро всех "ибешни...
Alex Sherbakov
5
Ребят в СИ можно реализовать ООП?
Николай
33
Как передать управляющий символ в открытую через CreateProcess консоль? Собсна, есть процедура: procedure TRedirectThread.WriteData(Data: OEMString); var Written: Cardinal;...
Serjone
6
https://github.com/erlang/otp/blob/OTP-27.1/lib/kernel/src/logger_h_common.erl#L174 https://github.com/erlang/otp/blob/OTP-27.1/lib/kernel/src/logger_olp.erl#L76 15 лет назад...
Maksim Lapshin
20
Всем привет! Имеется функция: function IsValidChar(ch: UTF8Char): Boolean; var i: Integer; ValidChars: AnsiString; begin ValidChars := 'abcdefghijklmnopqrstuvwxyzABCDE...
Евгений
44
Карта сайта