PostgreSQL-Ergebnismenge als JSON-Array zurückgeben?

134

Ich möchte, dass PostgreSQL das Ergebnis einer Abfrage als ein JSON-Array zurückgibt. Gegeben

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

Ich hätte gerne etwas ähnliches

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

oder

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

(Eigentlich wäre es sinnvoller, beide zu kennen). Ich habe einige Dinge wie ausprobiert

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;

Und ich fühle mich nah, aber nicht wirklich da. Sollte ich mir andere Unterlagen als 9.15 ansehen? JSON-Funktionen und Operatoren ?

Ich bin mir übrigens nicht sicher über meine Idee. Ist das eine übliche Designentscheidung? Ich denke, ich könnte natürlich das Ergebnis (zum Beispiel) der ersten der oben genannten 3 Abfragen nehmen und es in der Anwendung leicht bearbeiten, bevor ich es dem Client bereitstelle, aber wenn PostgreSQL das endgültige JSON-Objekt direkt erstellen kann, Es wäre einfacher, da ich noch keine Abhängigkeit von einer JSON-Bibliothek in meine Anwendung aufgenommen habe.

IngenieurX
quelle
1
PG 9.4, jetzt in Beta 1 verfügbar, bietet eine verbesserte Unterstützung für JSON, einschließlich binärer E / A. Wenn Sie sich auf einem Entwicklungscomputer befinden, sollten Sie ihn überprüfen.
Patrick
@Patrick: Danke, es sieht so aus, als ob json_object () eine neue Funktion in 9.4 ist und ich würde etwas wie SELECT json_object (array_agg (ta), array_agg (tb)) FROM t ausprobieren, wenn ich es hätte
engineeringX

Antworten:

264

TL; DR

SELECT json_agg(t) FROM t

für ein JSON-Array von Objekten und

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

für ein JSON-Objekt von Arrays.

Liste der Objekte

In diesem Abschnitt wird beschrieben, wie Sie ein JSON-Array von Objekten generieren, wobei jede Zeile in ein einzelnes Objekt konvertiert wird. Das Ergebnis sieht folgendermaßen aus:

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

9.3 und höher

Die json_aggFunktion erzeugt dieses Ergebnis sofort. Es findet automatisch heraus, wie seine Eingabe in JSON konvertiert und zu einem Array zusammengefasst wird.

SELECT json_agg(t) FROM t

Es gibt keine jsonb(in 9.4 eingeführte) Version von json_agg. Sie können die Zeilen entweder zu einem Array zusammenfassen und dann konvertieren:

SELECT to_jsonb(array_agg(t)) FROM t

oder json_aggmit einer Besetzung kombinieren :

SELECT json_agg(t)::jsonb FROM t

Meine Tests legen nahe, dass die Aggregation zu einem Array etwas schneller ist. Ich vermute, dass dies daran liegt, dass die Besetzung das gesamte JSON-Ergebnis analysieren muss.

9.2

9.2 verfügt nicht über die Funktionen json_aggoder to_json, daher müssen Sie die älteren verwenden array_to_json:

SELECT array_to_json(array_agg(t)) FROM t

Sie können optional einen row_to_jsonAnruf in die Abfrage aufnehmen:

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

Dadurch wird jede Zeile in ein JSON-Objekt konvertiert, die JSON-Objekte werden als Array zusammengefasst und anschließend wird das Array in ein JSON-Array konvertiert.

Ich konnte keinen signifikanten Leistungsunterschied zwischen den beiden feststellen.

Objekt von Listen

In diesem Abschnitt wird beschrieben, wie Sie ein JSON-Objekt generieren, wobei jeder Schlüssel eine Spalte in der Tabelle und jeder Wert ein Array der Werte der Spalte ist. Das Ergebnis sieht folgendermaßen aus:

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

9,5 und höher

Wir können die json_build_objectFunktion nutzen:

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

Sie können die Spalten auch aggregieren, eine einzelne Zeile erstellen und diese dann in ein Objekt konvertieren:

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

Beachten Sie, dass ein Aliasing der Arrays unbedingt erforderlich ist, um sicherzustellen, dass das Objekt die gewünschten Namen hat.

Welches klarer ist, ist Ansichtssache. Wenn Sie die json_build_objectFunktion verwenden, empfehle ich dringend, ein Schlüssel / Wert-Paar in eine Zeile zu setzen, um die Lesbarkeit zu verbessern.

Sie könnten auch array_agganstelle von verwenden json_agg, aber meine Tests zeigen, dass json_aggdas etwas schneller ist.

Es gibt keine jsonbVersion der json_build_objectFunktion. Sie können zu einer einzelnen Zeile zusammenfassen und Folgendes konvertieren:

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

Im Gegensatz zu den anderen Abfragen für diese Art von Ergebnis array_aggscheint die Verwendung etwas schneller zu sein to_jsonb. Ich vermute, dass dies auf das Overhead-Parsen und Validieren des JSON-Ergebnisses von zurückzuführen ist json_agg.

Oder Sie können eine explizite Besetzung verwenden:

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

Die to_jsonbVersion ermöglicht es Ihnen, die Besetzung zu vermeiden und ist laut meinen Tests schneller; Ich vermute erneut, dass dies auf den Aufwand für das Parsen und Validieren des Ergebnisses zurückzuführen ist.

9.4 und 9.3

Die json_build_objectFunktion war neu in 9.5, daher müssen Sie in früheren Versionen aggregieren und in ein Objekt konvertieren:

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

oder

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

je nachdem ob du willst jsonoder jsonb.

(9.3 hat nicht jsonb.)

9.2

In 9.2 existiert nicht einmal to_json. Sie müssen verwenden 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

Dokumentation

Die Dokumentation zu den JSON-Funktionen finden Sie unter JSON-Funktionen .

json_aggbefindet sich auf der Seite mit den Aggregatfunktionen .

Design

Wenn die Leistung wichtig ist, stellen Sie sicher, dass Sie Ihre Abfragen mit Ihrem eigenen Schema und Ihren eigenen Daten vergleichen, anstatt meinen Tests zu vertrauen.

Ob es sich um ein gutes Design handelt oder nicht, hängt wirklich von Ihrer spezifischen Anwendung ab. In Bezug auf die Wartbarkeit sehe ich kein besonderes Problem. Dies vereinfacht Ihren App-Code und bedeutet, dass in diesem Teil der App weniger zu warten ist. Wenn PG Ihnen genau das Ergebnis liefern kann, das Sie sofort benötigen, ist der einzige Grund, warum ich mir vorstellen kann, es nicht zu verwenden, Leistungsüberlegungen. Das Rad und alles nicht neu erfinden.

Nullen

Aggregatfunktionen geben normalerweise etwas zurück, NULLwenn sie über Nullzeilen arbeiten. Wenn dies eine Möglichkeit ist, möchten Sie sie möglicherweise COALESCEvermeiden. Einige Beispiele:

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

Oder

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

Dank an Hannes Landeholm für diesen Hinweis

jpmc26
quelle
3
Vielen Dank für Ihre Antwort. Sie haben mich dazu inspiriert, die Antwort auf meine zweite Frage zu finden: SELECT row_to_json (row (array_agg (ta), array_agg (tb))) FROM t, obwohl das Ergebnis "f1" und "f2" als Bezeichnungen anstelle von a und b enthält.
EngineerX
@engineerX Ich habe meine Antwort erweitert.
jpmc26
3
In einigen Fällen kann es unerwünscht sein, NULL anstelle eines leeren JSON-Arrays zurückzugewinnen, wenn die innere Auswahl (von t) null Zeilen zurückgibt. Dies wird durch Aggregatfunktionen verursacht, die beim Auswählen über keine Zeilen immer NULL zurückgeben und durch Koaleszenz lösbar sind: array_to_json (coalesce (array_agg (t), array [] :: record [])).
Hannes Landeholm
3
Sie können to_jsonanstelle von row_to_jsonundarray_to_json
itsnikolay
Um (mehrere) bestimmte Spalten auszuwählen, müssen Sie sie als einzelnes Argument übergeben - eine runde Klammerliste wie SELECT json_agg((column1, column2, ...)) FROM t - beachten Sie die zusätzlichen Klammern. Dies ist möglicherweise nicht offensichtlich "out of the box".
jave.web
19

Auch wenn Sie ausgewähltes Feld aus Tabelle und aggregiert dann als Array.

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

Das Ergebnis wird kommen.

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