Come trasformare array JSON in array Postgres?


69

Ho una colonna datache contiene un jsondocumento più o meno in questo modo:

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

Vorrei trasformare l' tagsarray nidificato in una stringa concatenata ( foo, bar). Ciò sarebbe facilmente possibile con la array_to_string()funzione in teoria. Tuttavia, questa funzione non agisce sugli jsonarray. Quindi mi chiedo come trasformare questo jsonarray in un Postgres array?


È json_extract_path_text(your_column, 'tags') quello che stai cercando?
a_horse_with_no_name

1
@a_horse_with_no_name: il problema rimanente: gli elementi dell'array sono ancora quotati per il formato JSON. Il testo non è stato estratto correttamente ...
Erwin Brandstetter,

Risposte:


94

Postgres 9.4 o successivo

Ovviamente ispirato a questo post , Postgres 9.4 ha aggiunto le funzioni mancanti:
Grazie a Laurence Rowe per la patch e Andrew Dunstan per l'impegno!

Per rovesciare l'array JSON. Quindi utilizzare array_agg()o un costruttore ARRAY per costruire un array Postgres da esso. O string_agg()per costruire una text stringa .

Aggrega elementi non desiderati per riga in una LATERALo sottoquery correlata. Quindi l' ordine originale viene preservato e non è necessario ORDER BY, GROUP BYo anche una chiave univoca nella query esterna. Vedere:

Sostituisci "json" con "jsonb" per jsonbin tutto il seguente codice SQL.

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;

Sintassi breve:

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;

Relazionato:

Costruttore ARRAY in subquery correlata:

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

Relazionato:

Differenza sottile : gli nullelementi sono conservati in array reali . Questo non è possibile nelle query sopra che producono una textstringa, che non può contenere nullvalori. La vera rappresentazione è un array.

Funzione wrapper

Per un uso ripetuto, per rendere questo ancora più semplice, incapsulare la logica in una funzione:

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

Renderlo una funzione SQL , quindi può essere integrato in query più grandi.
Renderlo IMMUTABLE(perché lo è) per evitare ripetute valutazioni in query più grandi e consentirlo nelle espressioni di indice.

Chiamata:

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

db <> violino qui


Postgres 9.3 o precedente

Utilizzare la funzione json_array_elements(). Ma otteniamo stringhe tra virgolette doppie da esso.

Query alternativa con aggregazione nella query esterna. CROSS JOINrimuove le righe con array mancanti o vuoti. Può essere utile anche per l'elaborazione di elementi. Abbiamo bisogno di una chiave univoca per aggregare:

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;

Costruttore ARRAY, ancora con stringhe tra virgolette:

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

Si noti che nullviene convertito nel valore di testo "null", diversamente da quanto sopra. Inesatto, a rigor di termini e potenzialmente ambiguo.

Povero uomo che non sta citando 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;

Recupera una singola riga da tbl:

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;

Sottoquery correlata forma stringhe:

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

Costruttore ARRAY:

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

Fiddle SQL originale (obsoleto) .
db <> violino qui.

Relazionato:

Note (obsolete da pag 9.4)

Avremmo bisogno di un json_array_elements_text(json), il gemello di json_array_elements(json)per restituire i textvalori corretti da un array JSON. Ma questo sembra mancare dall'arsenale fornito di funzioni JSON . O qualche altra funzione per estrarre un textvalore da un JSONvalore scalare . Mi sembra che manchi anche quello.
Così ho improvvisato con trim(), ma ciò fallirà per casi non banali ...


Buon post come sempre, ma con la tua conoscenza degli interni perché non è il cast di array-> jsonb lì. Posso capire di non implementare l'altro cast perché sql-array è tipizzato più fortemente. È solo perché PostgreSQL è contrario alla generazione automatica del codice da trasmettere (int [], bigint [], text []) ecc.
Evan Carroll,

3
@Evan: useresti to_jsonb()per la conversione array-> jsonb.
Erwin Brandstetter,

Fa SELECT ARRAY(SELECT json_array_elements_text(_js))davvero garantire che l'ordinamento della matrice è conservato? L'ottimizzatore non è autorizzato a modificare teoricamente l'ordine delle righe che escono da json_array_elements_text?
Felix Geisendörfer,

@Felix: non esiste una garanzia formale nello standard SQL. (anche in questo caso, le funzioni di ritorno impostate non sono nemmeno consentite nell'elenco SELECT in SQL standard per cominciare.) Ma c'è un'affermazione informale nel manuale di Postgres. vedi: dba.stackexchange.com/a/185862/3684 Per essere espliciti - a costo di una penalità minore sulle prestazioni - vedi: dba.stackexchange.com/a/27287/3684 . Personalmente, sono sicuro al 100% che questa particolare espressione funzioni come previsto in ogni versione presente e futura di Postgres dal 9.4.
Erwin Brandstetter,

@ErwinBrandstetter grazie mille per averlo confermato! Attualmente sto facendo delle ricerche per un articolo che riassume le garanzie formali e informali che ordinano le garanzie fornite da PostgreSQL e le tue risposte sono state incredibilmente utili! Se ti interessa rivedere l'articolo, fammelo sapere, ma non preoccuparti. Sono incredibilmente grato per i tuoi contributi StackOverflow e ho imparato molto da te nel corso degli anni!
Felix Geisendörfer,

16

PG 9.4+

La risposta accettata è sicuramente ciò di cui hai bisogno, ma per semplicità ecco un aiuto che uso per questo:

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;

Quindi fai semplicemente:

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

Ho aggiunto alcune espressioni più veloci alla mia risposta e una funzione più semplice. Questo può essere sostanzialmente più economico.
Erwin Brandstetter,

4
Questa funzione dovrebbe essere SQL puro in modo che l'ottimizzatore possa sbirciare. Non c'è bisogno di usare pgplsql qui.
Dividi il

8

Questa domanda è stata posta sulle mailing list di PostgreSQL e mi è venuta in mente questa maniera hacker di convertire il testo JSON in tipo di testo PostgreSQL tramite l'operatore di estrazione di campi JSON:

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
 

Fondamentalmente converte il valore in una matrice a elemento singolo e quindi chiede il primo elemento.

Un altro approccio sarebbe quello di utilizzare questo operatore per estrarre tutti i campi uno per uno. Ma per array di grandi dimensioni questo è probabilmente più lento, poiché deve analizzare l'intera stringa JSON per ciascun elemento dell'array, portando alla complessità O (n ^ 2).

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
 

1

Ho testato alcune opzioni. Ecco la mia domanda preferita. Supponiamo di avere una tabella contenente id e json field. Il campo json contiene array, che vogliamo trasformare in array pg.

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

Funziona ovunque e più velocemente di altri, ma sembra stampella)

In primo luogo la matrice json viene lanciata come testo, quindi cambiamo solo le parentesi quadre in parentesi. Finalmente il testo viene lanciato come matrice del tipo richiesto.

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

e se preferisci le matrici di testo []

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

2
SELECT TRANSLATE('{"name": "foo", "tags": ["foo", "bar"]}'::jsonb::text, '[]','{}')::INT[]; ERROR: malformed array literal: "{"name": "foo", "tags": {"foo", "bar"}}"Penso che tu debba aggiungere qualche spiegazione su come dovrebbe funzionare.
dezso,

La domanda è come trasformare un array JSON (!) In un array pg. Supponiamo di avere la tabella contenente colonne id e jsonb. La colonna JSONb contiene l'array json. Quindi
FiscalCliff il

TRANSLATE (jsonb :: jsonb :: text, '[]', '{}') :: INT [] converte array json in array pg.
FiscalCliff

SELECT translate('["foo", "bar"]'::jsonb::text, '[]','{}')::INT[]; ERROR: invalid input syntax for integer: "foo"Non è così a prova di bomba ...
Dezso

Prendi in considerazione l'uso del testo [] per questi array
FiscalCliff il

0

Queste poche funzioni, prese dalle risposte a questa domanda , sono ciò che sto usando e stanno funzionando alla grande

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 ognuno di essi, concatenandosi con un array vuoto, gestiscono un caso che mi ha fatto distrarre un po 'il cervello, nel senso che se provi a lanciare un array vuoto da json/ jsonbsenza di esso non otterrai nulla, al posto di un array vuoto ( {}) come ti aspetteresti. Sono certo che ci sia un po 'di ottimizzazione per loro, ma sono lasciati per semplicità nella spiegazione del concetto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.