¿Resultado de devolución de PostgreSQL establecido como matriz JSON?

134

Me gustaría que PostgreSQL devuelva el resultado de una consulta como una matriz JSON. Dado

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

Me gustaria algo similar a

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

o

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

(En realidad, sería más útil conocer ambos). He intentado algunas cosas como

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;

Y siento que estoy cerca, pero en realidad no. ¿Debo estar mirando otra documentación, excepto 9.15. ¿Funciones y operadores JSON ?

Por cierto, no estoy seguro de mi idea. ¿Es esta una decisión de diseño habitual? Mi opinión es que, por supuesto, podría tomar el resultado (por ejemplo) de la primera de las 3 consultas anteriores y manipularlo ligeramente en la aplicación antes de entregarlo al cliente, pero si PostgreSQL puede crear el objeto JSON final directamente, sería más simple, porque todavía no he incluido ninguna dependencia en ninguna biblioteca JSON en mi aplicación.

ingenieroX
fuente
1
PG 9.4, ahora disponible en la versión beta 1, ha mejorado la compatibilidad con JSON, incluidas las E / S binarias. Si está en una máquina de desarrollo, es posible que desee comprobarlo.
Patrick
@Patrick: gracias, parece que json_object () es una nueva función en 9.4 y probaría algo como SELECT json_object (array_agg (ta), array_agg (tb)) FROM t, si lo tuviera
engineerX

Respuestas:

265

TL; DR

SELECT json_agg(t) FROM t

para una matriz JSON de objetos, y

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

para un objeto JSON de matrices.

Lista de objetos

Esta sección describe cómo generar una matriz JSON de objetos, con cada fila convertida en un solo objeto. El resultado se ve así:

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

9.3 y hasta

La json_aggfunción produce este resultado fuera de la caja. Automáticamente descubre cómo convertir su entrada en JSON y la agrega en una matriz.

SELECT json_agg(t) FROM t

No hay una jsonbversión (introducida en 9.4) de json_agg. Puede agregar las filas en una matriz y luego convertirlas:

SELECT to_jsonb(array_agg(t)) FROM t

o combinar json_aggcon un yeso:

SELECT json_agg(t)::jsonb FROM t

Mi prueba sugiere que agregarlos primero en una matriz es un poco más rápido. Sospecho que esto se debe a que el elenco tiene que analizar todo el resultado JSON.

9.2

9.2 no tiene las funciones json_aggo to_json, por lo que debe usar las anteriores array_to_json:

SELECT array_to_json(array_agg(t)) FROM t

Opcionalmente, puede incluir una row_to_jsonllamada en la consulta:

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

Esto convierte cada fila en un objeto JSON, agrega los objetos JSON como una matriz y luego convierte la matriz en una matriz JSON.

No pude discernir ninguna diferencia de rendimiento significativa entre los dos.

Objeto de listas

Esta sección describe cómo generar un objeto JSON, cada clave es una columna en la tabla y cada valor es una matriz de los valores de la columna. Es el resultado que se ve así:

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

9.5 y más

Podemos aprovechar la json_build_objectfunción:

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

También puede agregar las columnas, crear una sola fila y luego convertirla en un objeto:

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

Tenga en cuenta que el alias de las matrices es absolutamente necesario para garantizar que el objeto tenga los nombres deseados.

Cuál es más claro es una cuestión de opinión. Si usa la json_build_objectfunción, le recomiendo poner un par clave / valor en una línea para mejorar la legibilidad.

También podría usarlo array_aggen lugar de json_agg, pero mis pruebas indican que json_agges un poco más rápido.

No hay una jsonbversión de la json_build_objectfunción. Puede agregar en una sola fila y convertir:

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

A diferencia de otras consultas para este tipo de resultado, array_aggparece ser un poco más rápido cuando se usa to_jsonb. Sospecho que esto se debe al análisis y validación generales del resultado JSON de json_agg.

O puede usar un reparto explícito:

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

La to_jsonbversión le permite evitar el reparto y es más rápido, según mis pruebas; De nuevo, sospecho que esto se debe a la sobrecarga de analizar y validar el resultado.

9.4 y 9.3

La json_build_objectfunción era nueva en 9.5, por lo que debe agregar y convertir a un objeto en versiones anteriores:

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

o

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

dependiendo de si quieres jsono jsonb.

(9.3 no tiene jsonb)

9.2

En 9.2, ni siquiera to_jsonexiste. Debes usar 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

Documentación

Encuentre la documentación para las funciones JSON en las funciones JSON .

json_aggestá en la página de funciones agregadas .

Diseño

Si el rendimiento es importante, asegúrese de comparar sus consultas con su propio esquema y datos, en lugar de confiar en mis pruebas.

Si es un buen diseño o no, realmente depende de su aplicación específica. En términos de mantenibilidad, no veo ningún problema en particular. Simplifica el código de su aplicación y significa que hay menos para mantener en esa parte de la aplicación. Si PG puede darle exactamente el resultado que necesita de inmediato, la única razón por la que se me ocurre no usarlo sería por consideraciones de rendimiento. No reinventes la rueda y todo.

Nulos

Las funciones agregadas generalmente devuelven NULLcuando operan sobre filas cero. Si esto es una posibilidad, puede utilizar COALESCEpara evitarlos. Un par de ejemplos:

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

O

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

Gracias a Hannes Landeholm por señalar esto

jpmc26
fuente
3
Gracias por su respuesta. Me inspiraste a encontrar la respuesta a mi segunda pregunta, SELECT row_to_json (row (array_agg (ta), array_agg (tb))) FROM t, aunque el resultado tiene "f1" y "f2" como etiquetas en lugar de a y b.
engineerX
@engineerX He ampliado mi respuesta.
jpmc26
3
Puede ser indeseable en algunos casos recuperar NULL en lugar de una matriz JSON vacía cuando la selección interna (desde t) devuelve cero filas. Esto se debe a que las funciones agregadas siempre devuelven NULL cuando se selecciona sin filas y se pueden resolver por fusión: array_to_json (fusión (array_agg (t), matriz [] :: registro [])).
Hannes Landeholm
3
puede usar en to_jsonlugar de row_to_jsonyarray_to_json
itsnikolay
Para seleccionar (múltiples) columnas específicas, debe pasarlas como un solo argumento, como una lista de corchetes redondos SELECT json_agg((column1, column2, ...)) FROM t , observe los corchetes adicionales. Esto puede no ser obvio "fuera de la caja".
jave.web
19

Además, si desea seleccionar el campo de la tabla y agregarlo como matriz.

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

El resultado vendrá.

 [{'data_a':1,'data_b':'value1'}
  {'data_a':2,'data_b':'value2'}]
Himanshu Sharma
fuente