PostgreSQL 递归行到 JSONB 映射
PostgreSQL recursive rows to JSONB map
这个问题最好用一个例子来解释。所以,如果你在 PostgreSQL 中有 2 tables category
和 event
如下: -
create table category (
id integer primary key,
type varchar(255),
label varchar (255),
parent_id integer
);
insert into category (id, type, label, parent_id)
values (1, 'organisation', 'Google', null),
(2, 'product', 'Gmail', 1),
(3, 'organisation', 'Apple', null),
(4, 'product', 'iPhone', 3),
(5, 'product', 'Mac', 3);
create table event (
id integer primary key,
name varchar (255),
category_id integer
);
insert into event (id, name, category_id)
values (1, 'add', 4),
(2, 'delete', 5),
(3, 'update', 2);
如您所见,category
table 非常动态,可以定义类别层次结构。
我想要实现的是选择 event
table 的条目并将其与类别合并,但将其展平为 JSON 结构。我可以使用以下查询来说明:-
select e.*,
jsonb_build_object(
c1.type, c1.label,
c2.type, c2.label
) as categories
from event e
left join category c2 on c2.id = e.category_id
left join category c1 on c1.id = c2.parent_id
这将 return: -
+----+--------+-------------+------------------------------------------------+
| id | name | category_id | categories |
+----+--------+-------------+------------------------------------------------+
| 1 | add | 4 | {"organisation": "Apple", "product": "iPhone"} |
| 2 | delete | 5 | {"organisation": "Apple", "product": "Mac"} |
| 3 | update | 2 | {"organisation": "Google", "product": "Gmail"} |
+----+--------+-------------+------------------------------------------------+
但是,此方法仅在 event.category_id
列引用恰好具有 1 个父级(2 个级别)的子类别时才有效。我真正要寻找的是生成 categories
,无论 (a) 它是否没有父类别(即 1 级类别)或 (b) 是否有超过 1 个父类别(例如 3 级) .例如,如果我将以下行添加到 event
和 category
table 中:-
insert into category (id, type, label, parent_id)
values (6, 'module', 'Mobile', 5), /* has 2 parents */
(7, 'organisation', 'AirBNB', null); /* has no parents */
insert into event (id, name, category_id)
values (4, 'event1', 6),
(5, 'event2', 7);
... 和 运行 上面的查询将 return: -
ERROR: argument 1: key must not be null
SQL state:
我的直觉是递归 CTE 可以解决这个问题。
更新 1
create or replace function category_array(category_parent_id int) returns setof jsonb as $$
select case
when count(x) > 0 then
jsonb_agg(f.x) || jsonb_build_object (
c.type, c.label
)
else jsonb_build_object (
c.type, c.label
)
end as category_pair
from category c
left join category_array (c.parent_id) as f(x) on true
where c.id = category_parent_id
group by c.id, c.type, c.label;
$$ language sql;
... 并使用此 SQL ...
调用
select *,
category_array(category_id)
from event;
...将return以下...
+----+--------+-------------+--------------------------------------------------------------------------+
| id | name | category_id | categories |
+----+--------+-------------+--------------------------------------------------------------------------+
| 1 | add | 4 | [{"organisation": "Apple"}, {"product": "iPhone"}] |
| 2 | delete | 5 | [{"organisation": "Apple"}, {"product": "Mac"}] |
| 3 | update | 2 | [{"organisation": "Google"}, {"product": "Gmail"}] |
| 4 | event1 | 6 | [[{"organisation": "Apple"}, {"product": "Mac"}], {"module": "Mobile"}] |
| 5 | event2 | 7 | {"organisation": "AirBNB"} |
+----+--------+-------------+--------------------------------------------------------------------------+
非常接近,但还没有完全实现!
使用concatenation operator ||构建累积jsonb对象:
with recursive events as (
select
e.id, e.name, e.category_id as parent_id,
jsonb_build_object(c.type, c.label) as categories
from event e
left join category c on c.id = e.category_id
union all
select
e.id, e.name, c.parent_id,
categories || jsonb_build_object(c.type, c.label)
from events e
join category c on c.id = e.parent_id
)
select id, name, categories
from events
where parent_id is null
order by id;
请注意,查询不受循环依赖保护,因此您需要确保 table 中的所有路径都以空值结尾。
上测试查询
备选方案:
create or replace function get_categories(int)
returns jsonb language sql as $$
select case
when parent_id is null then
jsonb_build_object (type, label)
else
jsonb_build_object (type, label) || get_categories(parent_id)
end as categories
from category
where id =
$$;
select id, name, get_categories(category_id)
from event
order by id;
这个问题最好用一个例子来解释。所以,如果你在 PostgreSQL 中有 2 tables category
和 event
如下: -
create table category (
id integer primary key,
type varchar(255),
label varchar (255),
parent_id integer
);
insert into category (id, type, label, parent_id)
values (1, 'organisation', 'Google', null),
(2, 'product', 'Gmail', 1),
(3, 'organisation', 'Apple', null),
(4, 'product', 'iPhone', 3),
(5, 'product', 'Mac', 3);
create table event (
id integer primary key,
name varchar (255),
category_id integer
);
insert into event (id, name, category_id)
values (1, 'add', 4),
(2, 'delete', 5),
(3, 'update', 2);
如您所见,category
table 非常动态,可以定义类别层次结构。
我想要实现的是选择 event
table 的条目并将其与类别合并,但将其展平为 JSON 结构。我可以使用以下查询来说明:-
select e.*,
jsonb_build_object(
c1.type, c1.label,
c2.type, c2.label
) as categories
from event e
left join category c2 on c2.id = e.category_id
left join category c1 on c1.id = c2.parent_id
这将 return: -
+----+--------+-------------+------------------------------------------------+
| id | name | category_id | categories |
+----+--------+-------------+------------------------------------------------+
| 1 | add | 4 | {"organisation": "Apple", "product": "iPhone"} |
| 2 | delete | 5 | {"organisation": "Apple", "product": "Mac"} |
| 3 | update | 2 | {"organisation": "Google", "product": "Gmail"} |
+----+--------+-------------+------------------------------------------------+
但是,此方法仅在 event.category_id
列引用恰好具有 1 个父级(2 个级别)的子类别时才有效。我真正要寻找的是生成 categories
,无论 (a) 它是否没有父类别(即 1 级类别)或 (b) 是否有超过 1 个父类别(例如 3 级) .例如,如果我将以下行添加到 event
和 category
table 中:-
insert into category (id, type, label, parent_id)
values (6, 'module', 'Mobile', 5), /* has 2 parents */
(7, 'organisation', 'AirBNB', null); /* has no parents */
insert into event (id, name, category_id)
values (4, 'event1', 6),
(5, 'event2', 7);
... 和 运行 上面的查询将 return: -
ERROR: argument 1: key must not be null
SQL state:
我的直觉是递归 CTE 可以解决这个问题。
更新 1
create or replace function category_array(category_parent_id int) returns setof jsonb as $$
select case
when count(x) > 0 then
jsonb_agg(f.x) || jsonb_build_object (
c.type, c.label
)
else jsonb_build_object (
c.type, c.label
)
end as category_pair
from category c
left join category_array (c.parent_id) as f(x) on true
where c.id = category_parent_id
group by c.id, c.type, c.label;
$$ language sql;
... 并使用此 SQL ...
调用select *,
category_array(category_id)
from event;
...将return以下...
+----+--------+-------------+--------------------------------------------------------------------------+
| id | name | category_id | categories |
+----+--------+-------------+--------------------------------------------------------------------------+
| 1 | add | 4 | [{"organisation": "Apple"}, {"product": "iPhone"}] |
| 2 | delete | 5 | [{"organisation": "Apple"}, {"product": "Mac"}] |
| 3 | update | 2 | [{"organisation": "Google"}, {"product": "Gmail"}] |
| 4 | event1 | 6 | [[{"organisation": "Apple"}, {"product": "Mac"}], {"module": "Mobile"}] |
| 5 | event2 | 7 | {"organisation": "AirBNB"} |
+----+--------+-------------+--------------------------------------------------------------------------+
非常接近,但还没有完全实现!
使用concatenation operator ||构建累积jsonb对象:
with recursive events as (
select
e.id, e.name, e.category_id as parent_id,
jsonb_build_object(c.type, c.label) as categories
from event e
left join category c on c.id = e.category_id
union all
select
e.id, e.name, c.parent_id,
categories || jsonb_build_object(c.type, c.label)
from events e
join category c on c.id = e.parent_id
)
select id, name, categories
from events
where parent_id is null
order by id;
请注意,查询不受循环依赖保护,因此您需要确保 table 中的所有路径都以空值结尾。
上测试查询备选方案:
create or replace function get_categories(int)
returns jsonb language sql as $$
select case
when parent_id is null then
jsonb_build_object (type, label)
else
jsonb_build_object (type, label) || get_categories(parent_id)
end as categories
from category
where id =
$$;
select id, name, get_categories(category_id)
from event
order by id;