PostgreSQL restituisce il risultato impostato come array JSON?


135

Vorrei che PostgreSQL restituisse il risultato di una query come un array JSON. Dato

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

Vorrei qualcosa di simile a

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

o

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

(in realtà sarebbe più utile conoscerli entrambi). Ho provato alcune cose come

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;

E mi sento vicino, ma non ci sono davvero. Dovrei guardare altra documentazione tranne la 9.15. Funzioni e operatori JSON ?

A proposito, non sono sicuro della mia idea. È una normale decisione di design? Il mio pensiero è che potrei, ovviamente, prendere il risultato (ad esempio) della prima delle 3 query precedenti e manipolarlo leggermente nell'applicazione prima di servirlo al client, ma se PostgreSQL può creare direttamente l'oggetto JSON finale, sarebbe più semplice, perché ancora non ho incluso alcuna dipendenza da nessuna libreria JSON nella mia applicazione.


1
PG 9.4, ora disponibile nella versione beta 1, ha migliorato il supporto per JSON, incluso I / O binario. Se sei su una macchina di sviluppo potresti voler controllare.
Patrick

@Patrick: grazie, sembra che json_object () sia una nuova funzione in 9.4 e proverei qualcosa come SELECT json_object (array_agg (ta), array_agg (tb)) FROM t, se lo avessi
engineerX

Risposte:


266

TL; DR

SELECT json_agg(t) FROM t

per una matrice JSON di oggetti e

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

per un oggetto JSON di array.

Elenco di oggetti

Questa sezione descrive come generare un array JSON di oggetti, con ogni riga convertita in un singolo oggetto. Il risultato è simile a questo:

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

9.3 e versioni successive

La json_aggfunzione produce questo risultato fuori dagli schemi. Capisce automaticamente come convertire il suo input in JSON e lo aggrega in un array.

SELECT json_agg(t) FROM t

Non esiste una versione jsonb(introdotta nella 9.4) di json_agg. Puoi aggregare le righe in un array e quindi convertirle:

SELECT to_jsonb(array_agg(t)) FROM t

o combinalo json_aggcon un cast:

SELECT json_agg(t)::jsonb FROM t

I miei test suggeriscono che aggregarli prima in un array è un po 'più veloce. Sospetto che ciò sia dovuto al fatto che il cast deve analizzare l'intero risultato JSON.

9.2

9.2 non ha le funzioni json_aggo to_json, quindi è necessario utilizzare la versione precedente array_to_json:

SELECT array_to_json(array_agg(t)) FROM t

Facoltativamente, puoi includere una row_to_jsonchiamata nella query:

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

Questo converte ogni riga in un oggetto JSON, aggrega gli oggetti JSON come un array e quindi converte l'array in un array JSON.

Non sono riuscito a distinguere alcuna differenza significativa di prestazioni tra i due.

Oggetto delle liste

Questa sezione descrive come generare un oggetto JSON, in cui ogni chiave è una colonna nella tabella e ogni valore è un array dei valori della colonna. È il risultato che assomiglia a questo:

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

9.5 e versioni successive

Possiamo sfruttare la json_build_objectfunzione:

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

Puoi anche aggregare le colonne, creando una singola riga e quindi convertirla in un oggetto:

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

Notare che l'aliasing degli array è assolutamente necessario per garantire che l'oggetto abbia i nomi desiderati.

Quale sia più chiaro è una questione di opinione. Se si utilizza iljson_build_object funzione, consiglio vivamente di mettere una coppia chiave / valore su una riga per migliorare la leggibilità.

Potresti anche usare array_aggal posto di json_agg, ma i miei test indicano che json_aggè leggermente più veloce.

Non esiste una jsonbversione della json_build_objectfunzione. Puoi aggregare in una singola riga e convertire:

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

A differenza delle altre query per questo tipo di risultato, array_aggsembra essere un po 'più veloce durante l'utilizzo to_jsonb. Sospetto che ciò sia dovuto all'overhead dell'analisi e alla convalida del risultato JSON di json_agg.

Oppure puoi usare un cast esplicito:

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

La to_jsonbversione consente di evitare il cast ed è più veloce, secondo i miei test; di nuovo, sospetto che ciò sia dovuto al sovraccarico dell'analisi e della convalida del risultato.

9.4 e 9.3

La json_build_objectfunzione era nuova nella 9.5, quindi devi aggregare e convertire in un oggetto nelle versioni precedenti:

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

a seconda che tu voglia jsono jsonb.

(9.3 non ha jsonb.)

9.2

In 9.2, non to_jsonesiste nemmeno . Devi usare 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

Documentazione

Trova la documentazione per le funzioni JSON nelle funzioni JSON .

json_aggsi trova nella pagina delle funzioni aggregate .

Design

Se le prestazioni sono importanti, assicurati di confrontare le tue query con il tuo schema e i tuoi dati, piuttosto che fidarti dei miei test.

Che si tratti di un buon design o meno dipende dall'applicazione specifica. In termini di manutenibilità, non vedo alcun problema particolare. Semplifica il codice dell'app e significa che c'è meno da mantenere in quella parte dell'app. Se PG può darti esattamente il risultato di cui hai bisogno fuori dagli schemi, l'unica ragione per cui posso pensare di non usarlo sarebbero considerazioni sulle prestazioni. Non reinventare la ruota e tutto il resto.

Null

Le funzioni aggregate in genere restituiscono NULLquando operano su zero righe. Se questa è una possibilità, potresti volerla usare COALESCEper evitarli. Un paio di esempi:

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

O

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

Ringraziamo Hannes Landeholm per averlo sottolineato


3
La ringrazio per la risposta. Mi hai ispirato a trovare la risposta alla mia seconda domanda, SELECT row_to_json (row (array_agg (ta), array_agg (tb))) FROM t, sebbene il risultato abbia "f1" e "f2" come etichette invece di ae b.
engineerX

@engineerX Ho ampliato la mia risposta.
jpmc26

3
In alcuni casi può essere indesiderabile recuperare NULL invece di un array JSON vuoto quando la selezione interna (da t) restituisce zero righe. Ciò è causato dalle funzioni aggregate che restituiscono sempre NULL quando la selezione su nessuna riga e risolvibile da coalesce: array_to_json (coalesce (array_agg (t), array [] :: record [])).
Hannes Landeholm

3
puoi usare al to_jsonposto di row_to_jsonearray_to_json
itsnikolay

Per selezionare (più) colonne specifiche, è necessario passarle come un singolo argomento - come un elenco di parentesi tonde SELECT json_agg((column1, column2, ...)) FROM t - notare le parentesi extra. Questo potrebbe non essere ovvio "fuori dagli schemi".
jave.web

19

Inoltre, se si desidera un campo selezionato dalla tabella e quindi aggregato come matrice.

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

Il risultato arriverà.

 [{'data_a':1,'data_b':'value1'}
  {'data_a':2,'data_b':'value2'}]
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.