Étant donné la table band
, avec une colonne json
contenant un tableau:
id | people
---+-------------
1 | ['John', 'Thomas']
2 | ['John', 'James']
3 | ['James', 'George']
Comment lister le nombre de bandes dont chaque nom fait partie?
sortie souhaitée:
name | count
-------+------------
John | 2
James | 2
Thomas | 1
George | 1
Le type de données de la colonne people
est json
, tout comme le résultat de json_array_elements(people)
. Et il n'y a pas d'opérateur d'égalité (=
) Pour le type de données json
. Vous ne pouvez donc pas non plus exécuter GROUP BY
Dessus. Plus:
jsonb
possède un opérateur d'égalité, donc la "solution de contournement" dans votre réponse consiste à transtyper en jsonb
et à utiliser l'équivalent jsonb_array_elements()
. Le casting ajoute du coût:
jsonb_array_elements(people::jsonb)
Depuis Postgres 9.4, nous avons également json_array_elements_text(json)
renvoyant les éléments du tableau sous la forme text
. En relation:
Donc:
SELECT p.name, count(*) AS c
FROM band b, json_array_elements_text(b.people) p(name)
GROUP BY p.name;
Il semble plus pratique d'obtenir des noms sous la forme d'objets text
au lieu de jsonb
(entre guillemets dans la représentation textuelle) et votre "sortie souhaitée" indique que vous voulez/avez besoin text
dans le résultat pour commencer.
GROUP BY
Sur text
les données sont également moins chères que sur jsonb
, donc cette solution alternative devrait être plus rapide pour deux raisons. (Testez avec EXPLAIN (ANALYZE, TIMING OFF)
.)
Pour mémoire, il n'y avait rien de mal à votre réponse d'origine . La virgule (,
) Est tout aussi "correcte" que CROSS JOIN LATERAL
. Le fait d'avoir été défini plus tôt dans SQL standard ne le rend pas inférieur. Voir:
Il n'est pas non plus plus portable pour les autres SGBDR, et puisque jsonb_array_elements()
ou json_array_elements_text()
ne sont pas portables pour les autres SGBDR pour commencer, ce n'est pas non plus pertinent. La courte requête ne devient pas plus claire avec CROSS JOIN LATERAL
IMO, mais le dernier bit est juste mon opinion personnelle.
J'ai utilisé l'alias de table et de colonne plus explicite p(name)
et la référence qualifiée de table p.name
Pour se défendre contre les noms en double possibles. name
est un tel mot commun, il peut également apparaître comme nom de colonne dans la table sous-jacente band
, auquel cas il se résoudrait silencieusement en band.name
. La forme simple json_array_elements_text(people) name
attache uniquement un alias table, le nom de la colonne est toujours value
, tel que renvoyé par la fonction. Mais name
résout sa seule colonne value
lorsqu'il est utilisé dans la liste SELECT
. Il arrive à fonctionner comme prévu. Mais un vrai nom de colonne name
(si band.name
Devait exister) se lierait en premier. Bien que cela ne morde pas dans l'exemple donné, il peut s'agir d'un une arme à pied chargée dans d'autres cas.
N'utilisez pas le "nom" générique comme identifiant pour commencer. Peut-être que c'était juste pour le cas de test simple.
Si la colonne people
peut contenir autre chose qu'un simple tableau JSON, l'une ou l'autre requête déclencherait une exception. Si vous ne pouvez pas garantir l'intégrité des données, vous voudrez peut-être vous défendre avec json_typeof()
:
SELECT p.name, count(*) AS c
FROM band b, json_array_elements_text(b.people) p(name)
WHERE json_typeof(b.people) = 'array'
GROUP BY 1; -- optional short syntax since you seem to prefer short syntax
Exclut les lignes violées de la requête.
En relation:
Basé sur @ ypercubeᵀᴹ commentaire avec lequel je me suis retrouvé:
SELECT name, count(*) as c
FROM band
CROSS JOIN LATERAL jsonb_array_elements(people::jsonb) as name
GROUP BY name;
Juste utilisé jsonb_array_elements
au lieu de unnest
.