Sélectionner des colonnes dans json_agg

21

J'ai une requête comme:

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;

Comment puis-je sélectionner les colonnes bafin que je n'en ai pas b.item_iddans l'objet JSON?

J'ai lu ROW, mais il renvoie un objet JSON comme:

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

J'aurais besoin de remapper l'objet JSON une fois qu'il est récupéré pour correspondre aux clés de colonne appropriées. Je voudrais éviter cela et conserver les noms de colonnes d'origine.

Yanick Rochon
la source

Réponses:

50

Malheureusement, il n'y a aucune disposition dans la syntaxe SQL pour dire "toutes les colonnes sauf celle-ci" . Vous pouvez atteindre votre objectif en précisant la liste restante des colonnes dans une expression de type ligne :

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;

C'est court pour la forme plus explicite: . ROW(b.col1, b.col2, b.col3)

Cependant, les noms de colonnes ne sont pas conservés dans les expressions de type ligne. Vous obtenez ainsi des noms de clé génériques dans l'objet JSON. Je vois 3 options pour conserver les noms de colonnes d'origine:

1. Cast au type enregistré

Cast sur un type de ligne bien connu (enregistré). Un type est enregistré pour chaque table ou vue existante ou avec une CREATE TYPEinstruction explicite . Vous pouvez utiliser une table temporaire pour une solution ad-hoc (vit pour la durée de la session):

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. Utilisez une sous-sélection

Utilisez une sous-sélection pour construire une table dérivée et référencer la table dans son ensemble . Cela porte également des noms de colonne. Il est plus détaillé, mais vous n'avez pas besoin d'un type enregistré:

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()dans Postgres 9.4 ou version ultérieure

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;

En relation:

Similaire pour jsonbles fonctions respectives jsonb_agg()et jsonb_build_object().

Pour Postgres 9.5 ou version ultérieure, voir également la réponse de a_horse avec une nouvelle variante de syntaxe plus courte: Postgres a ajouté l' opérateur moins -pourjsonb dire "toutes les clés sauf celle-ci" .
Étant donné que Postgres 10 "sauf plusieurs touches" est implémenté avec le même opérateur prenant text[]comme commentaire le 2ème opérande comme mlt.

Erwin Brandstetter
la source
1
> ou plusieurs touches Notez que json (b) -text [] est disponible à partir de 10.
mlt
La solution 3 a fonctionné pour moi comme un charme!
Luiz Fernando da Silva
17

À partir de 9.6, vous pouvez simplement utiliser -pour supprimer une clé d'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)convertira toute la ligne, - 'item_id'puis supprimera la clé avec le nom dont item_idle résultat est ensuite agrégé.

un cheval sans nom
la source
Ces nouvelles fonctionnalités semblent être ce que le PO espérait. J'ai ajouté un lien à ma réponse.
Erwin Brandstetter
Lorsque j'ai essayé la variante de sous-sélection, j'ai eu une erreur liée à la json_aggfonction:function json_agg(record) does not exist
fraxture
@fraxture: alors vous n'utilisez pas Postgres 9.6
a_horse_with_no_name
C'était effectivement le problème. Existe-t-il un moyen de filtrer les colonnes dans la version 9.2?
fraxture
8

Vous pouvez le faire sans groupe en utilisant des sous-requêtes

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;

Retour

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

Cet article de John Atten est vraiment intéressant et contient plus de détails

redben
la source
2

J'ai trouvé qu'il était préférable de créer le JSON, puis de l'agréger. par exemple

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)

Notez que cela peut être fait en tant que sous-requête si vous n'aimez pas les CTE (ou si vous rencontrez des problèmes de performances en raison de son utilisation).

Notez également que si vous allez souvent faire cela, il peut être avantageux de créer une fonction pour envelopper les paires clé-valeur pour que le code soit plus propre. Vous passeriez votre fonction (par exemple) 'ecks', 'x'et elle reviendrait "ecks": "x".

MikeM
la source
1

Bien qu'il n'y ait toujours aucun moyen de faire quoi que ce soit pour sélectionner toutes les colonnes sauf un bit, mais vous pouvez utiliser json_agg(to_json(b.col_1, b.col_2, b.col_3 ...))pour obtenir un tableau json de jsons chacune au format {"col_1":"col_1 value", ...}.

Ainsi, la requête ressemblerait à quelque chose comme:

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;

et retournerait des lignes comme:

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

(Je suis maintenant sur Postgres 9.5.3 et je ne suis pas sûr à 100% quand ce support a été ajouté.)

David K
la source
1

Vous pouvez utiliser json_build_objectcomme ça

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;
Tan Duong
la source