PostgreSQL renvoie l'ensemble de résultats en tant que tableau JSON?

135

Je voudrais que PostgreSQL renvoie le résultat d'une requête sous la forme d'un tableau JSON. Donné

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

Je voudrais quelque chose de similaire à

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

ou

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

(en fait, il serait plus utile de connaître les deux). J'ai essayé des choses comme

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;

Et je sens que je suis proche, mais pas vraiment là. Dois-je regarder d'autres documents à l'exception de 9.15. Fonctions et opérateurs JSON ?

Au fait, je ne suis pas sûr de mon idée. Est-ce une décision de conception habituelle? Je pense que je pourrais, bien sûr, prendre le résultat (par exemple) de la première des 3 requêtes ci-dessus et le manipuler légèrement dans l'application avant de le servir au client, mais si PostgreSQL peut créer directement l'objet JSON final, ce serait plus simple, car je n'ai toujours inclus aucune dépendance sur une bibliothèque JSON dans mon application.

ingénieurX
la source
1
PG 9.4, désormais disponible dans la version bêta 1, a amélioré la prise en charge de JSON, y compris les E / S binaires. Si vous êtes sur une machine de développement, vous voudrez peut-être la vérifier.
Patrick
@Patrick: merci, cela ressemble à json_object () est une nouvelle fonction dans 9.4 et j'essaierais quelque chose comme SELECT json_object (array_agg (ta), array_agg (tb)) FROM t, si je l'avais
engineerX

Réponses:

266

TL; DR

SELECT json_agg(t) FROM t

pour un tableau d'objets JSON, et

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

pour un objet JSON de tableaux.

Liste des objets

Cette section décrit comment générer un tableau d'objets JSON, chaque ligne étant convertie en un seul objet. Le résultat ressemble à ceci:

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

9.3 et plus

La json_aggfonction produit ce résultat hors de la boîte. Il détermine automatiquement comment convertir son entrée en JSON et l'agrège en un tableau.

SELECT json_agg(t) FROM t

Il n'y a pas de version jsonb(introduite dans 9.4) de json_agg. Vous pouvez soit agréger les lignes dans un tableau, puis les convertir:

SELECT to_jsonb(array_agg(t)) FROM t

ou combiner json_aggavec un casting:

SELECT json_agg(t)::jsonb FROM t

Mes tests suggèrent que les agréger d'abord dans un tableau est un peu plus rapide. Je soupçonne que c'est parce que le casting doit analyser l'intégralité du résultat JSON.

9.2

9.2 n'a pas les fonctions json_aggou to_json, vous devez donc utiliser l'ancienne array_to_json:

SELECT array_to_json(array_agg(t)) FROM t

Vous pouvez éventuellement inclure un row_to_jsonappel dans la requête:

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

Cela convertit chaque ligne en objet JSON, agrège les objets JSON en tant que tableau, puis convertit le tableau en tableau JSON.

Je n'ai pas pu discerner de différence de performance significative entre les deux.

Objet des listes

Cette section décrit comment générer un objet JSON, chaque clé étant une colonne de la table et chaque valeur étant un tableau des valeurs de la colonne. C'est le résultat qui ressemble à ceci:

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

9.5 et plus

Nous pouvons tirer parti de la json_build_objectfonction:

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

Vous pouvez également agréger les colonnes, créer une seule ligne, puis la convertir en objet:

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

Notez que l'aliasing des tableaux est absolument nécessaire pour garantir que l'objet a les noms souhaités.

Laquelle est la plus claire est une question d’opinion. Si vous utilisez la json_build_objectfonction, je recommande fortement de mettre une paire clé / valeur sur une ligne pour améliorer la lisibilité.

Vous pouvez également utiliser array_aggà la place de json_agg, mais mes tests indiquent que json_aggc'est légèrement plus rapide.

Il n'y a pas de jsonbversion de la json_build_objectfonction. Vous pouvez regrouper en une seule ligne et convertir:

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

Contrairement aux autres requêtes pour ce type de résultat, array_aggsemble être un peu plus rapide lors de l'utilisation to_jsonb. Je soupçonne que cela est dû à une analyse des frais généraux et à la validation du résultat JSON de json_agg.

Ou vous pouvez utiliser une distribution explicite:

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

La to_jsonbversion vous permet d'éviter le casting et est plus rapide, selon mes tests; encore une fois, je soupçonne que cela est dû à la surcharge de l'analyse et de la validation du résultat.

9.4 et 9.3

La json_build_objectfonction était nouvelle dans la version 9.5, vous devez donc agréger et convertir en objet dans les versions précédentes:

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

ou

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

selon que vous voulez jsonou jsonb.

(9.3 n'a pas jsonb.)

9.2

En 9.2, to_jsonn'existe même pas . Vous devez utiliser 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

Documentation

Recherchez la documentation des fonctions JSON dans les fonctions JSON .

json_aggse trouve sur la page des fonctions d'agrégation .

Conception

Si les performances sont importantes, assurez-vous de comparer vos requêtes à votre propre schéma et à vos propres données, plutôt que de faire confiance à mes tests.

Que ce soit une bonne conception ou pas dépend vraiment de votre application spécifique. En termes de maintenabilité, je ne vois pas de problème particulier. Cela simplifie le code de votre application et signifie qu'il y a moins à maintenir dans cette partie de l'application. Si PG peut vous donner exactement le résultat dont vous avez besoin, la seule raison à laquelle je pense pour ne pas l'utiliser serait des considérations de performances. Ne réinventez pas la roue et tout.

Nulls

Les fonctions d'agrégation donnent généralement en retour NULLlorsqu'elles fonctionnent sur zéro ligne. Si c'est une possibilité, vous voudrez peut-être l'utiliser COALESCEpour les éviter. Quelques exemples:

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

Ou

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

Crédit à Hannes Landeholm pour avoir signalé

jpmc26
la source
3
Merci pour votre réponse. Vous m'avez inspiré pour trouver la réponse à ma deuxième question, SELECT row_to_json (row (array_agg (ta), array_agg (tb))) FROM t, bien que le résultat ait "f1" et "f2" comme étiquettes au lieu de a et b.
ingénieurX
@engineerX J'ai développé ma réponse.
jpmc26
3
Il peut être indésirable dans certains cas de récupérer NULL au lieu d'un tableau JSON vide lorsque la sélection interne (à partir de t) renvoie zéro ligne. Cela est dû au fait que les fonctions d'agrégation retournent toujours NULL lors de la sélection sur aucune ligne et peuvent être résolues par coalesce: array_to_json (coalesce (array_agg (t), array [] :: record [])).
Hannes Landeholm
3
vous pouvez utiliser à la to_jsonplace de row_to_jsonetarray_to_json
itsnikolay
Pour sélectionner (plusieurs) colonnes spécifiques, vous devez les passer comme un seul argument - une liste de parenthèses rondes comme SELECT json_agg((column1, column2, ...)) FROM t - notez les crochets supplémentaires. Cela peut ne pas être évident "hors de la boîte".
jave.web
19

Aussi si vous voulez un champ sélectionné à partir de la table et agrégé alors en tant que tableau.

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

Le résultat viendra.

 [{'data_a':1,'data_b':'value1'}
  {'data_a':2,'data_b':'value2'}]
Himanshu sharma
la source