web-dev-qa-db-fra.com

Sélectionner des colonnes dans json_agg

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 dans b pour ne pas avoir b.item_id dans 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.

24
Yanick Rochon

Malheureusement, il n'y a aucune disposition dans la syntaxe SQL pour dire "toutes les colonnes sauf cette seule colonne". Vous pouvez atteindre votre objectif en épelant 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 l'abréviation de la forme la plus explicite: ROW(b.col1, b.col2, b.col3).

Cependant, 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 instruction CREATE TYPE 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 jsonb avec les fonctions respectives jsonb_agg() et jsonb_build_object() .

Pour Postgres 9.5 ou plus tard, voir aussi réponse de a_horse avec une nouvelle variante de syntaxe plus courte: Postgres a ajouté le ( opérateur moins - pour jsonb pour dire "toutes les clés sauf celle-ci".
Puisque Postgres 10 "sauf plusieurs touches" est implémenté avec le même opérateur prenant text[] Comme le commentait le 2ème opérande - comme mlt.

58
Erwin Brandstetter

Depuis la version 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 et - 'item_id' supprimera alors la clé avec le nom item_id dont le résultat sera ensuite agrégé.

21

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

8
redben

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 il retournerait "ecks": "x".

2
MikeM

Bien qu'il n'y ait toujours aucun moyen de faire quoi que ce soit sur la sélection de 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é.)

1
David K

Vous pouvez utiliser json_build_object comme ç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;
1
Tan Duong