Seleccionar columnas dentro de json_agg


21

Tengo una consulta como:

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;

¿Cómo puedo seleccionar las columnas bpara que no tenga b.item_iden el objeto JSON?

He leído sobre ROW, pero devuelve un objeto JSON como:

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

Necesitaría reasignar el objeto JSON una vez que se recupere para que coincida con las teclas de columna adecuadas. Me gustaría evitar eso y mantener los nombres de columna originales.

Respuestas:


50

Desafortunadamente, no hay ninguna disposición en la sintaxis SQL para decir "todas las columnas excepto esta columna" . Puede lograr su objetivo deletreando la lista restante de columnas en una expresión de tipo fila :

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;

Esa es la abreviatura de la forma más explícita: . ROW(b.col1, b.col2, b.col3)

Sin embargo, los nombres de las columnas no se conservan en las expresiones de tipo fila. Obtiene nombres de clave genéricos en el objeto JSON de esta manera. Veo 3 opciones para preservar los nombres de columna originales:

1. Transmitir al tipo registrado

Transmitir a un tipo de fila conocido (registrado). Se registra un tipo para cada tabla o vista existente o con una CREATE TYPEdeclaración explícita . Puede usar una tabla temporal para una solución ad-hoc (dura toda la sesión):

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. Use una subselección

Use una subselección para construir una tabla derivada y hacer referencia a la tabla como un todo . Esto también lleva nombres de columna. Es más detallado, pero no necesita un tipo registrado:

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()en Postgres 9.4 o posterior

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;

Relacionado:

Similar para jsonbcon las funciones respectivas jsonb_agg()y jsonb_build_object().

Para Postgres 9.5 o posterior también vea la respuesta de a_horse con una nueva variante de sintaxis más corta: Postgres agregó el operador menos -parajsonb decir "todas las teclas excepto esta clave" .
Dado que Postgres 10 "excepto varias claves" se implementa con el mismo operador tomando text[]como segundo operando, como comentó mlt.


1
> o varias teclas Tenga en cuenta que json (b) -text [] está disponible a partir de 10.
mlt

¡La solución 3 funcionó para mí como un encanto!
Luiz Fernando da Silva

17

Comenzando con 9.6, simplemente puede usar -para eliminar una clave de un 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)convertirá la fila completa y - 'item_id'luego eliminará la clave con el nombre item_iddel resultado que se agrega.


Estas nuevas características parecen ser lo que esperaba el OP. Agregué un enlace a mi respuesta.
Erwin Brandstetter

Cuando probé la variante de subselección, recibí un error relacionado con la json_aggfunción:function json_agg(record) does not exist
fraxture

@fraxture: entonces no estás usando Postgres 9.6
a_horse_with_no_name

De hecho ese era el problema. ¿Hay alguna forma de filtrar columnas en v9.2?
fraxture

8

En realidad, puede hacerlo sin grupo, utilizando subconsultas

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;

devoluciones

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

Este artículo de John Atten es realmente interesante y tiene más detalles.


2

He descubierto que es mejor crear el JSON y luego agregarlo. p.ej

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)

Tenga en cuenta que esto se puede hacer como una subconsulta si no le gustan los CTE (o si tiene problemas de rendimiento debido a su uso).

Tenga en cuenta también que si va a hacer esto mucho, puede ser beneficioso crear una función para ajustar los pares clave-valor para que el código se vea más limpio. Pasarías tu función (por ejemplo) 'ecks', 'x'y volvería "ecks": "x".


1

Si bien todavía no hay forma de hacer nada sobre la selección de todas las columnas excepto un bit, puede usar json_agg(to_json(b.col_1, b.col_2, b.col_3 ...))para obtener una matriz json de jsons cada una en el formato {"col_1":"col_1 value", ...}.

Entonces la consulta se vería así:

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;

y devolvería filas como:

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

(Ahora estoy en Postgres 9.5.3 y no estoy 100% seguro de cuándo se agregó este soporte).


1

Puedes usar json_build_objectasí

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;
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.