Comment désinstaller et regrouper des éléments d'un tableau JSON?


8

Étant donné la bandtable, avec une jsoncolonne 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 désirée:

name   | count
-------+------------
John   | 2
James  | 2
Thomas | 1
George | 1

Réponses:


7

Le type de données de la colonne peopleest json, 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 courir GROUP BYdessus. Plus:

jsonba un opérateur d'égalité, donc la "solution de contournement" dans votre réponse consiste à convertir jsonbet à 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)renvoyé des éléments de 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 textplutôt que des jsonbobjets (entre guillemets dans la représentation textuelle) et votre "sortie souhaitée" indique que vous voulez / avez besoin textdans le résultat pour commencer.

GROUP BYsur les textdonnées est également moins cher 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 avec votre réponse originale . 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 plus portable à d' autres SGBDR, et depuis jsonb_array_elements()ou json_array_elements_text()ne sont pas portables à d' autres SGBDR pour commencer, qui est également hors de propos. La courte requête ne devient pas plus claire avec CROSS JOIN LATERALIMO, 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.namepour se défendre contre les noms en double possibles. nameest un mot si courant qu'il peut également apparaître comme nom de colonne dans la table sous-jacente band, auquel cas il se résoudrait en silence band.name. Le formulaire simple json_array_elements_text(people) nameattache uniquement un alias de table , le nom de la colonne est toujours value, tel que renvoyé par la fonction. Mais namerésout sa seule colonne valuelorsqu'il est utilisé dans la SELECTliste. Cela fonctionne comme prévu . Mais un vrai nom de colonne name(s'il band.namedevrait exister) se lierait en premier. Bien que cela ne morde pas dans l'exemple donné, il peut s'agir d'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 peoplepeut 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:


4

Sur la base du commentaire @ ypercubeᵀᴹ, je me suis retrouvé avec:

SELECT name, count(*) as c
FROM band 
CROSS JOIN LATERAL jsonb_array_elements(people::jsonb) as name
GROUP BY name;

Juste utilisé jsonb_array_elementsau lieu de unnest.


-1

Pour quelqu'un dans MySQL

SELECT
  JSON_EXTRACT(people, CONCAT('$[', idx, ']')) AS name, count(*) as count
FROM yourtable
JOIN subtable AS indexes
WHERE JSON_EXTRACT(people, CONCAT('$[', idx, '].id')) IS NOT NULL
group by name

avec sous-table comme: Colum: idx, ligne: 0,1,2,3,4,5,6,7,8,9 ...

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.