Seleziona le colonne all'interno di json_agg


21

Ho una domanda come:

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;

Come posso selezionare le colonne in bmodo da non avere b.item_idnell'oggetto JSON?

Ho letto ROW, ma restituisce un oggetto JSON come:

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

Dovrei rimappare l'oggetto JSON una volta recuperato per abbinare le chiavi di colonna appropriate. Vorrei evitarlo e conservare i nomi delle colonne originali.

Risposte:


50

Sfortunatamente, nella sintassi SQL non è prevista la dicitura "tutte le colonne tranne questa" . Puoi raggiungere il tuo obiettivo precisando l'elenco rimanente di colonne in un'espressione di tipo riga :

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;

Questo è breve per la forma più esplicita: . ROW(b.col1, b.col2, b.col3)

Tuttavia, i nomi delle colonne non vengono conservati nelle espressioni di tipo riga. In questo modo si ottengono nomi di chiavi generici nell'oggetto JSON. Vedo 3 opzioni per preservare i nomi delle colonne originali:

1. Trasmetti al tipo registrato

Trasmetti a un tipo di riga noto (registrato). Un tipo è registrato per ogni tabella o vista esistente o con un'istruzione esplicita CREATE TYPE. È possibile utilizzare una tabella temporanea per una soluzione ad hoc (durata per la durata della sessione):

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. Utilizzare una sottoselezione

Utilizzare una sottoselezione per costruire una tabella derivata e fare riferimento alla tabella nel suo insieme . Questo porta anche nomi di colonne. È più dettagliato, ma non è necessario un tipo registrato:

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()in Postgres 9.4 o successivo

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;

Relazionato:

Simile per jsonbcon le rispettive funzioni jsonb_agg()e jsonb_build_object().

Per Postgres 9.5 o versioni successive vedi anche la risposta di a_horse con una nuova variante di sintassi più breve: Postgres ha aggiunto l' operatore meno -perjsonb dire "tutte le chiavi tranne questa chiave" .
Poiché Postgres 10 "tranne diverse chiavi" è implementato con lo stesso operatore che prende text[]il 2o operando, come commentato da mlt.


1
> o più tasti Si noti che json (b) -text [] è disponibile a partire da 10.
mlt

La soluzione 3 ha funzionato per me come un incantesimo!
Luiz Fernando da Silva,

17

A partire da 9.6 puoi semplicemente usare -per rimuovere una chiave da 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à l'intera riga e - 'item_id'quindi rimuoverà la chiave con il nome item_idil cui risultato verrà quindi aggregato.


Questa nuova funzionalità sembra essere ciò che sperava l'OP. Ho aggiunto un link alla mia risposta.
Erwin Brandstetter,

Quando ho provato la variante di subselect, ho ricevuto un errore relativo alla json_aggfunzione:function json_agg(record) does not exist
fraxture

@fraxture: allora non stai usando Postgres 9.6
a_horse_with_no_name

In effetti quello era il problema. Esiste un modo per filtrare le colonne in v9.2?
fraternità

8

Puoi effettivamente farlo senza raggruppare, usando le subquery

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;

ritorna

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

Questo articolo di John Atten è davvero interessante e contiene maggiori dettagli


2

Ho scoperto che è meglio creare JSON, quindi aggregarlo. per esempio

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)

Nota che questo può essere fatto come una sottoquery se non ti piacciono i CTE (o se hai problemi di prestazioni a causa dell'utilizzo).

Nota anche che se lo farai molto, potrebbe essere utile creare una funzione per avvolgere le coppie chiave-valore per te in modo che il codice appaia più pulito. Passeresti la tua funzione (per esempio) 'ecks', 'x'e ritornerebbe "ecks": "x".


1

Anche se non c'è ancora modo di fare nulla per selezionare tutte le colonne tranne un bit, ma puoi usare json_agg(to_json(b.col_1, b.col_2, b.col_3 ...))per ottenere una matrice json di jsons ciascuna nel formato{"col_1":"col_1 value", ...} .

Quindi la query sarebbe simile a:

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;

e restituirebbe le righe come:

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

(Sono su Postgres 9.5.3 ora e non sono sicuro al 100% quando questo supporto è stato aggiunto.)


1

Puoi usare json_build_objectcosì

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;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.