Como transformar json array em um postgres array?

69

Eu tenho uma coluna dataque contém um jsondocumento aproximadamente assim:

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

Gostaria de transformar a tagsmatriz aninhada em uma string concatenada ( foo, bar). Isso seria facilmente possível com a array_to_string()função na teoria. No entanto, essa função não atua em jsonmatrizes. Então, eu me pergunto como transformar essa jsonmatriz em um Postgres array?

Christoph
fonte
É o json_extract_path_text(your_column, 'tags') que você está procurando?
precisa saber é o seguinte
11
@a_horse_with_no_name: O problema restante: os elementos da matriz ainda são citados para o formato JSON. O texto não é devidamente extraídos ...
Erwin Brandstetter

Respostas:

94

Postgres 9.4 ou mais recente

Obviamente inspirado neste post , o Postgres 9.4 adicionou as funções ausentes:
Obrigado a Laurence Rowe pelo patch e Andrew Dunstan por cometer!

Desnaturar a matriz JSON. Em seguida, use array_agg()ou um construtor ARRAY para criar uma matriz do Postgres a partir dele. Ou string_agg()para construir uma text string .

Agregue elementos não aninhados por linha em uma LATERALsubconsulta ou correlacionada. O pedido original é preservado e não precisamos ORDER BY, GROUP BYnem mesmo uma chave exclusiva na consulta externa. Vejo:

Substitua 'json' por 'jsonb' jsonbem todos os seguintes 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;

Sintaxe curta:

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:

Construtor ARRAY na subconsulta correlacionada:

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

Relacionado:

Diferença sutil : os nullelementos são preservados em matrizes reais . Isso não é possível nas consultas acima produzindo uma textsequência que não pode conter nullvalores. A representação verdadeira é uma matriz.

Wrapper de função

Para uso repetido, para tornar isso ainda mais simples, encapsule a lógica em uma função:

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

Torne-a uma função SQL , para que possa ser incorporada em consultas maiores.
Faça isso IMMUTABLE(porque é) para evitar a avaliação repetida em consultas maiores e permita-o em expressões de índice.

Ligar:

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

db <> mexer aqui


Postgres 9.3 ou anterior

Use a função json_array_elements(). Mas obtemos strings com aspas duplas .

Consulta alternativa com agregação na consulta externa. CROSS JOINremove linhas com matrizes ausentes ou vazias. Também pode ser útil para processar elementos. Precisamos de uma chave exclusiva 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;

Construtor ARRAY, ainda com seqüências de caracteres citadas:

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

Observe que nullé convertido para o valor do texto "nulo", diferente da acima. Incorreto, estritamente falando, e potencialmente ambíguo.

O pobre homem não cita 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 uma única linha 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;

Strings formam subconsulta correlacionada:

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

Construtor ARRAY:

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

SQL Fiddle original (desatualizado) .
db <> mexa aqui.

Relacionado:

Notas (desatualizadas desde a página 9.4)

Nós precisaríamos de um json_array_elements_text(json), o gêmeo de, json_array_elements(json)para retornar textvalores adequados de uma matriz JSON. Mas isso parece estar faltando no arsenal fornecido de funções JSON . Ou alguma outra função para extrair um textvalor de um JSONvalor escalar . Parece que sinto falta também.
Então eu improvisei com trim(), mas isso falhará em casos não triviais ...

Erwin Brandstetter
fonte
Bom post como sempre, mas com o seu conhecimento dos internos, por que não é o elenco de array-> jsonb lá. Eu posso entender a não implementação do outro elenco porque o sql-array é mais fortemente digitado. É apenas porque o PostgreSQL é avesso à geração automática de código para converter (int [], bigint [], texto []) etc.] #
Evan Carroll
3
@Evan: Você usaria to_jsonb()para conversão de array-> jsonb.
Erwin Brandstetter
Será que SELECT ARRAY(SELECT json_array_elements_text(_js))realmente garantir que a ordem da matriz é preservado? O otimizador não pode alterar teoricamente a ordem das linhas que saem de json_array_elements_text?
Felix Geisendörfer
@ Felix: não há garantia formal no padrão SQL. (então, novamente, as funções de retorno definidas nem são permitidas na lista SELECT no SQL padrão para começar.) mas há uma afirmação informal no manual do Postgres. consulte: dba.stackexchange.com/a/185862/3684 Para ser explícito - ao custo de uma penalidade menor por desempenho - consulte: dba.stackexchange.com/a/27287/3684 . Pessoalmente, tenho 100% de certeza de que essa expressão específica funciona conforme o esperado em todas as versões presentes e futuras do Postgres desde a 9.4.
Erwin Brandstetter em
@ErwinBrandstetter muito obrigado por confirmar isso! Atualmente, estou pesquisando um artigo que resume as garantias formais e informais solicitando garantias fornecidas pelo PostgreSQL e suas respostas foram incrivelmente úteis! Se você estiver interessado em revisar o artigo, me avise, mas não se preocupe. Sou incrivelmente grato por suas contribuições ao StackOverflow e aprendi muito com você ao longo dos anos!
Felix Geisendörfer
16

PG 9.4+

A resposta aceita é definitivamente o que você precisa, mas por uma questão de simplicidade, aqui está um ajudante que eu uso para isso:

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;

Então faça:

SELECT jsonb_array_to_text_array('["a", "b", "c"]'::jsonb);
andrew.carpenter
fonte
Eu adicionei algumas expressões mais rápidas à minha resposta e uma função mais simples. Isso pode ser substancialmente mais barato.
Erwin Brandstetter
4
Essa função deve ser SQL puro, para que o otimizador possa espiá-la. Não há necessidade de usar o pgplsql aqui.
Dividir
8

Esta pergunta foi feita nas listas de discussão do PostgreSQL e eu vim com essa maneira imprudente de converter texto JSON em tipo de texto PostgreSQL através do operador de extração 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
 

Basicamente, ele converte o valor em uma matriz de elemento único e solicita o primeiro elemento.

Outra abordagem seria usar esse operador para extrair todos os campos um por um. Porém, para matrizes grandes, isso provavelmente é mais lento, pois precisa analisar toda a cadeia JSON de cada elemento da matriz, levando à complexidade de 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
fonte
1

Eu testei algumas opções. Aqui está minha consulta favorita. Suponha que tenhamos uma tabela contendo o campo id e json. O campo json contém array, que queremos transformar em pg array.

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

Está funcionando em qualquer lugar e mais rápido que outros, mas parece mal-humorado)

Primeiro, o array json é convertido como texto e, em seguida, apenas trocamos colchetes entre parênteses. Finalmente, o texto está sendo convertido como uma matriz do tipo necessário.

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

e se você preferir matrizes de texto []

SELECT TRANSLATE('[1]'::jsonb::text, '[]','{}')::TEXT[];
FiscalCliff
fonte
2
SELECT TRANSLATE('{"name": "foo", "tags": ["foo", "bar"]}'::jsonb::text, '[]','{}')::INT[]; ERROR: malformed array literal: "{"name": "foo", "tags": {"foo", "bar"}}"Eu acho que você tem que adicionar alguma explicação sobre como isso deve funcionar.
Dezso
A questão é como transformar a matriz JSON (!) Na matriz pg. Suponha que eu tenha a tabela contendo as colunas id e jsonb. A coluna JSONb contém matriz json. Então
FiscalCliff
TRANSLATE (jsonb :: jsonb :: text, '[]', '{}') :: INT [] converte a matriz json em pg.
FiscalCliff
SELECT translate('["foo", "bar"]'::jsonb::text, '[]','{}')::INT[]; ERROR: invalid input syntax for integer: "foo"Não é tão à prova de bomba ...
Dezso
Considere usar o texto [] para essas matrizes
FiscalCliff 6/16/16
0

Essas poucas funções, tiradas das respostas a esta pergunta , são o que eu estou usando e elas estão funcionando muito bem

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;

Em cada um deles, ao concatenar com uma matriz vazia, eles lidam com um caso que me atormentou um pouco, pois se você tentar lançar uma matriz vazia de json/ jsonbsem ela, não receberá nada de volta, em vez de um array vazio ( {}) como seria de esperar. Estou certo de que há alguma otimização para eles, mas eles são deixados para simplificar a explicação do conceito.

Joel B
fonte