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.