it-swarm.com.de

Wie verwandle ich ein JSON-Array in ein Postgres-Array?

Ich habe eine Spalte data vom Typ json, die JSON-Dokumente wie folgt enthält:

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

Ich möchte das verschachtelte Array tags in eine verkettete Zeichenfolge ('foo, bar') Verwandeln. Das wäre theoretisch mit der Funktion array_to_string() leicht möglich. Diese Funktion akzeptiert jedoch keine Eingabe von json. Ich frage mich also, wie ich dieses JSON-Array in ein Postgres-Array verwandeln kann (Typ text[]).

77
Christoph

Postgres 9.4 oder neuer

Offensichtlich inspiriert von diesem Beitrag , Postgres 9.4 hat die fehlenden Funktionen hinzugefügt:
Danke an Laurence Rowe für den Patch und Andrew Dunstan für das Commit!

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

Aggregieren Sie nicht verschachtelte Elemente pro Zeile in einer LATERAL oder einer korrelierten Unterabfrage. Dann bleibt die ursprüngliche Reihenfolge erhalten und wir benötigen weder ORDER BY, GROUP BY Noch einen eindeutigen Schlüssel im äußeren Abfrage. Sehen:

Ersetzen Sie 'json' durch 'jsonb' für jsonb in allen folgenden SQL-Codes.

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 : null Elemente bleiben in tatsächlichen Arrays erhalten. Dies ist in den obigen Abfragen nicht möglich, die eine text Zeichenfolge erzeugen, die keine null Werte enthalten darf. Die wahre Darstellung ist ein Array.

Funktions-Wrapper

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

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

Machen Sie es zu einer SQL-Funktion , damit es in größeren Abfragen inlined sein kann.
Machen Sie es IMMUTABLE (weil es so ist), um eine wiederholte Auswertung bei größeren Abfragen zu vermeiden und es in Indexausdrücken zuzulassen.
Machen Sie es PARALLEL SAFE (In Postgres 9.6 oder später!), Der Parallelität nicht im Wege zu stehen. Sehen:

Anruf:

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

db <> fiddle hier


Postgres 9.3 oder älter

Verwenden Sie die Funktion json_array_elements() . Aber wir erhalten Zeichenfolgen in doppelten Anführungszeichen .

Alternative Abfrage mit Aggregation in der äußeren Abfrage. CROSS JOIN Entfernt Zeilen mit fehlenden oder leeren Arrays. Kann auch zur Verarbeitung von Elementen nützlich sein. Wir brauchen einen eindeutigen Schlüssel zum Aggregieren:

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 Anführungszeichen:

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

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

Der arme Mann zitiert 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 korrelierte Unterabfragen:

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;

Original (veraltet) SQL Fiddle .
db <> fiddle hier.

Verbunden:

Anmerkungen (veraltet seit S. 9.4)

Wir würden ein json_array_elements_text(json) brauchen, den Zwilling von json_array_elements(json) to Rückgabe der richtigen text -Werte aus einem JSON-Array. Aber das scheint im bereitgestelltes Arsenal an JSON-Funktionen zu fehlen. Oder eine andere Funktion, um einen text -Wert aus einem skalaren JSON -Wert zu extrahieren. Mir scheint auch dieser zu fehlen.
Also habe ich mit trim() improvisiert, aber das wird in nicht trivialen Fällen fehlschlagen ...

99

PG 9.4 +

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

CREATE OR REPLACE FUNCTION jsonb_array_to_text_array(p_input jsonb)
 RETURNS text[]
 LANGUAGE sql
 IMMUTABLE
AS $function$

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

$function$;

Dann machen Sie einfach:

SELECT jsonb_array_to_text_array('["a", "b", "c"]'::jsonb);

Aktualisiert am 23.02.2020 als Antwort auf Kommentare: Kommentare sind korrekt, dass dies effizienter sein könnte. Zu dem Zeitpunkt, als ich gepostet habe, wurde keine modularisierte Lösung angeboten, daher habe ich ernsthaft eine angeboten, wenn auch nicht optimal. Seitdem hat Erwin seine Antwort mit einer einfachen und effizienten Funktion aktualisiert, sodass ich meine nie aktualisiert habe. Aktualisieren Sie es jetzt, da diese Antwort immer noch beachtet wird

Noch ein Update, weil mich das nur gebissen hat: Die obige Funktion gibt null zurück, wenn es keine Werte gibt. Dies ist abhängig von Ihrer Situation möglicherweise nicht wünschenswert. Hier ist eine Funktion, die ein leeres Array zurückgibt, wenn der Wert nicht null ist, aber dennoch null zurückgibt, wenn die Eingabe null ist.

CREATE OR REPLACE FUNCTION jsonb_array_to_text_array_strict(p_input jsonb)
 RETURNS text[]
 LANGUAGE sql
 IMMUTABLE
AS $function$

SELECT 
  CASE 
    WHEN p_input IS null 
    THEN null 
    ELSE coalesce(ary_out, ARRAY[]::text[]) 
  END
FROM (
  SELECT array_agg(ary)::text[] AS ary_out
  FROM jsonb_array_elements_text(p_input) AS ary
) AS extracted;

$function$
;
17

Diese Frage wurde auf den PostgreSQL-Mailinglisten gestellt, und ich habe mir diese hackige Methode ausgedacht, um JSON-Text über den JSON-Feldextraktionsoperator in den PostgreSQL-Texttyp zu konvertieren:

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 jedoch wahrscheinlich langsamer, da die gesamte JSON-Zeichenfolge 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
 ☃
8
intgr

Ich habe einige Optionen getestet. Hier ist meine Lieblingsabfrage. Angenommen, wir haben eine Tabelle mit den Feldern id und json. Das json-Feld enthält ein Array, das wir in ein pg-Array umwandeln möchten.

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

Es funktioniert überall und schneller als andere, sieht aber krückenhaft aus.

Zuerst wird das JSON-Array als Text umgewandelt, und dann ändern wir nur eckige 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[];
2
FiscalCliff

Diese wenigen Funktionen, die aus den Antworten auf diese Frage stammen, 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 behandeln sie durch Verketten mit einem leeren Array einen Fall, bei dem ich mir ein bisschen den Kopf zerbrochen habe, wenn Sie versuchen, ein leeres Array aus json/jsonb zu werfen ohne sie erhalten Sie nichts zurück, anstatt ein leeres Array ({}) wie zu erwarten. Ich bin mir sicher, dass es einige Optimierungen für sie gibt, aber sie bleiben der Einfachheit halber bei der Erklärung des Konzepts.

0
Joel B