Результат возврата PostgreSQL в виде массива JSON?

135

Я хотел бы, чтобы PostgreSQL возвращал результат запроса в виде одного массива JSON. Дано

create table t (a int primary key, b text);

insert into t values (1, 'value1');
insert into t values (2, 'value2');
insert into t values (3, 'value3');

Хотелось бы что-то похожее на

[{"a":1,"b":"value1"},{"a":2,"b":"value2"},{"a":3,"b":"value3"}]

или

{"a":[1,2,3], "b":["value1","value2","value3"]}

(на самом деле было бы полезнее знать оба). Я пробовал такие вещи, как

select row_to_json(row) from (select * from t) row;
select array_agg(row) from (select * from t) row;
select array_to_string(array_agg(row), '') from (select * from t) row;

И я чувствую, что близок, но на самом деле не там. Стоит ли искать другую документацию, кроме 9.15. Функции и операторы JSON ?

Кстати, я не совсем уверен в своей идее. Это обычное дизайнерское решение? Я думаю, что я мог бы, конечно, взять результат (например) первого из трех вышеупомянутых запросов и немного манипулировать им в приложении, прежде чем передавать его клиенту, но если PostgreSQL может напрямую создать конечный объект JSON, это было бы проще, потому что я до сих пор не включил в свое приложение какую-либо зависимость от какой-либо библиотеки JSON.

engineerX
источник
1
В PG 9.4, теперь доступном в версии beta 1, улучшена поддержка JSON, включая двоичный ввод-вывод. Если вы работаете на машине разработки, возможно, вы захотите ее проверить.
Патрик
@Patrick: спасибо, это будет выглядеть как json_object () является новой функцией в 9.4 , и я хотел бы попробовать что - то вроде SELECT , json_object (array_agg (та), array_agg (Tb)) ОТ т, если у меня было
engineerX

Ответы:

266

TL; DR

SELECT json_agg(t) FROM t

для массива объектов JSON и

SELECT
    json_build_object(
        'a', json_agg(t.a),
        'b', json_agg(t.b)
    )
FROM t

для объекта массивов JSON.

Список объектов

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

[{"a":1,"b":"value1"},{"a":2,"b":"value2"},{"a":3,"b":"value3"}]

9.3 и выше

json_aggФункция производит этот результат из коробки. Он автоматически определяет, как преобразовать входные данные в JSON, и объединяет их в массив.

SELECT json_agg(t) FROM t

Не существует jsonb(введено в 9.4) версии json_agg. Вы можете объединить строки в массив, а затем преобразовать их:

SELECT to_jsonb(array_agg(t)) FROM t

или комбинировать json_aggс гипсом:

SELECT json_agg(t)::jsonb FROM t

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

9,2

9.2 не имеет функций json_aggили to_json, поэтому вам нужно использовать старую версию array_to_json:

SELECT array_to_json(array_agg(t)) FROM t

Вы можете дополнительно включить row_to_jsonвызов в запрос:

SELECT array_to_json(array_agg(row_to_json(t))) FROM t

Это преобразует каждую строку в объект JSON, объединяет объекты JSON в массив, а затем преобразует массив в массив JSON.

Я не смог заметить какой-либо значительной разницы в производительности между ними.

Объект списков

В этом разделе описывается, как сгенерировать объект JSON, где каждый ключ является столбцом в таблице, а каждое значение - массивом значений столбца. Результат выглядит так:

{"a":[1,2,3], "b":["value1","value2","value3"]}

9.5 и выше

Мы можем использовать json_build_objectфункцию:

SELECT
    json_build_object(
        'a', json_agg(t.a),
        'b', json_agg(t.b)
    )
FROM t

Вы также можете агрегировать столбцы, создав одну строку, а затем преобразовать ее в объект:

SELECT to_json(r)
FROM (
    SELECT
        json_agg(t.a) AS a,
        json_agg(t.b) AS b
    FROM t
) r

Обратите внимание, что псевдонимы массивов абсолютно необходимы, чтобы гарантировать, что объект имеет желаемые имена.

Какой из них яснее - вопрос личного мнения. Если вы используете json_build_objectфункцию, я настоятельно рекомендую поместить одну пару ключ / значение в строку, чтобы улучшить читаемость.

Вы также можете использовать array_aggвместо json_agg, но мое тестирование показывает, что json_aggэто немного быстрее.

Нет jsonbверсии json_build_objectфункции. Вы можете объединить в одну строку и преобразовать:

SELECT to_jsonb(r)
FROM (
    SELECT
        array_agg(t.a) AS a,
        array_agg(t.b) AS b
    FROM t
) r

В отличие от других запросов для такого рода результатов, array_aggпри использовании кажется немного быстрее to_jsonb. Я подозреваю, что это связано с дополнительным анализом и проверкой результата JSON json_agg.

Или вы можете использовать явное приведение:

SELECT
    json_build_object(
        'a', json_agg(t.a),
        'b', json_agg(t.b)
    )::jsonb
FROM t

to_jsonbВерсия позволяет избежать бросания и быстрее, по моему тестирования; опять же, я подозреваю, что это связано с накладными расходами на синтаксический анализ и проверку результата.

9.4 и 9.3

Эта json_build_objectфункция была новой для 9.5, поэтому в предыдущих версиях вам нужно было агрегировать и преобразовывать в объект:

SELECT to_json(r)
FROM (
    SELECT
        json_agg(t.a) AS a,
        json_agg(t.b) AS b
    FROM t
) r

или

SELECT to_jsonb(r)
FROM (
    SELECT
        array_agg(t.a) AS a,
        array_agg(t.b) AS b
    FROM t
) r

в зависимости от того, хотите ли вы jsonили jsonb.

(9.3 не имеет jsonb.)

9,2

В 9.2 даже не to_jsonсуществует. Вы должны использовать row_to_json:

SELECT row_to_json(r)
FROM (
    SELECT
        array_agg(t.a) AS a,
        array_agg(t.b) AS b
    FROM t
) r

Документация

Найдите документацию по функциям JSON в функциях JSON .

json_aggнаходится на странице агрегатных функций .

дизайн

Если производительность важна, убедитесь, что вы сравниваете свои запросы со своей собственной схемой и данными, а не доверяете моему тестированию.

Хороший ли это дизайн или нет, на самом деле зависит от вашего конкретного приложения. В плане ремонтопригодности особых проблем не вижу. Это упрощает код вашего приложения и означает, что в этой части приложения меньше нужно поддерживать. Если PG может дать вам именно тот результат, который вам нужен, единственная причина, по которой я могу не использовать его, - это соображения производительности. Не изобретайте велосипед и все такое.

Нулевые

Агрегатные функции обычно возвращаются, NULLкогда они работают над нулевыми строками. Если это возможно, вы можете использовать, COALESCEчтобы избежать их. Пара примеров:

SELECT COALESCE(json_agg(t), '[]'::json) FROM t

Или

SELECT to_jsonb(COALESCE(array_agg(t), ARRAY[]::t[])) FROM t

Кредит Ханнес Landeholm для указывая на это

jpmc26
источник
3
Спасибо за ваш ответ. Вы вдохновили меня найти ответ на мой второй вопрос: ВЫБРАТЬ row_to_json (row (array_agg (ta), array_agg (tb))) FROM t, хотя в результате вместо a и b в качестве меток используются «f1» и «f2».
EngineerX
@engineerX Я расширил свой ответ.
jpmc26 03
3
В некоторых случаях может быть нежелательно возвращать NULL вместо пустого массива JSON, когда внутренний выбор (из t) возвращает нулевые строки. Это вызвано тем, что агрегатные функции всегда возвращают NULL, когда выборка отсутствует, и решается с помощью coalesce: array_to_json (coalesce (array_agg (t), array [] :: record [])).
Ханнес Ландехольм, 03
3
вы можете использовать to_jsonвместо row_to_jsonиarray_to_json
itsnikolay
Чтобы выбрать (несколько) определенных столбцов, вы должны передать их как один аргумент - например, список с круглыми скобками SELECT json_agg((column1, column2, ...)) FROM t - обратите внимание на дополнительные скобки. Это может быть неочевидно «из коробки».
jave.web
19

Также, если вы хотите выбрать поле из таблицы и агрегировать его как массив.

SELECT json_agg(json_build_object('data_a',a,
                                  'data_b',b,
))  from t;

Результат придет.

 [{'data_a':1,'data_b':'value1'}
  {'data_a':2,'data_b':'value2'}]
Химаншу шарма
источник