Jak zamienić tablicę Json w tablicę Postgres?


69

Mam kolumnę, dataktóra zawiera jsonmniej więcej taki dokument:

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

Chciałbym przekształcić zagnieżdżoną tagstablicę w konkatenowany ciąg ( foo, bar). Byłoby to łatwo możliwe z array_to_string()funkcją w teorii. Ta funkcja nie działa jednak na jsontablice. Zastanawiam się więc, jak zmienić tę jsontablicę w Postgres array?


Czy json_extract_path_text(your_column, 'tags') tego szukasz?
a_horse_w_no_name

1
@ a_horse_w_no_name: Pozostały problem: elementy tablicy są nadal cytowane dla formatu JSON. Tekst nie został poprawnie wyodrębniony ...
Erwin Brandstetter,

Odpowiedzi:


94

Postgres 9.4 lub nowszy

Oczywiście zainspirowany tym postem Postgres 9.4 dodał brakujące funkcje:
Podziękowania dla Laurence'a Rowe za łatkę i Andrew Dunstana za zatwierdzenie!

Aby odczepić tablicę JSON. Następnie użyj array_agg()lub konstruktora ARRAY, aby zbudować z niego tablicę Postgres . Lub string_agg()zbudować text ciąg .

Agreguj niezanieczyszczone elementy na wiersz w LATERALpodkwerendie lub skorelowanej. Następnie pierwotny porządek jest zachowany i nie musimy ORDER BY, GROUP BYa nawet unikalny klucz w zapytaniu zewnętrznym. Widzieć:

Zamień „json” na „jsonb” dla jsonbwszystkich następujących kodów 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;

Krótka składnia:

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;

Związane z:

Konstruktor ARRAY w skorelowanym podzapytaniu:

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

Związane z:

Subtelna różnica : nullelementy są zachowywane w rzeczywistych tablicach . Nie jest to możliwe w powyższych zapytaniach generujących textciąg, który nie może zawierać nullwartości. Prawdziwy obraz jest tablicą.

Opakowanie funkcji

W celu wielokrotnego użycia, aby uczynić to jeszcze prostszym, enkapsuluj logikę w funkcji:

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

Zrób z tego funkcję SQL , aby można ją było wstawiać w większych zapytaniach.
Zrób to IMMUTABLE(bo tak jest), aby uniknąć powtarzania oceny w większych zapytaniach i zezwól na to w wyrażeniach indeksowych.

Połączenie:

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

db <> skrzypce tutaj


Postgres 9.3 lub starszy

Użyj funkcji json_array_elements(). Ale otrzymujemy z niego ciągi cudzysłowów .

Alternatywne zapytanie z agregacją w zapytaniu zewnętrznym. CROSS JOINusuwa wiersze z brakującymi lub pustymi tablicami. Może być również przydatny do przetwarzania elementów. Potrzebujemy unikalnego klucza do agregacji:

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;

Konstruktor ARRAY, wciąż z ciągami cytowanymi:

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

Zauważ, że nulljest konwertowany na wartość tekstową „null”, inaczej niż powyżej. Niepoprawne, ściśle mówiąc i potencjalnie dwuznaczne.

Biedny człowiek nie cytuje 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;

Pobierz pojedynczy wiersz z 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;

Ciągi tworzą skorelowane podzapytanie:

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

Konstruktor ARRAY:

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

Oryginalne (nieaktualne) skrzypce SQL .
db <> skrzypce tutaj.

Związane z:

Uwagi (nieaktualne od str. 9.4)

Potrzebowalibyśmy json_array_elements_text(json)bliźniaka, json_array_elements(json)aby zwrócić odpowiednie textwartości z tablicy JSON. Ale wydaje się, że brakuje tego w arsenale funkcji JSON . Lub inna funkcja do wyodrębnienia textwartości z JSONwartości skalarnej . Wydaje mi się, że też tego brakuje.
Więc improwizowałem trim(), ale to się nie powiedzie w nietrywialnych przypadkach ...


Dobry post jak zawsze, ale ze swoją wiedzą na temat elementów wewnętrznych dlaczego nie ma obsady z tablicy-> jsonb. Rozumiem, że nie zaimplementowałem drugiej rzutowania, ponieważ tablica SQL jest silniej typowana. Czy tylko dlatego, że PostgreSQL jest niechętny automatycznemu generowaniu kodu do rzutowania (int [], bigint [], text []) itd.
Evan Carroll

3
@Evan: to_jsonb()Użyłbyś do konwersji tablica-> jsonb.
Erwin Brandstetter,

Czy SELECT ARRAY(SELECT json_array_elements_text(_js))naprawdę gwarantuje zachowanie kolejności tablic? Czy optymalizator nie może teoretycznie zmieniać kolejności wierszy wychodzących z tekstu json_array_elements_text?
Felix Geisendörfer

@ Felix: nie ma formalnej gwarancji w standardzie SQL. (z drugiej strony, zestaw zwracanych funkcji nie jest nawet dozwolony na liście SELECT w standardowym SQL na początek.), ale istnieje nieformalne stwierdzenie w podręczniku Postgres. patrz: dba.stackexchange.com/a/185862/3684 Mówiąc wprost - kosztem niewielkiej kary za wykonanie - patrz: dba.stackexchange.com/a/27287/3684 . Osobiście jestem w 100% pewien, że to konkretne wyrażenie działa zgodnie z oczekiwaniami w każdej obecnej i przyszłej wersji Postgres od wersji 9.4.
Erwin Brandstetter,

@ErwinBrandstetter bardzo dziękuję za potwierdzenie tego! Obecnie szukam artykułu, który podsumowuje formalne i nieformalne gwarancje zamawiania gwarancji zapewnianych przez PostgreSQL, a twoje odpowiedzi były niezwykle pomocne! Jeśli chcesz przejrzeć artykuł, daj mi znać, ale nie martw się, jeśli nie. Jestem niezmiernie wdzięczny za wkład StackOverflow i wiele się od ciebie nauczyłem przez lata!
Felix Geisendörfer

16

PG 9.4+

Akceptowana odpowiedź jest zdecydowanie tym, czego potrzebujesz, ale dla uproszczenia oto pomocnik, którego używam do tego:

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;

Następnie po prostu wykonaj:

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

Dodałem kilka szybszych wyrażeń do mojej odpowiedzi i prostszą funkcję. Może to być znacznie tańsze.
Erwin Brandstetter,

4
Ta funkcja powinna być czystym SQL, aby optymalizator mógł zajrzeć do niej. Nie musisz tutaj używać pgplsql.
Podziel

8

To pytanie zostało zadane na listach mailingowych PostgreSQL i wymyśliłem ten hackerski sposób konwersji tekstu JSON na tekst PostgreSQL za pomocą operatora ekstrakcji pola 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
 

Zasadniczo konwertuje wartość na tablicę jednoelementową, a następnie prosi o pierwszy element.

Innym podejściem byłoby użycie tego operatora do wyodrębnienia wszystkich pól jeden po drugim. Ale w przypadku dużych tablic jest to prawdopodobnie wolniejsze, ponieważ musi przeanalizować cały ciąg JSON dla każdego elementu tablicy, co prowadzi do złożoności 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

Przetestowałem kilka opcji. Oto moje ulubione zapytanie. Załóżmy, że mamy tabelę zawierającą pole id i json. Pole json zawiera tablicę, którą chcemy przekształcić w tablicę pg.

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

Działa gdziekolwiek i szybciej niż inne, ale wygląda chudo)

Najpierw tablica json jest rzutowana jako tekst, a następnie zmieniamy nawiasy kwadratowe na nawiasy. Na koniec tekst jest rzutowany jako tablica wymaganego typu.

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

a jeśli wolisz tablice tekstowe []

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"}}"Myślę, że musisz dodać wyjaśnienie, jak to ma działać.
dezso,

Pytanie brzmi: jak zamienić tablicę JSON (!) W tablicę pg. Załóżmy, że mam tabelę zawierającą kolumny id i jsonb. Kolumna JSONb zawiera tablicę json. Następnie
FiscalCliff

TRANSLATE (jsonb :: jsonb :: text, '[]', '{}') :: INT [] konwertuje tablicę json na tablicę pg.
FiscalCliff

SELECT translate('["foo", "bar"]'::jsonb::text, '[]','{}')::INT[]; ERROR: invalid input syntax for integer: "foo"Nie jest tak odporny na bomby ...
dezso

Rozważ użycie text [] dla tych tablic
FiscalCliff

0

Tych kilku funkcji, zaczerpniętych z odpowiedzi na to pytanie , używam i działają świetnie

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;

W każdym z nich, łącząc się z pustą tablicą, zajmują się sprawą, która zmusiła mnie do drżenia mózgu przez chwilę, w tym przypadku, jeśli spróbujesz rzucić pustą tablicę z json/ jsonbbez niej, nic nie otrzymasz, zamiast pusta tablica ( {}), jak można się spodziewać. Jestem pewien, że istnieje dla nich pewna optymalizacja, ale zostały one uproszczone w objaśnieniu tej koncepcji.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.