Выберите столбцы внутри json_agg

21

У меня есть запрос как:

SELECT a.id, a.name, json_agg(b.*) as "item"
  FROM a
  JOIN b ON b.item_id = a.id
 GROUP BY a.id, a.name;

Как я могу выбрать столбцы, bчтобы у меня не было b.item_idобъекта JSON?

Я читал о ROW, но он возвращает объект JSON, как:

{"f1": "Foo", "f2": "Bar"}

Мне нужно будет переназначить объект JSON, как только он будет выбран для соответствия ключам столбца. Я хотел бы избежать этого и сохранить оригинальные имена столбцов.

Яник Рошон
источник

Ответы:

50

К сожалению, в синтаксисе SQL не предусмотрено выражение «все столбцы, кроме этого одного столбца» . Вы можете достичь своей цели, изложив оставшийся список столбцов в выражении типа строки :

SELECT a.id, a.name
     , json_agg((b.col1, b.col2, b.col3)) AS item
FROM   a
JOIN   b ON b.item_id = a.id
GROUP  BY a.id, a.name;

Это коротко для более явной формы: . ROW(b.col1, b.col2, b.col3)

Однако имена столбцов не сохраняются в выражениях типа строки. Таким образом вы получаете общие имена ключей в объекте JSON. Я вижу 3 варианта сохранения оригинальных имен столбцов:

1. Приведение к зарегистрированному типу

Приведение к известному (зарегистрированному) типу строки. Тип регистрируется для каждой существующей таблицы или представления или с явным CREATE TYPEоператором. Вы можете использовать временную таблицу для специального решения (действует в течение всего сеанса):

CREATE TEMP TABLE x (col1 int, col2 text, col3 date);  -- use adequate data types!

SELECT a.id, a.name
     , json_agg((b.col1, b.col2, b.col3)::x) AS item
FROM   a
JOIN   b ON b.item_id = a.id
GROUP  BY a.id, a.name;

2. Используйте подвыбор

Используйте вложенный выбор для построения производной таблицы и ссылки на таблицу в целом . Это также несет имена столбцов. Это более многословно, но вам не нужен зарегистрированный тип:

SELECT a.id, a.name
     , json_agg((SELECT x FROM (SELECT b.col1, b.col2, b.col3) AS x)) AS item
FROM   a
JOIN   b ON b.item_id = a.id
GROUP  BY a.id, a.name;

3. json_build_object()в Postgres 9.4 или позже

SELECT a.id, a.name
     , json_agg(json_build_object('col1', b.col1, 'col2', b.col2, 'col3', b.col3)) AS item
FROM   a
JOIN   b ON b.item_id = a.id
GROUP  BY a.id, a.name;

Связанный:

Аналогично для jsonbсоответствующих функций jsonb_agg()и jsonb_build_object().

Для Postgres 9.5 или более поздней версии также увидеть ответ a_horse в с новым вариантом короче синтаксис: Postgres добавлен оператор минус -дляjsonb сказать «все клавиши , кроме этого одного ключа» .
Поскольку Postgres 10 «кроме нескольких ключей» реализован с тем же оператором, text[]что и второй операнд - как в комментариях к MLT.

Эрвин Брандштеттер
источник
1
> или несколько клавиш Обратите внимание, что json (b) -text [] доступен начиная с 10.
mlt
Решение 3 сработало для меня как шарм!
Луис Фернандо да Силва
17

Начиная с 9.6, вы можете просто использовать -для удаления ключа из JSONB:

SELECT a.id, a.name, jsonb_agg(to_jsonb(b) - 'item_id') as "item"
FROM a
  JOIN b ON b.item_id = a.id
GROUP BY a.id, a.name;

to_jsonb(b)преобразует всю строку и - 'item_id'затем удаляет ключ с именем, item_idрезультат которого затем агрегируется.

a_horse_with_no_name
источник
Похоже, эти новые функции были тем, на что надеялся ОП. Я добавил ссылку на мой ответ.
Эрвин Брандштеттер
Когда я попробовал вариант выбора, я получил ошибку, связанную с json_aggфункцией:function json_agg(record) does not exist
fraxture
@fraxture: тогда вы не используете Postgres 9.6
a_horse_with_no_name
На самом деле это была проблема. Есть ли способ отфильтровать столбцы в v9.2?
Fragture
8

Вы можете сделать это без группировки, используя подзапросы

SELECT 
  a.id, a.name, 
  ( 
    SELECT json_agg(item)
    FROM (
      SELECT b.c1 AS x, b.c2 AS y 
      FROM b WHERE b.item_id = a.id
    ) item
  ) AS items
FROM a;

возвращается

{
  id: 1,
  name: "thing one",
  items:[
    { x: "child1", y: "child1 col2"},
    { x: "child2", y: "child2 col2"}
  ]
}

Эта статья от Джона Аттена действительно интересна и имеет больше деталей

redben
источник
2

Я обнаружил, что лучше всего создать JSON, а затем агрегировать его. например

with base as (
select a, b, ('{"ecks":"' || to_json(x) || '","wai":"' || to_json(y) || '","zee":"' || to_json(z) || '"}"')::json c
) select (a, b, array_to_json(array_agg(c)) as c)

Обратите внимание, что это можно сделать как подзапрос, если вам не нравятся CTE (или у вас проблемы с производительностью из-за его использования).

Также обратите внимание, что если вы собираетесь много заниматься этим, может быть полезно создать функцию для переноса пар ключ-значение, чтобы код выглядел чище. Вы передадите свою функцию (например), 'ecks', 'x'и она вернется "ecks": "x".

MikeM
источник
1

Хотя до сих пор нет способа сделать что-либо с выделением всех столбцов, кроме одного бита, но вы можете использовать json_agg(to_json(b.col_1, b.col_2, b.col_3 ...))для получения массива json jsons в каждом формате {"col_1":"col_1 value", ...}.

Таким образом, запрос будет выглядеть примерно так:

SELECT a.id, a.name, json_agg(to_json(b.col_1,b.col_2,b.col_3...)) as item
  FROM a
  JOIN b ON b.item_id = a.id
GROUP BY a.id, a.name;

и будет возвращать строки как:

id, name, item
8, the_name, [{"col_1":"value_1","col_2":"value_2","col_3":"value_3"...}, {"col_1":"value_1.2","col_2":"value_2.2","col_3":"value_3.2"...},...]
9, the_next_name, [{"col_1":"value_1.3","col_2":"value_2.3","col_3":"value_3.3"...},   {"col_1":"value_1.4","col_2":"value_2.4","col_3":"value_3.4"...},...]
...

(Я сейчас на Postgres 9.5.3 и не уверен на 100%, когда эта поддержка была добавлена.)

Дэвид К
источник
1

Вы можете использовать json_build_objectкак это

SELECT 
  a.id, 
  a.name,
  json_agg(json_build_object('col1', b.col1, 'col2', b.col2) AS item
FROM a
JOIN b ON b.item_id = a.id
GROUP BY a.id, a.name;
Тан Дуонг
источник