Как установить правильные имена атрибутов в агрегированный результат json с предложением GROUP BY?

У меня есть таблица 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 называются f1, f2, f3 вместо id, name, body по мере необходимости. Я знаю, что можно правильно их использовать, используя подзапрос или общее табличное выражение, например:

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

Но я, честно говоря, не вижу способа использовать его в сочетании с агрегацией. Что мне не хватает?

Ответ 1

Для этого вам не нужна таблица temp или тип, но это не красиво.

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

В 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

Ответ 3

Основываясь на @Craig 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"}]

Ответ 4

Я просто собирался опубликовать очень похожее решение на ваш, используя временную таблицу

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

Ответ 5

Если вам нужны все поля из таблицы, вы можете использовать этот подход:

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

Ответ 6

Хорошо, отвечая на мой вопрос через пару минут после того, как я спрошу, но я нашел способ... Я просто не знаю, что это лучший. Я решил это, создав композитный тип:

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