Wie kann ich Elemente eines JSON-Arrays entfernen und nach Gruppen gruppieren?


8

In Anbetracht der bandTabelle mit einer jsonSpalte, die ein Array enthält:

id | people
---+-------------
1  | ['John', 'Thomas']
2  | ['John', 'James']
3  | ['James', 'George']

Wie liste ich die Anzahl der Bands auf, zu denen jeder Name gehört?
Gewünschte Ausgabe:

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

Antworten:


6

Der Datentyp der Spalte peopleist json, wie das Ergebnis von json_array_elements(people). Und es gibt keinen Gleichheitsoperator ( =) für den Datentyp json. Sie können also auch nicht darauf laufen GROUP BY. Mehr:

jsonbhat einen Gleichheitsoperator, daher besteht die "Problemumgehung" in Ihrer Antwort darin, jsonbdas Äquivalent zu verwenden und zu verwenden jsonb_array_elements(). Die Besetzung erhöht die Kosten:

jsonb_array_elements(people::jsonb)

Seit Postgres 9.4 haben wir auch json_array_elements_text(json)Array-Elemente als zurückgegeben text. Verbunden:

Damit:

SELECT p.name, count(*) AS c
FROM   band b, json_array_elements_text(b.people) p(name)
GROUP  BY p.name;

Es scheint bequemer zu sein, Namen textanstelle von jsonbObjekten zu erhalten (in Textdarstellung in doppelten Anführungszeichen), und Ihre "gewünschte Ausgabe" zeigt an, dass Sie textim Ergebnis zunächst wollen / brauchen .

GROUP BYon textdata ist auch billiger als on jsonb, daher sollte diese alternative "Problemumgehung" aus zwei Gründen schneller sein. (Test mit EXPLAIN (ANALYZE, TIMING OFF).)

Für die Aufzeichnung war nichts falsch mit Ihrer ursprünglichen Antwort . Das Komma ( ,) ist genauso "korrekt" wie CROSS JOIN LATERAL. Eine frühere Definition in Standard-SQL macht es nicht minderwertig. Sehen:

Es ist auch nicht portabler für andere RDBMS, und da es zunächst nicht für andere RDBMS portierbar ist jsonb_array_elements()oder json_array_elements_text()nicht, ist dies auch irrelevant. Die kurze Abfrage wird mit CROSS JOIN LATERALIMO nicht klarer , aber das letzte Stück ist nur meine persönliche Meinung.

Ich habe den expliziteren Tabellen- und Spaltenalias p(name)und die tabellenqualifizierte Referenz verwendet p.name, um mich gegen mögliche doppelte Namen zu verteidigen. nameist ein so gebräuchliches Wort, dass es möglicherweise auch als Spaltenname in der zugrunde liegenden Tabelle angezeigt bandwird. In diesem Fall wird es stillschweigend aufgelöst band.name. Das einfache Formular json_array_elements_text(people) namefügt nur einen Tabellenalias hinzu , der Spaltenname bleibt valuewie von der Funktion zurückgegeben. Aber namelöst es die einzige Spalte , valuewenn in der verwendeten SELECTListe. Es funktioniert wie erwartet . Aber ein wahrer Spaltenname name(falls band.namevorhanden) würde zuerst binden. Während das im gegebenen Beispiel nicht beißt, kann es in anderen Fällen eine geladene Fußwaffe sein.

Verwenden Sie zunächst nicht den generischen "Namen" als Kennung. Vielleicht war das nur für den einfachen Testfall.


Wenn die Spalte peoplealles andere als ein einfaches JSON-Array enthalten kann , würde jede Abfrage eine Ausnahme auslösen. Wenn Sie die Datenintegrität nicht garantieren können, möchten Sie möglicherweise Folgendes verteidigen 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

Schließt verletzende Zeilen von der Abfrage aus.

Verbunden:


4

Basierend auf dem Kommentar von @ ypercubeᵀᴹ endete ich mit:

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

Nur verwendet jsonb_array_elementsanstelle von unnest.


-1

Für jemanden in 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

mit Untertabelle wie: Spalte: idx, Zeile: 0,1,2,3,4,5,6,7,8,9 ...

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.