Список привязок к графику JSON с Postgres
У меня есть следующая схема для таблицы тегов:
CREATE TABLE tags (
id integer NOT NULL,
name character varying(255) NOT NULL,
parent_id integer
);
Мне нужно построить запрос, чтобы вернуть следующую структуру (здесь представлен как yaml для удобочитаемости):
- name: Ciencia
parent_id:
id: 7
children:
- name: Química
parent_id: 7
id: 9
children: []
- name: Biología
parent_id: 7
id: 8
children:
- name: Botánica
parent_id: 8
id: 19
children: []
- name: Etología
parent_id: 8
id: 18
children: []
После некоторых проб и ошибок и поиска похожих вопросов в SO, я придумал этот запрос:
WITH RECURSIVE tagtree AS (
SELECT tags.name, tags.parent_id, tags.id, json '[]' children
FROM tags
WHERE NOT EXISTS (SELECT 1 FROM tags tt WHERE tt.parent_id = tags.id)
UNION ALL
SELECT (tags).name, (tags).parent_id, (tags).id, array_to_json(array_agg(tagtree)) children FROM (
SELECT tags, tagtree
FROM tagtree
JOIN tags ON tagtree.parent_id = tags.id
) v
GROUP BY v.tags
)
SELECT array_to_json(array_agg(tagtree)) json
FROM tagtree
WHERE parent_id IS NULL
Но он возвращает следующие результаты при преобразовании в yaml:
- name: Ciencia
parent_id:
id: 7
children:
- name: Química
parent_id: 7
id: 9
children: []
- name: Ciencia
parent_id:
id: 7
children:
- name: Biología
parent_id: 7
id: 8
children:
- name: Botánica
parent_id: 8
id: 19
children: []
- name: Etología
parent_id: 8
id: 18
children: []
Корень node дублируется.
Я мог бы объединить результаты с ожидаемым результатом в моем коде приложения, но я чувствую, что я близко, и это может быть сделано al из PG.
Вот пример с SQL Fiddle:
http://sqlfiddle.com/#!15/1846e/1/0
Ожидаемый результат:
https://gist.github.com/maca/e7002eb10f36fcdbc51b
Фактический выход:
https://gist.github.com/maca/78e84fb7c05ff23f07f4
Ответы
Ответ 1
Вот решение, использующее PLV8 для вашей схемы.
Сначала создайте материализованный путь, используя функцию PLSQL и рекурсивные CTE.
CREATE OR REPLACE FUNCTION get_children(tag_id integer)
RETURNS json AS $$
DECLARE
result json;
BEGIN
SELECT array_to_json(array_agg(row_to_json(t))) INTO result
FROM (
WITH RECURSIVE tree AS (
SELECT id, name, ARRAY[]::INTEGER[] AS ancestors
FROM tags WHERE parent_id IS NULL
UNION ALL
SELECT tags.id, tags.name, tree.ancestors || tags.parent_id
FROM tags, tree
WHERE tags.parent_id = tree.id
) SELECT id, name, ARRAY[]::INTEGER[] AS children FROM tree WHERE $1 = tree.ancestors[array_upper(tree.ancestors,1)]
) t;
RETURN result;
END;
$$ LANGUAGE plpgsql;
Затем построим дерево из вывода указанной выше функции.
CREATE OR REPLACE FUNCTION get_tree(data json) RETURNS json AS $$
var root = [];
for(var i in data) {
build_tree(data[i]['id'], data[i]['name'], data[i]['children']);
}
function build_tree(id, name, children) {
var exists = getObject(root, id);
if(exists) {
exists['children'] = children;
}
else {
root.push({'id': id, 'name': name, 'children': children});
}
}
function getObject(theObject, id) {
var result = null;
if(theObject instanceof Array) {
for(var i = 0; i < theObject.length; i++) {
result = getObject(theObject[i], id);
if (result) {
break;
}
}
}
else
{
for(var prop in theObject) {
if(prop == 'id') {
if(theObject[prop] === id) {
return theObject;
}
}
if(theObject[prop] instanceof Object || theObject[prop] instanceof Array) {
result = getObject(theObject[prop], id);
if (result) {
break;
}
}
}
}
return result;
}
return JSON.stringify(root);
$$ LANGUAGE plv8 IMMUTABLE STRICT;
Это даст требуемый JSON, упомянутый в вашем вопросе. Надеюсь, что это поможет.
Я написал подробное сообщение о том, как это решение работает здесь.
Ответ 2
Попробуйте PL/Python и networkx.
По общему признанию, использование следующего не дает JSON в точно запрошенном формате, но информация, кажется, все там, и если PL/Python является приемлемым, это может быть адаптировано в полный ответ.
CREATE OR REPLACE FUNCTION get_adjacency_data(
names text[],
ids integer[],
parent_ids integer[])
RETURNS jsonb AS
$BODY$
pairs = zip(ids, parent_ids)
import networkx as nx
import json
from networkx.readwrite import json_graph
name_dict = dict(zip(ids, names))
G=nx.DiGraph()
G.add_nodes_from(ids)
nx.set_node_attributes(G, 'name', name_dict)
G.add_edges_from(pairs)
return json.dumps(json_graph.adjacency_data(G))
$BODY$ LANGUAGE plpythonu;
WITH raw_data AS (
SELECT array_agg(name) AS names,
array_agg(parent_id) AS parent_ids,
array_agg(id) AS ids
FROM tags
WHERE parent_id IS NOT NULL)
SELECT get_adjacency_data(names, parent_ids, ids)
FROM raw_data;
Ответ 3
я нашел то же решение, и может быть этот пример может быть полезен для всех
протестировано на Postgres 10 с таблицей такой же структуры
таблица со столбцами: id, name и pid как parent_id
create or replace function get_company_tree(p_parent int8) returns setof jsonb as $$
select
case
when count(x) > 0 then jsonb_build_object('id', c.id, 'name', c.name, 'children', jsonb_agg(f.x))
else jsonb_build_object('id', c.id, 'name', c.name, 'children', jsonb_agg(f.x))
end
from company c left join get_company_children(c.id) as f(x) on true
where c.pid = p_parent or (p_parent is null and c.pid is null)
group by c.id, c.name;
$$ language sql;
select jsonb_agg(get_company_tree) from get_company_tree(null::int);