【问题标题】:PostgreSQL recursive rows to JSONB mapPostgreSQL 递归行到 JSONB 映射
【发布时间】:2018-04-22 13:29:54
【问题描述】:

这个问题最好用一个例子来解释。因此,如果您在 PostgreSQL 中有 2 个表 categoryevent,如下所示:-

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 表非常动态,可以定义类别层次结构。

我想要实现的是选择 event 表的条目并将其与类别连接,但将其展平为 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

这将返回:-

+----+--------+-------------+------------------------------------------------+
| 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级)。例如,如果我将以下行添加到 eventcategory 表中:-

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);

...并从上面运行查询,它将返回:-

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;

... 将返回以下 ...

+----+--------+-------------+--------------------------------------------------------------------------+
| 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"}                                              |
+----+--------+-------------+--------------------------------------------------------------------------+

已经很接近了,但还不够!

【问题讨论】:

    标签: json postgresql common-table-expression recursive-query jsonb


    【解决方案1】:

    使用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;
    

    请注意,查询不受循环依赖保护,因此您需要确保表中的所有路径都以空值结尾。

    DbFiddle.上测试查询


    替代解决方案:

    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 = $1
    $$;
    
    select id, name, get_categories(category_id)
    from event
    order by id;
    

    DbFiddle.

    【讨论】:

    • 完美,我最初尝试了递归CTE,然后尝试了一个函数,感谢您提供清晰准确的解决方案!
    • 很高兴能帮上忙。您的功能非常接近,尽管它可能要简单得多。
    猜你喜欢
    • 2022-12-14
    • 2022-01-19
    • 2021-12-07
    • 1970-01-01
    • 2015-10-02
    • 2011-07-24
    • 1970-01-01
    • 2013-10-31
    • 1970-01-01
    相关资源
    最近更新 更多