Wie verwandle ich ein json Array in ein postgres Array?

69

Ich habe eine Spalte data, die ein jsonDokument ungefähr so ​​enthält:

{
    "name": "foo",
    "tags": ["foo", "bar"]
}

Ich möchte das verschachtelte tagsArray in eine verkettete Zeichenfolge ( foo, bar) verwandeln . Das wäre mit der array_to_string()Funktion in der Theorie leicht möglich . Diese Funktion wirkt sich jedoch nicht auf jsonArrays aus. Also frage ich mich, wie ich dieses jsonArray in ein Postgres verwandeln kann array?

Christoph
quelle
Ist json_extract_path_text(your_column, 'tags') was du suchst
a_horse_with_no_name
1
@a_horse_with_no_name: Das verbleibende Problem: Array-Elemente werden für das JSON-Format weiterhin in Anführungszeichen gesetzt. Text wird nicht richtig extrahiert ...
Erwin Brandstetter

Antworten:

94

Postgres 9.4 oder neuer

Postgres 9.4 wurde offensichtlich von diesem Beitrag inspiriert und fügte die fehlenden Funktionen hinzu:
Danke an Laurence Rowe für den Patch und Andrew Dunstan für das Commit!

So entfernen Sie das JSON-Array. Verwenden Sie dann array_agg()oder einen ARRAY-Konstruktor , um ein Postgres- Array daraus zu erstellen . Oder string_agg()um einen text String zu bauen .

Aggregieren Sie nicht verschachtelte Elemente pro Zeile in einer LATERALoder einer korrelierten Unterabfrage. Dann wird ursprünglicher Auftrag erhalten und wir nicht brauchen ORDER BY, GROUP BYoder sogar ein eindeutiger Schlüssel in der äußeren Abfrage. Sehen:

Ersetzen Sie 'json' jsonbin allen folgenden SQL-Codes durch 'jsonb' .

SELECT t.tbl_id, d.list
FROM   tbl t
CROSS  JOIN LATERAL (
   SELECT string_agg(d.elem::text, ', ') AS list
   FROM   json_array_elements_text(t.data->'tags') AS d(elem)
   ) d;

Kurze Syntax:

SELECT t.tbl_id, d.list
FROM   tbl t, LATERAL (
   SELECT string_agg(value::text, ', ') AS list
   FROM   json_array_elements_text(t.data->'tags')  -- col name default: "value"
   ) d;

Verbunden:

ARRAY-Konstruktor in korrelierter Unterabfrage:

SELECT tbl_id, ARRAY(SELECT json_array_elements_text(t.data->'tags')) AS txt_arr
FROM   tbl t;

Verbunden:

Subtiler Unterschied : nullElemente bleiben in tatsächlichen Arrays erhalten . Dies ist in den obigen Abfragen, die eine textZeichenfolge erzeugen , die keine nullWerte enthalten kann, nicht möglich . Die wahre Darstellung ist ein Array.

Funktions-Wrapper

Um dies noch einfacher zu machen, kapseln Sie bei wiederholter Verwendung die Logik in eine Funktion:

CREATE OR REPLACE FUNCTION json_arr2text_arr(_js json)
  RETURNS text[] LANGUAGE sql IMMUTABLE AS
'SELECT ARRAY(SELECT json_array_elements_text(_js))';

Machen Sie es zu einer SQL-Funktion , damit es in größeren Abfragen eingebunden werden kann.
Machen Sie es IMMUTABLE(weil es ist), um wiederholte Auswertung in größeren Abfragen zu vermeiden und es in Indexausdrücken zuzulassen.

Anruf:

SELECT tbl_id, json_arr2text_arr(data->'tags')
FROM   tbl;

db <> hier fummeln


Postgres 9.3 oder älter

Nutzen Sie die Funktion json_array_elements(). Aber wir bekommen doppelte Anführungszeichen .

Alternative Abfrage mit Aggregation in der äußeren Abfrage. CROSS JOINEntfernt Zeilen mit fehlenden oder leeren Arrays. Kann auch zur Verarbeitung von Elementen nützlich sein. Wir benötigen einen eindeutigen Schlüssel für die Aggregation:

SELECT t.tbl_id, string_agg(d.elem::text, ', ') AS list
FROM   tbl t
CROSS  JOIN LATERAL json_array_elements(t.data->'tags') AS d(elem)
GROUP  BY t.tbl_id;

ARRAY-Konstruktor, immer noch mit zitierten Zeichenfolgen:

SELECT tbl_id, ARRAY(SELECT json_array_elements(t.data->'tags')) AS quoted_txt_arr
FROM   tbl t;

Beachten Sie, dass nullim Gegensatz zu oben in den Textwert "null" konvertiert wird. Falsch, genau genommen und möglicherweise mehrdeutig.

Der arme Mann spricht nicht mit trim():

SELECT t.tbl_id, string_agg(trim(d.elem::text, '"'), ', ') AS list
FROM   tbl t, json_array_elements(t.data->'tags') d(elem)
GROUP  BY 1;

Rufen Sie eine einzelne Zeile von tbl ab:

SELECT string_agg(trim(d.elem::text, '"'), ', ') AS list
FROM   tbl t, json_array_elements(t.data->'tags') d(elem)
WHERE  t.tbl_id = 1;

Zeichenfolgen bilden eine korrelierte Unterabfrage:

SELECT tbl_id, (SELECT string_agg(trim(value::text, '"'), ', ')
                FROM   json_array_elements(t.data->'tags')) AS list
FROM   tbl t;

ARRAY-Konstruktor:

SELECT tbl_id, ARRAY(SELECT trim(value::text, '"')
                     FROM   json_array_elements(t.data->'tags')) AS txt_arr
FROM   tbl t;

Ursprüngliche (veraltete) SQL-Geige .
db <> hier fummeln .

Verbunden:

Anmerkungen (veraltet seit Seite 9.4)

Wir benötigen a json_array_elements_text(json), den Zwilling von json_array_elements(json), um korrekte textWerte aus einem JSON-Array zurückzugeben. Dies scheint jedoch im bereitgestellten Arsenal von JSON-Funktionen zu fehlen . Oder eine andere Funktion, um einen textWert aus einem Skalarwert zu extrahieren JSON. Das scheint mir auch zu fehlen.
Also habe ich mit improvisiert trim(), aber das wird für nicht-triviale Fälle scheitern ...

Erwin Brandstetter
quelle
Guter Beitrag wie immer, aber mit Ihrem Wissen über die Interna, warum ist die Besetzung von Array-> Jsonb nicht da. Ich kann verstehen, dass die andere Besetzung nicht implementiert wird, da das SQL-Array stärker typisiert ist. Ist es nur, weil PostgreSQL es ablehnt, automatisch Code zu generieren (int [], bigint [], text []) usw.
Evan Carroll
3
@Evan: Du würdest to_jsonb()für die Array-> Jsonb-Konvertierung verwenden.
Erwin Brandstetter
Ist SELECT ARRAY(SELECT json_array_elements_text(_js))wirklich gewährleistet, dass die Reihenfolge des Arrays erhalten bleibt? Darf der Optimierer nicht theoretisch die Reihenfolge der Zeilen ändern, die aus json_array_elements_text stammen?
Felix Geisendörfer
@Felix: Es gibt keine formale Garantie im SQL-Standard. (Andererseits sind set returning-Funktionen in der SELECT-Liste in Standard-SQL zunächst nicht einmal zulässig.) Es gibt jedoch eine informelle Aussage im Postgres-Handbuch. siehe: dba.stackexchange.com/a/185862/3684 Um ausdrücklich zu sein - auf Kosten einer geringfügigen Leistungsstrafe - siehe: dba.stackexchange.com/a/27287/3684 . Persönlich bin ich mir zu 100% sicher, dass dieser spezielle Ausdruck in jeder gegenwärtigen und zukünftigen Version von Postgres seit 9.4 erwartungsgemäß funktioniert.
Erwin Brandstetter
@ErwinBrandstetter vielen Dank für die Bestätigung! Ich recherchiere gerade für einen Artikel, in dem die formellen und informellen Garantien zusammengefasst sind. Bestellgarantien von PostgreSQL und Ihre Antworten waren unglaublich hilfreich! Wenn Sie Interesse haben, den Artikel zu lesen, lassen Sie es mich wissen, aber keine Sorge, wenn nicht. Ich bin unglaublich dankbar für Ihre StackOverflow-Beiträge und habe im Laufe der Jahre viel von Ihnen gelernt!
Felix Geisendörfer
16

PG 9.4+

Die akzeptierte Antwort ist definitiv das, was Sie brauchen, aber der Einfachheit halber hier ist ein Helfer, den ich dafür benutze:

CREATE OR REPLACE FUNCTION jsonb_array_to_text_array(
  p_input jsonb
) RETURNS TEXT[] AS $BODY$

DECLARE v_output text[];

BEGIN

  SELECT array_agg(ary)::text[]
  INTO v_output
  FROM jsonb_array_elements_text(p_input) AS ary;

  RETURN v_output;

END;

$BODY$
LANGUAGE plpgsql VOLATILE;

Dann machen Sie einfach:

SELECT jsonb_array_to_text_array('["a", "b", "c"]'::jsonb);
andrew.carpenter
quelle
Ich habe meiner Antwort einige schnellere Ausdrücke und eine einfachere Funktion hinzugefügt. Dies kann wesentlich günstiger sein.
Erwin Brandstetter
4
Diese Funktion sollte reines SQL sein, damit der Optimierer einen Blick darauf werfen kann. Hier muss pgplsql nicht verwendet werden.
Teilen Sie den
8

Diese Frage wurde in den PostgreSQL-Mailinglisten gestellt und ich fand diese hackige Methode zum Konvertieren von JSON-Text in PostgreSQL-Text über den JSON-Feldextraktionsoperator:

CREATE FUNCTION json_text(json) RETURNS text IMMUTABLE LANGUAGE sql
AS $$ SELECT ('['||$1||']')::json->>0 $$;

db=# select json_text(json_array_elements('["hello",1.3,"\u2603"]'));
 json_text 
-----------
 hello
 1.3
 

Grundsätzlich konvertiert es den Wert in ein Einzelelement-Array und fragt dann nach dem ersten Element.

Ein anderer Ansatz wäre, diesen Operator zu verwenden, um alle Felder einzeln zu extrahieren. Bei großen Arrays ist dies wahrscheinlich langsamer, da der gesamte JSON-String für jedes Array-Element analysiert werden muss, was zu einer Komplexität von O (n ^ 2) führt.

CREATE FUNCTION json_array_elements_text(json) RETURNS SETOF text IMMUTABLE LANGUAGE sql
AS $$ SELECT $1->>i FROM generate_series(0, json_array_length($1)-1) AS i $$;

db=# select json_array_elements_text('["hello",1.3,"\u2603"]');
 json_array_elements_text 
--------------------------
 hello
 1.3
 
intgr
quelle
1

Ich habe ein paar Optionen getestet. Hier ist meine Lieblingsfrage. Angenommen, wir haben eine Tabelle mit ID und JSON-Feld. Das json-Feld enthält array, das wir in pg array umwandeln wollen.

SELECT * 
FROM   test 
WHERE  TRANSLATE(jsonb::jsonb::text, '[]','{}')::INT[] 
       && ARRAY[1,2,3];

Es funktioniert überall und ist schneller als andere, sieht aber mürrisch aus.)

Zuerst wird das JSON-Array als Text umgewandelt, und dann ändern wir die eckigen Klammern in Klammern. Schließlich wird der Text als Array des erforderlichen Typs umgewandelt.

SELECT TRANSLATE('[1]'::jsonb::text, '[]','{}')::INT[];

und wenn Sie Text [] Arrays bevorzugen

SELECT TRANSLATE('[1]'::jsonb::text, '[]','{}')::TEXT[];
Fiskalklippe
quelle
2
SELECT TRANSLATE('{"name": "foo", "tags": ["foo", "bar"]}'::jsonb::text, '[]','{}')::INT[]; ERROR: malformed array literal: "{"name": "foo", "tags": {"foo", "bar"}}"Ich denke, Sie müssen eine Erklärung hinzufügen, wie dies funktionieren soll.
Dezso
Die Frage ist, wie man aus einem JSON-Array (!) Ein pg-Array macht. Angenommen, ich habe die Tabelle mit den Spalten id und jsonb. Die JSONb-Spalte enthält das JSON-Array. Dann
FiscalCliff
TRANSLATE (jsonb :: jsonb :: text, '[]', '{}') :: INT [] konvertiert ein json-Array in ein pg-Array.
FiscalCliff
SELECT translate('["foo", "bar"]'::jsonb::text, '[]','{}')::INT[]; ERROR: invalid input syntax for integer: "foo"Es ist nicht so bombensicher ...
6.
Erwägen
0

Diese wenigen Funktionen, die den Antworten auf diese Frage entnommen sind, werden von mir verwendet und funktionieren hervorragend

CREATE OR REPLACE FUNCTION json_array_casttext(json) RETURNS text[] AS $f$
    SELECT array_agg(x) || ARRAY[]::text[] FROM json_array_elements_text($1) t(x);
$f$ LANGUAGE sql IMMUTABLE;

CREATE OR REPLACE FUNCTION jsonb_array_casttext(jsonb) RETURNS text[] AS $f$
    SELECT array_agg(x) || ARRAY[]::text[] FROM jsonb_array_elements_text($1) t(x);
$f$ LANGUAGE sql IMMUTABLE;

CREATE OR REPLACE FUNCTION json_array_castint(json) RETURNS int[] AS $f$
    SELECT array_agg(x)::int[] || ARRAY[]::int[] FROM json_array_elements_text($1) t(x);
$f$ LANGUAGE sql IMMUTABLE;

CREATE OR REPLACE FUNCTION jsonb_array_castint(jsonb) RETURNS int[] AS $f$
    SELECT array_agg(x)::int[] || ARRAY[]::int[] FROM jsonb_array_elements_text($1) t(x);
$f$ LANGUAGE sql IMMUTABLE;

In jedem von ihnen wird durch die Verkettung mit einem leeren Array ein Fall behandelt, bei dem ich mir ein wenig den Kopf zerbrochen habe. Wenn Sie versuchen, ein leeres Array von json/ jsonbohne Array zu werfen , wird statt eines nichts zurückgegeben leeres Array ( {}), wie Sie es erwarten würden. Ich bin mir sicher, dass es Optimierungen für sie gibt, aber sie bleiben der Einfachheit halber bei der Erläuterung des Konzepts erhalten.

Joel B
quelle