Гость
Форумы / PostgreSQL [игнор отключен] [закрыт для гостей] / Набор данных с иерархическими данными в json / 16 сообщений из 16, страница 1 из 1
19.06.2017, 23:24
    #39474495
dake
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Набор данных с иерархическими данными в json
Всем привет! У меня вьюха с данными которые из себя представляет типа дерево(id, parent_id). Как мне лучше перевести эти данные в json. Чтобы получилось типа {"table_id": 0, "branch": "Данные по абоненту", ...., [{"table_id": 1, "branch": "Начисления", ...}, {}, ...]}
Хочу строить на основе этого дерево в Qt.
Заранне спасибо!
...
Рейтинг: 0 / 0
19.06.2017, 23:29
    #39474496
dake
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Набор данных с иерархическими данными в json
вот скрин данных
...
Рейтинг: 0 / 0
20.06.2017, 01:20
    #39474518
p2.
p2.
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Набор данных с иерархическими данными в json
with recursive
dake в Qt. ну и делай "дерево" на клиенте из тех данных, что есть. зачем гонять из пустого в порожнее на стороне сервера.
...
Рейтинг: 0 / 0
20.06.2017, 08:06
    #39474556
dake
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Набор данных с иерархическими данными в json
авториз пустого в порожнее
ну почему сразу так...я думаю вполне приемлимо... а на клиенте простым обходом этой json-структры можно легко построить дерево)
...
Рейтинг: 0 / 0
20.06.2017, 15:59
    #39474875
LeXa NalBat
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Набор данных с иерархическими данными в json
...
Рейтинг: 0 / 0
21.06.2017, 16:14
    #39475617
sereginseregin
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Набор данных с иерархическими данными в json
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
22.06.2017, 15:41
    #39476255
dake
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Набор данных с иерархическими данными в json
sereginseregin,

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

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

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

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

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

т.е. рекомендую попробовать это сделать в немного другом pl . или вернуться к рек-ции p2
...
Рейтинг: 0 / 0
25.06.2017, 16:54
    #39477270
sereginseregin
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Набор данных с иерархическими данными в json
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
26.06.2017, 14:41
    #39477746
dake
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Набор данных с иерархическими данными в json
вообщем решил вопрос рекурсивной функцией

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
26.06.2017, 16:07
    #39477792
LeXa NalBat
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Набор данных с иерархическими данными в json
может быть получится и через рекурсивную функцию на SQL, а не на PL/PGSQL
...
Рейтинг: 0 / 0
27.06.2017, 15:06
    #39478504
sereginseregin
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Набор данных с иерархическими данными в json
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
27.06.2017, 15:48
    #39478535
qwwq
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Набор данных с иерархическими данными в json
sereginsereginПровел экстремальное тестирование
полный скрипт теста от генерации таблиц и данных приведите
, пжлст

тут есть например спойлеры -- для простынок.
...
Рейтинг: 0 / 0
27.06.2017, 16:06
    #39478551
sereginseregin
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Набор данных с иерархическими данными в json
[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
28.06.2017, 08:01
    #39478773
sereginseregin
Гость
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Набор данных с иерархическими данными в json
Из двух вариантов самый быстрый и простой третий, с определением уровня вложенности:
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
Форумы / PostgreSQL [игнор отключен] [закрыт для гостей] / Набор данных с иерархическими данными в json / 16 сообщений из 16, страница 1 из 1
Целевая тема:
Создать новую тему:
Автор:
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


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