如何使用GROUP BY子句为json聚合结果设置正确的属性名称?

时间:2014-07-26 11:13:57

标签: json postgresql types aggregate-functions postgresql-9.3

我有一个像这样定义的表temp

id |  name  |  body  | group_id
-------------------------------
1  | test_1 | body_1 | 1
2  | test_2 | body_2 | 1
3  | test_3 | body_3 | 2
4  | test_4 | body_4 | 2

我想生成一个按group_id分组的结果并汇总到json。但是,查询如下:

SELECT group_id, json_agg(ROW(id, name, body)) FROM temp
GROUP BY group_id;

产生这个结果:

1;[{"f1":1,"f2":"test_1","f3":"body_1"}, 
   {"f1":2,"f2":"test_2","f3":"body_2"}]
2;[{"f1":3,"f2":"test_3","f3":"body_3"}, 
   {"f1":4,"f2":"test_4","f3":"body_4"}]

json对象中的属性根据需要命名为f1f2f3而不是idnamebody。我知道可以通过使用子查询或公用表表达式来正确地对它们进行别名,例如:

SELECT json_agg(r.*) FROM (
  SELECT id, name, body FROM temp
) r;

产生这个结果:

[{"id":1,"name":"test_1","body":"body_1"}, 
 {"id":2,"name":"test_2","body":"body_2"}, 
 {"id":3,"name":"test_3","body":"body_3"}, 
 {"id":4,"name":"test_4","body":"body_4"}]

但老实说,我没有看到如何将它与聚合结合使用。我错过了什么?

6 个答案:

答案 0 :(得分:28)

在Postgres 9.4中,您可以使用json_build_object()

对于您的示例,它的工作方式如下:

SELECT group_id, 
       json_agg(json_build_object('id', id, 'name', name, 'body', body)) 
FROM temp
GROUP BY group_id;

这是一种更友好的方式,Postgres爱我们:3

答案 1 :(得分:17)

你不需要临时表或类型,但它并不漂亮。

SELECT json_agg(row_to_json( (SELECT r FROM (SELECT id, name, body) r) )) 
FROM t
GROUP BY group_id;

在这里,我们使用两个子查询 - 首先,构造一个只包含三个所需列的结果集,然后使用外部子查询将其作为复合行类型。

它仍然会表现良好。


为了使用较少丑陋的语法完成此操作,PostgreSQL需要让您为匿名行类型设置别名,如下面的(无效)语法:

SELECT json_agg(row_to_json( ROW(id, name, body) AS (id, name, body) )) 
FROM t
GROUP BY group_id;

或者我们需要一个row_to_json的变体来获取列别名,比如(再次无效):

SELECT json_agg(row_to_json( ROW(id, name, body), ARRAY['id', 'name', 'body'])) 
FROM t
GROUP BY group_id;

其中两者都很好,但目前不支持。

答案 2 :(得分:4)

@Craig's answer上构建以使其更优雅,此处复合行类型构建在from列表

select json_agg(row_to_json(s))
from
    t
    cross join lateral 
    (select id, name, body) s
group by group_id;
                                       json_agg                                       
--------------------------------------------------------------------------------------
 [{"id":1,"name":"test_1","body":"body_1"}, {"id":2,"name":"test_2","body":"body_2"}]
 [{"id":3,"name":"test_3","body":"body_3"}, {"id":4,"name":"test_4","body":"body_4"}]

答案 3 :(得分:2)

我只想使用临时表

发布一个非常相似的解决方案to yours
create table t (
    id int,
    name text,
    body text,
    group_id int
);
insert into t (id, name, body, group_id) values
(1, 'test_1', 'body_1', 1),
(2, 'test_2', 'body_2', 1),
(3, 'test_3', 'body_3', 2),
(4, 'test_4', 'body_4', 2);

create temporary table tt(
    id int,
    name text,
    body text
);

select group_id, json_agg(row(id, name, body)::tt)
from t
group by group_id;
 group_id |                  json_agg                   
----------+---------------------------------------------
        1 | [{"id":1,"name":"test_1","body":"body_1"}, +
          |  {"id":2,"name":"test_2","body":"body_2"}]
        2 | [{"id":3,"name":"test_3","body":"body_3"}, +
          |  {"id":4,"name":"test_4","body":"body_4"}]

答案 4 :(得分:1)

如果你需要表中的所有字段,那么你可以使用这种方法:

SELECT 
    group_id, json_agg(temp.*) 
FROM 
    temp
GROUP BY 
    group_id;

答案 5 :(得分:0)

嗯,在问了几分钟之后回答我自己的问题,但我找到了办法......我只是不知道这是最好的问题。我通过创建复合类型来解决它:

CREATE TYPE temp_type AS (
  id bigint,
  name text,
  body text
);

通过向类型添加强制转换来重写我的查询:

SELECT group_id, json_agg(CAST(ROW(id, name, body) AS temp_type)) FROM temp
GROUP BY group_id;

产生了预期的结果:

1;[{"id":1,"name":"test_1","body":"body_1"}, 
   {"id":2,"name":"test_2","body":"body_2"}]
2;[{"id":3,"name":"test_3","body":"body_3"}, 
   {"id":4,"name":"test_4","body":"body_4"}]