powered by simpleCommunicator - 2.0.53     © 2025 Programmizd 02
Форумы / PostgreSQL [игнор отключен] [закрыт для гостей] / Набор данных с иерархическими данными в json
16 сообщений из 16, страница 1 из 1
Набор данных с иерархическими данными в json
    #39474495
dake
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Всем привет! У меня вьюха с данными которые из себя представляет типа дерево(id, parent_id). Как мне лучше перевести эти данные в json. Чтобы получилось типа {"table_id": 0, "branch": "Данные по абоненту", ...., [{"table_id": 1, "branch": "Начисления", ...}, {}, ...]}
Хочу строить на основе этого дерево в Qt.
Заранне спасибо!
...
Рейтинг: 0 / 0
Набор данных с иерархическими данными в json
    #39474496
dake
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
вот скрин данных
...
Рейтинг: 0 / 0
Набор данных с иерархическими данными в json
    #39474518
p2.
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
with recursive
dake в Qt. ну и делай "дерево" на клиенте из тех данных, что есть. зачем гонять из пустого в порожнее на стороне сервера.
...
Рейтинг: 0 / 0
Набор данных с иерархическими данными в json
    #39474556
dake
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
авториз пустого в порожнее
ну почему сразу так...я думаю вполне приемлимо... а на клиенте простым обходом этой json-структры можно легко построить дерево)
...
Рейтинг: 0 / 0
Набор данных с иерархическими данными в json
    #39474875
LeXa NalBat
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
...
Рейтинг: 0 / 0
Набор данных с иерархическими данными в json
    #39475617
sereginseregin
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
LeXa NalBatнагуглил http://bender.io/2013/09/22/returning-hierarchical-data-in-a-single-sql-query/

В примере:
Код: plsql
1.
...json_agg(p.*) as projects...


агрегируются в массив только одноуровневые проекты, а у автора, на сколько я понимаю, массивы в JSON могут включать данные с массивами.
Без рекурсии такую сборку на SQL не сделать, а рекурсивные запросы Postgres агрегатные функции не понимают.

Чтобы собрать JSON одним запросом (скриптом), можно использовать DO..., в котором обычным циклом собираем JSON, куда-то его сохраняем (PREPARE). После DO... получаем результат через обычный SELECT...

Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
DO $$
DECLARE result_json_arr JSONB[];

BEGIN
	-- собираем JSON  в result_json_arr
        -- ...
        -- ...

        -- подготавливаем запрос для выполнения после DO 
	BEGIN
		EXECUTE 'DEALLOCATE result_sql';
	EXCEPTION
	WHEN invalid_sql_statement_name THEN
	END;
	EXECUTE 'PREPARE result_sql AS SELECT '''||cast(coalesce(result_json_arr, ARRAY[]::JSONB[]) as varchar)||'''::JSONB[]';
END$$;

-- выполняем подготовленный запрос
EXECUTE result_sql;
...
Рейтинг: 0 / 0
Набор данных с иерархическими данными в json
    #39476255
dake
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
sereginseregin,

согласен, в рекурсивной части не получается вызвать агрегатную функцию...
немного не понял по твоему подходу...
что значит авторв котором обычным циклом собираем JSON и все остальное
можешь пояснить
...
Рейтинг: 0 / 0
Набор данных с иерархическими данными в json
    #39476796
sereginseregin
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
dakesereginseregin,
авторв котором обычным циклом собираем JSON и все остальное
можешь пояснить

Конечно такое лучше готовым скриптом показать, но времени нет его отрабатывать:
1. Выбираем в начальный МАССИВ (JSON элементов) записи у которых нет дочерних

2. В цикле, пока новые родительские записи по МАССИВУ отбираются:
2.1. добавляем в МАССИВ родительские записи с вложенными дочерними

3. Фильтруем полученный МАССИВ (JSON элементов), отбирая только те записи, у которых нет родителей.
...
Рейтинг: 0 / 0
Набор данных с иерархическими данными в json
    #39476861
qwwq
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
sereginseregin2. В цикле, пока новые родительские записи по МАССИВУ отбираются:
2.1. добавляем в МАССИВ родительские записи с вложенными дочерними
в plpgsql это дорого. (именно поэтому реализованы агрегаты типа str_agg() и array_agg() )

//если что-то тут изменилось -- прошу all поправить.

т.е. рекомендую попробовать это сделать в немного другом pl . или вернуться к рек-ции p2
...
Рейтинг: 0 / 0
Набор данных с иерархическими данными в json
    #39477270
sereginseregin
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
sereginseregin2. В цикле, пока новые родительские записи по МАССИВУ отбираются:
2.1. добавляем в МАССИВ родительские записи с вложенными дочерними

Порядок обхода записей пришлось усложнить

qwwqв plpgsql это дорого. (именно поэтому реализованы агрегаты типа str_agg() и array_agg() )


без array_agg() никуда, но все равно сборка в JSON сложная и дорогая. Цикл нужен для эмуляции рекурсии.

Добил код, интересны Ваши замечания:
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
-- 0. Эмуляция таблицы с данными
CREATE OR REPLACE TEMP VIEW my_data AS SELECT * FROM (VALUES(1,0), (2,1), (3,1), (4,2), (5,2)) AS a(id, parent_id);


DO $$
DECLARE result JSONB[];
	childs JSONB[];
	tempp JSONB[];
BEGIN
	-- 1. Закидываем все записи во временный массив JSONB
	SELECT 
		INTO tempp 
		array_agg(jsonb_build_object('id', id, 'parent_id', parent_id, 'childs', ARRAY[]::JSONB[])) 
	FROM my_data; 

	-- 2. Цикл, пока временный массив не пустой
	WHILE array_length(tempp, 1)>0 LOOP

		-- 2.1. Выбираем крайние дочерние записи (не имеющие других дочерних) 
		SELECT INTO childs array_agg(el) 
		FROM unnest(tempp) AS a(el)
		WHERE el->'id' NOT IN (SELECT el->'parent_id' FROM unnest(tempp) AS a(el))
		;

		-- 2.2. Вкладываем крайние дочерние элементы в родительские
		SELECT INTO tempp array_agg(jsonb_build_object(
				'id', el->'id'
				, 'parent_id', el->'parent_id'
				, 'childs', (SELECT array_agg(el) FROM (SELECT child.el FROM jsonb_array_elements(el->'childs') AS child(el) UNION SELECT child.el FROM unnest(childs) AS child(el) WHERE parent.el->'id'=child.el->'parent_id') AS a)
				))  
		FROM unnest(tempp) AS parent(el)
		;

		-- 2.3. Добавляем в результирующий массив одинокие элементы (не имеющие родителей и потомков)
		SELECT INTO result array_agg(el) 
		FROM (	SELECT el FROM unnest(result) AS a(el)

			UNION 

			SELECT el FROM unnest(tempp) AS a(el)
			WHERE el->'id' NOT IN (SELECT el->'parent_id' FROM unnest(tempp) AS a(el))
				AND el->'parent_id' NOT IN (SELECT el->'id' FROM unnest(tempp) AS a(el))
		) AS a
		;

		-- 2.4. Удаляем из временного массива элементы не попавшие в дочерние и результат
		SELECT INTO tempp array_agg(el) 
		FROM unnest(tempp) AS a(el)
		WHERE el->'id' NOT IN (SELECT el->'id' FROM unnest(childs) AS a(el))
			AND el->'id' NOT IN (SELECT el->'id' FROM unnest(result) AS a(el))
		;
		

	END LOOP ;


	-- 3. Подготавливаем запрос для выполнения после DO
	BEGIN
		EXECUTE 'DEALLOCATE result_sql';
	EXCEPTION
	WHEN invalid_sql_statement_name THEN
	END;
	EXECUTE 'PREPARE result_sql AS SELECT el FROM unnest('''||cast(coalesce(result, ARRAY[]::JSONB[]) as varchar)||'''::JSONB[]) AS a(el)';
END$$;
-- 4. Выполняем подготовленный запрос
EXECUTE result_sql;
...
Рейтинг: 0 / 0
Набор данных с иерархическими данными в json
    #39477746
dake
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
вообщем решил вопрос рекурсивной функцией

CREATE OR REPLACE FUNCTION storage.get_tree_json (
p_id bigint = 1
)
RETURNS jsonb AS
$body$
declare
node record;
res jsonb;
childs jsonb;
begin
/* получение данных для определенного(current) узла*/
select row_to_json(t) into res from storage.view_abn_tree t where t.id = p_id;
childs:= '[]'::jsonb;
/*перебираем дочерние узлы*/
for node in select * from storage.view_abn_tree t where t.parent_id = p_id loop
/*рекурсивный вызов нашей функции для дочерних узлов*/
select jsonb_set(childs, format('{%s}', node.id)::text[], storage.get_tree_json(node.id), true) into childs;
end loop;
/* полученный на выходе из рекурсивного вызова json-массив с дочерними узлами добавляем в данные current узла*/
select jsonb_set(res, '{childs}', childs, true) into res;
return res;
end;
$body$
LANGUAGE 'plpgsql'
VOLATILE

на мой взгляд довольно таки элгегантно))
...
Рейтинг: 0 / 0
Набор данных с иерархическими данными в json
    #39477792
LeXa NalBat
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
может быть получится и через рекурсивную функцию на SQL, а не на PL/PGSQL
...
Рейтинг: 0 / 0
Набор данных с иерархическими данными в json
    #39478504
sereginseregin
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
dakeвообщем решил вопрос рекурсивной функцией...
Провел экстремальное тестирование твоего и своего вариантов с глубоким вложением элементов для таблицы:
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
CREATE OR REPLACE TEMP VIEW view_abn_tree AS
	WITH RECURSIVE t AS (SELECT 1 AS id, 0 AS parent_id 
			UNION
			SELECT id+1 AS id, id As parent_id
			FROM t
			WHERE id<800
			)
	SELECT * FROM t;



У тебя до 2 сек, но для id>900 не работает, требует увеличить "max_stack_depth" (currently 2048kB).
У меня в 20 раз оказался медленнее из-за множества вложенных запросов, что естественно надо оптимизировать.

Нашел у себя косяк, строку в 2.2:
Код: plsql
1.
, 'childs', (SELECT array_agg(el) FROM (SELECT child.el FROM jsonb_array_elements(el->'childs') AS child(el) UNION SELECT child.el FROM unnest(childs) AS child(el) WHERE parent.el->'id'=child.el->'parent_id') AS a)


заменить на:
Код: plsql
1.
, 'childs', coalesce((SELECT array_agg(el) FROM (SELECT child.el FROM jsonb_array_elements(el->'childs') AS child(el) UNION SELECT child.el FROM unnest(childs) AS child(el) WHERE parent.el->'id'=child.el->'parent_id') AS a),ARRAY[]::JSONB[])
...
Рейтинг: 0 / 0
Набор данных с иерархическими данными в json
    #39478535
qwwq
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
sereginsereginПровел экстремальное тестирование
полный скрипт теста от генерации таблиц и данных приведите
, пжлст

тут есть например спойлеры -- для простынок.
...
Рейтинг: 0 / 0
Набор данных с иерархическими данными в json
    #39478551
sereginseregin
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
[quot qwwq]
полный скрипт теста от генерации таблиц и данных приведите

Скрипт тестирования кода sereginseregin

Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
CREATE OR REPLACE TEMP VIEW my_data 
AS
	WITH RECURSIVE t AS (SELECT 1 AS id, 0 AS parent_id 
			UNION
			SELECT id+1 AS id, id As parent_id
			FROM t
			WHERE id<800
			)
	SELECT * FROM t;

DO $$
DECLARE result JSONB[];
	childs JSONB[];
	tempp JSONB[];
BEGIN
	-- Закидываем все записи во временный массив JSONB
	SELECT 
		INTO tempp 
		array_agg(jsonb_build_object('id', id, 'parent_id', parent_id, 'childs', ARRAY[]::JSONB[])) 
	FROM my_data; 

	-- Цикл, пока временный массив не пустой
	WHILE array_length(tempp, 1)>0 LOOP

		-- Выбираем крайние дочерние записи (не имеющие других дочерних) 
		SELECT INTO childs array_agg(el) 
		FROM unnest(tempp) AS a(el)
		WHERE el->'id' NOT IN (SELECT el->'parent_id' FROM unnest(tempp) AS a(el))
		;

		RAISE NOTICE '%', cast(tempp As varchar);


		-- Вкладываем крайние дочерние элементы в родительские
		SELECT INTO tempp array_agg(jsonb_build_object(
				'id', el->'id'
				, 'parent_id', el->'parent_id'
				, 'childs', coalesce((SELECT array_agg(el) FROM (SELECT child.el FROM jsonb_array_elements(el->'childs') AS child(el) UNION SELECT child.el FROM unnest(childs) AS child(el) WHERE parent.el->'id'=child.el->'parent_id') AS a),ARRAY[]::JSONB[])
				))  
		FROM unnest(tempp) AS parent(el)
		;

		-- Добавляем в результирующий массив одинокие элементы (не имеющие родителей и потомков)
		SELECT INTO result array_agg(el) 
		FROM (	SELECT el FROM unnest(result) AS a(el)

			UNION 

			SELECT el FROM unnest(tempp) AS a(el)
			WHERE el->'id' NOT IN (SELECT el->'parent_id' FROM unnest(tempp) AS a(el))
				AND el->'parent_id' NOT IN (SELECT el->'id' FROM unnest(tempp) AS a(el))
		) AS a
		;

		-- Удаляем из временного массива элементы не попавшие в дочерние и результат
		SELECT INTO tempp array_agg(el) 
		FROM unnest(tempp) AS a(el)
		WHERE el->'id' NOT IN (SELECT el->'id' FROM unnest(childs) AS a(el))
			AND el->'id' NOT IN (SELECT el->'id' FROM unnest(result) AS a(el))
		;
		

	END LOOP ;


	BEGIN
		EXECUTE 'DEALLOCATE result_sql';
	EXCEPTION
	WHEN invalid_sql_statement_name THEN
	END;
	EXECUTE 'PREPARE result_sql AS SELECT el FROM unnest('''||cast(coalesce(result, ARRAY[]::JSONB[]) as varchar)||'''::JSONB[]) AS a(el)';
END$$;
EXECUTE result_sql;




Скрипт тестирования кода dake
из функции убрал схему storage, чтоб на временную таблицу с именем view_abn_tree ссылаться:
Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
CREATE OR REPLACE FUNCTION get_tree_json(p_id bigint DEFAULT 1)
  RETURNS jsonb AS
$BODY$
declare
node record;
res jsonb;
childs jsonb;
begin
/* получение данных для определенного(current) узла*/
select row_to_json(t) into res from view_abn_tree t where t.id = p_id;
childs:= '[]'::jsonb;
/*перебираем дочерние узлы*/
for node in select * from view_abn_tree t where t.parent_id = p_id loop
/*рекурсивный вызов нашей функции для дочерних узлов*/
select jsonb_set(childs, format('{%s}', node.id)::text[], get_tree_json(node.id), true) into childs;
end loop;
/* полученный на выходе из рекурсивного вызова json-массив с дочерними узлами добавляем в данные current узла*/
select jsonb_set(res, '{childs}', childs, true) into res;
return res;
end;
$BODY$
  LANGUAGE plpgsql VOLATILE
  ;



сам тест:

Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
CREATE OR REPLACE TEMP VIEW view_abn_tree AS
	WITH RECURSIVE t AS (SELECT 1 AS id, 0 AS parent_id 
			UNION
			SELECT id+1 AS id, id As parent_id
			FROM t
			WHERE id<800
			)
	SELECT * FROM t;


SELECT get_tree_json()


...
Рейтинг: 0 / 0
Набор данных с иерархическими данными в json
    #39478773
sereginseregin
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Из двух вариантов самый быстрый и простой третий, с определением уровня вложенности:
1. Закидываем все записи во временный массив JSONB с указанием уровня вложенности
2. Цикл по уровням вложенности от MAX до 0
2.1. Вкладываем крайние дочерние элементы в родительские (текущего уровня)


Код: plsql
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
-- 0. Эмуляция таблицы с данными
CREATE OR REPLACE TEMP VIEW view_abn_tree 
AS
	WITH RECURSIVE t AS (SELECT 1 AS id, 0 AS parent_id 
			UNION
			SELECT id+1 AS id, id As parent_id
			FROM t
			WHERE id<1000
			)
	SELECT * FROM t;

DO $$
DECLARE 
	max_level integer;
	tempp JSONB[];
	result JSONB[];
BEGIN
	-- 1. Закидываем все записи во временный массив JSONB с указанием уровня вложенности
	WiTH RECURSIVE t AS (
		SELECT *, 0 as level_id FROM view_abn_tree WHERE parent_id=0
		UNION
		SELECT child.*, parent.level_id+1 AS level_id
		FROM t AS parent
		INNER JOIN view_abn_tree AS child ON child.parent_id=parent.id 

		)
	SELECT 
		INTO 
			tempp
			, max_level 

			array_agg(jsonb_build_object('id', id, 'parent_id', parent_id, 'level_id', level_id, 'childs', ARRAY[]::JSONB[])) 
			, max(level_id)
	FROM t; 
	max_level=coalesce(max_level,0);

	-- 2. Цикл по уровням вложенности от MAX до 0
	result=ARRAY[]::JSONB[];
	FOR current_level IN REVERSE max_level..0 LOOP

		--RAISE NOTICE '%', cast(current_level As varchar);
		--RAISE NOTICE '%', cast((SELECT array_agg(el) AS els FROM unnest(result) AS child(el) ) As varchar);

		-- 2.1. Вкладываем крайние дочерние элементы в родительские (текущего уровня)
		SELECT INTO result array_agg(jsonb_build_object(
				'id', parent.el->'id'
				, 'parent_id', parent.el->'parent_id'
				, 'level_id', parent.el->'level_id'
				, 'childs', coalesce(child.els,ARRAY[]::JSONB[])
				))  
		FROM unnest(tempp) AS parent(el)
		
		LEFT JOIN (SELECT el->'parent_id' AS parent_id, array_agg(el) AS els 
			FROM unnest(result) AS child(el) 
			GROUP BY el->'parent_id'
			) AS child ON child.parent_id=parent.el->'id'
			 
		WHERE cast(parent.el->>'level_id' AS integer)=current_level
		;
		
		--RAISE NOTICE '%', cast(result As varchar);

	END LOOP;

	-- 3. Подготавливаем запрос для выполнения после DO
	BEGIN
		EXECUTE 'DEALLOCATE result_sql';
	EXCEPTION
	WHEN invalid_sql_statement_name THEN
	END;
	EXECUTE 'PREPARE result_sql AS SELECT el FROM unnest('''||cast(coalesce(result, ARRAY[]::JSONB[]) as varchar)||'''::JSONB[]) AS a(el)';
END$$;
-- 4. Выполняем подготовленный запрос
EXECUTE result_sql;

...
Рейтинг: 0 / 0
16 сообщений из 16, страница 1 из 1
Форумы / PostgreSQL [игнор отключен] [закрыт для гостей] / Набор данных с иерархическими данными в json
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]