¿Cómo convertir json array en matriz de postgres?

69

Tengo una columna dataque contiene un jsondocumento más o menos así:

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

Me gustaría convertir la tagsmatriz anidada en una cadena concatenada ( foo, bar). Eso sería fácilmente posible con la array_to_string()función en teoría. Sin embargo, esta función no actúa en jsonmatrices. ¿Entonces me pregunto cómo convertir esta jsonmatriz en un Postgres array?

Christoph
fuente
¿Es json_extract_path_text(your_column, 'tags') lo que estás buscando?
a_horse_with_no_name
1
@a_horse_with_no_name: El problema restante: los elementos de la matriz todavía se citan para el formato JSON. El texto no se extrae correctamente ...
Erwin Brandstetter

Respuestas:

94

Postgres 9.4 o más reciente

Obviamente inspirado en esta publicación , Postgres 9.4 agregó las funciones que faltan: ¡
Gracias a Laurence Rowe por el parche y a Andrew Dunstan por comprometerse!

Para deshacer la matriz JSON. Luego use array_agg()o un constructor ARRAY para construir una matriz Postgres a partir de él. O string_agg()para construir una text cadena .

Agregue elementos no anidados por fila en una LATERALsubconsulta correlacionada. Luego se conserva el orden original y no necesitamos ORDER BY, GROUP BYni siquiera una clave única en la consulta externa. Ver:

Reemplace 'json' con 'jsonb' jsonben todos los siguientes códigos 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;

Sintaxis corta:

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;

Relacionado:

ARRAY constructor en subconsulta correlacionada:

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

Relacionado:

Diferencia sutil : los nullelementos se conservan en matrices reales . Esto no es posible en las consultas anteriores que producen una textcadena, que no puede contener nullvalores. La verdadera representación es una matriz.

Envoltorio de funciones

Para uso repetido, para hacer esto aún más simple, encapsule la lógica en una función:

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

Lo convierten en una función SQL , para que pueda ser inline en las consultas más grandes.
Hágalo IMMUTABLE(porque lo es) para evitar evaluaciones repetidas en consultas más grandes y permitirlo en expresiones de índice.

Llamada:

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

db <> violín aquí


Postgres 9.3 o anterior

Usa la función json_array_elements(). Pero obtenemos cadenas dobles entre comillas .

Consulta alternativa con agregación en la consulta externa. CROSS JOINelimina filas con matrices faltantes o vacías. También puede ser útil para procesar elementos. Necesitamos una clave única para agregar:

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 constructor, todavía con cadenas citadas:

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

Tenga en cuenta que nullse convierte en el valor de texto "nulo", a diferencia de lo anterior. Incorrecto, estrictamente hablando, y potencialmente ambiguo.

Pobre hombre citando con 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;

Recupere una sola fila de 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;

Las cadenas forman una subconsulta correlacionada:

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

ARRAY constructor:

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

Fiddle SQL original (obsoleto) .
db <> violín aquí.

Relacionado:

Notas (desactualizadas desde la página 9.4)

Necesitaríamos un json_array_elements_text(json), el gemelo de json_array_elements(json)para devolver textvalores adecuados de una matriz JSON. Pero eso parece faltar en el arsenal provisto de funciones JSON . O alguna otra función para extraer un textvalor de un JSONvalor escalar . Parece que me estoy perdiendo ese también.
Así que improvisé trim(), pero eso fallará en casos no triviales ...

Erwin Brandstetter
fuente
Buena publicación como siempre, pero con su conocimiento de las partes internas, ¿por qué no está el elenco de array-> jsonb allí? Puedo entender no implementar el otro elenco porque el sql-array está más fuertemente tipado. ¿Es solo porque PostgreSQL es contrario a la generación automática de código para emitir (int [], bigint [], texto []) etc.
Evan Carroll
3
@Evan: Lo to_jsonb()usarías para la conversión de matriz-> jsonb.
Erwin Brandstetter
¿ SELECT ARRAY(SELECT json_array_elements_text(_js))Realmente garantiza que se conserva el orden de la matriz? ¿No se le permite al optimizador alterar teóricamente el orden de las filas que salen de json_array_elements_text?
Felix Geisendörfer
@Felix: no hay una garantía formal en el estándar SQL. (para empezar, las funciones de retorno establecidas ni siquiera están permitidas en la lista SELECT en SQL estándar para empezar), pero hay una afirmación informal en el manual de Postgres. consulte: dba.stackexchange.com/a/185862/3684 Para ser explícito, a costa de una multa menor de rendimiento, consulte: dba.stackexchange.com/a/27287/3684 . Personalmente, estoy 100% seguro de que esta expresión en particular funciona como se espera en cada versión presente y futura de Postgres desde la 9.4.
Erwin Brandstetter
@ErwinBrandstetter ¡muchas gracias por confirmar esto! Actualmente estoy investigando un artículo que resume las garantías formales e informales que piden las garantías proporcionadas por PostgreSQL y sus respuestas han sido increíblemente útiles. Si le interesa revisar el artículo, hágamelo saber, pero no se preocupe si no. ¡Estoy increíblemente agradecido por sus contribuciones a StackOverflow y aprendí mucho de usted a lo largo de los años!
Felix Geisendörfer
16

PG 9.4+

La respuesta aceptada es definitivamente lo que necesita, pero por simplicidad, aquí hay una ayuda que uso para esto:

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;

Entonces solo haz:

SELECT jsonb_array_to_text_array('["a", "b", "c"]'::jsonb);
andrew.carpenter
fuente
Agregué algunas expresiones más rápidas a mi respuesta y una función más simple. Esto puede ser sustancialmente más barato.
Erwin Brandstetter
44
Esta función debe ser SQL puro para que el optimizador pueda verla. No es necesario usar pgplsql aquí.
Divida el
8

Esta pregunta se hizo en las listas de correo de PostgreSQL y se me ocurrió esta forma hack de convertir texto JSON a tipo de texto PostgreSQL a través del operador de extracción de campo 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
 

Básicamente, convierte el valor en una matriz de un solo elemento y luego solicita el primer elemento.

Otro enfoque sería utilizar este operador para extraer todos los campos uno por uno. Pero para matrices grandes esto es probablemente más lento, ya que necesita analizar toda la cadena JSON para cada elemento de la matriz, lo que lleva a una complejidad 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
 
intgr
fuente
1

He probado algunas opciones. Aquí está mi consulta favorita. Supongamos que tenemos una tabla que contiene id y campo json. El campo json contiene una matriz, que queremos convertir en matriz pg.

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

Funciona en cualquier lugar y más rápido que otros, pero parece muuuuuuuuuuy)

Primero, la matriz json se convierte como texto, y luego solo cambiamos los corchetes por paréntesis. Finalmente, el texto se está convirtiendo en una matriz del tipo requerido.

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

y si prefieres las matrices de texto []

SELECT TRANSLATE('[1]'::jsonb::text, '[]','{}')::TEXT[];
Acantilado fiscal
fuente
2
SELECT TRANSLATE('{"name": "foo", "tags": ["foo", "bar"]}'::jsonb::text, '[]','{}')::INT[]; ERROR: malformed array literal: "{"name": "foo", "tags": {"foo", "bar"}}"Creo que tienes que agregar alguna explicación sobre cómo se supone que funciona.
dezso
La pregunta es cómo convertir la matriz JSON (!) En matriz pg. Supongamos que tengo la tabla que contiene las columnas id y jsonb. La columna JSONb contiene una matriz json. Entonces
FiscalCliff
TRANSLATE (jsonb :: jsonb :: text, '[]', '{}') :: INT [] convierte la matriz json en matriz pg.
FiscalCliff
SELECT translate('["foo", "bar"]'::jsonb::text, '[]','{}')::INT[]; ERROR: invalid input syntax for integer: "foo"No es tan a prueba de bombas ...
dezso
Considere el uso de texto [] para estas matrices
FiscalCliff
0

Estas pocas funciones, tomadas de las respuestas a esta pregunta , son lo que estoy usando y están funcionando muy bien

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;

En cada uno de ellos, al concatenar con una matriz vacía, manejan un caso que me hizo aturdir mi cerebro por un momento, en el que si intentas lanzar una matriz vacía desde json/ jsonbsin ella no obtendrás nada, en lugar de un matriz vacía ( {}) como cabría esperar. Estoy seguro de que hay algo de optimización para ellos, pero se dejan como es para simplificar la explicación del concepto.

Joel B
fuente