Як перетворити масив json в масив postgres?


69

У мене стовпчик, dataякий містить jsonдокумент приблизно так:

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

Я хотів би перетворити вкладений tagsмасив у об'єднаний рядок ( foo, bar). Це було б легко можливо array_to_string()в теоретичній функції. Однак ця функція не діє на jsonмасиви. Тож мені цікаво, як перетворити цей jsonмасив у Postgres array?


Це json_extract_path_text(your_column, 'tags') те, що ви шукаєте?
a_horse_with_no_name

1
@a_horse_with_no_name: Проблема, що залишилася: елементи масиву все ще цитуються у форматі JSON. Текст неправильно вилучений ...
Ервін Брандштеттер

Відповіді:


94

Постгреси 9.4 або новіші

Очевидно, надихнувшись цією публікацією , Postgres 9.4 додав відсутні функції:
Дякую Лоренсу Роу за патч та Ендрю Данстану за вчинення!

Щоб зняти масив JSON. Потім використовуйте array_agg()або конструктор ARRAY, щоб створити з нього масив Postgres . Або string_agg()побудувати text рядок .

Об'єднайте немережі елементи на рядок у підзапросі LATERALчи кореляції. Тоді оригінальне замовлення зберігається і нам не потрібен ORDER BY, GROUP BYа то й унікальний ключ у зовнішньому запиті. Побачити:

Замініть "json" на "jsonb" на jsonbвсі наступні 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;

Короткий синтаксис:

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;

Пов'язані:

Конструктор ARRAY у корельованому підзапиті:

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

Пов'язані:

Тонка різниця : nullелементи зберігаються у фактичних масивах . Це неможливо для вищезазначених запитів, що створюють textрядок, який не може містити nullзначення. Точне уявлення є масивом.

Функціональна обгортка

Для багаторазового використання, щоб зробити це ще простіше, інкапсулюйте логіку у функції:

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

Зробіть це функцією SQL , щоб вона могла бути вписана в більші запити.
Зробіть це IMMUTABLE(оскільки воно є), щоб уникнути повторного оцінювання у великих запитах і дозволити його в виразах індексу.

Виклик:

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

db <> скрипка тут


Постгреси 9.3 або новіші

Використовуйте функцію json_array_elements(). Але ми отримуємо з нього подвійні цитовані рядки .

Альтернативний запит з агрегуванням у зовнішньому запиті. CROSS JOINвидаляє рядки з відсутніми або порожніми масивами. Може бути корисним і для обробки елементів. Нам потрібен унікальний ключ для агрегації:

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 конструктор, як і раніше з цитованими рядками:

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

Зауважте, що nullперетворюється у текстове значення "null", на відміну від вище. Неправильно, строго кажучи, і потенційно неоднозначне.

Бідні чоловіки розмовляють з 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;

Отримайте один рядок із 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;

Рядки утворюють корельований підзапит:

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

Конструктор ARRAY:

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

Оригінальний (застарілий) скрипт SQL .
db <> скрипка тут.

Пов'язані:

Примітки (застарілі з р. 9.4)

Нам знадобиться a json_array_elements_text(json), близнюк, json_array_elements(json)щоб повернути належні textзначення з масиву JSON. Але цього, здається, не вистачає з наданого арсеналу функцій JSON . Або якась інша функція для отримання textзначення зі скалярного JSONзначення. Мені, здається, і цього не вистачає.
Тож я імпровізував trim(), але це не вдасться до нетривіальних випадків ...


Хороший пост, як завжди, але з вашим знанням внутрішніх справ, чому це не акторський склад з масиву-> jsonb. Я можу зрозуміти, що не реалізувати інший склад, оскільки sql-масив сильніше набраний. Це лише тому, що PostgreSQL не проти автоматичного створення коду для передачі (int [], bigint [], text []) тощо
Еван Керролл

3
@Evan: Ви б використовували to_jsonb()для перетворення масиву-> jsonb.
Erwin Brandstetter

Чи SELECT ARRAY(SELECT json_array_elements_text(_js))справді гарантує збереження впорядкованості масиву? Чи не дозволяється оптимізатору теоретично змінити порядок рядків, що виходять з json_array_elements_text?
Фелікс Гейзендорфер

@Felix: у стандарті SQL немає офіційної гарантії. (знову ж таки, встановити функції повернення навіть у списку SELECT у стандартному SQL для початку не дозволяється.), але в посібнику Postgres є неофіційне твердження. див.: dba.stackexchange.com/a/185862/3684 Ясно - ціною мінорного покарання за дію - див .: dba.stackexchange.com/a/27287/3684 . Особисто я впевнений, що цей конкретний вираз працює так, як очікувалося в кожній теперішній та майбутній версії Postgres з 9.4.
Ервін Брандстеттер

@ErwinBrandstetter дякую тобі за підтвердження цього! В даний час я провожу кілька досліджень для статті, в якій підсумовуються формальні та неофіційні гарантії, впорядковуючи гарантії, надані PostgreSQL, і ваші відповіді були надзвичайно корисними! Якщо вам буде цікаво переглянути статтю, дайте мені знати, але хвилюйтесь, якщо ні. Я неймовірно вдячний за ваші вклади StackOverflow і багато років дізнався від вас!
Фелікс Гейзендорфер

16

PG 9.4+

Прийнята відповідь, безумовно, те, що вам потрібно, але для простоти тут є помічник, який я використовую для цього:

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;

Тоді просто зробіть:

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

Я додав кілька швидших виразів у свою відповідь і більш просту функцію. Це може бути значно дешевше.
Ервін Брандстеттер

4
Ця функція повинна бути чистою SQL, щоб оптимізатор міг зазирнути в неї. Тут не потрібно використовувати pgplsql.
Розділіться

8

Це запитання було задано у списках розсилки PostgreSQL, і я придумав цей шалений спосіб перетворення тексту JSON у тип тексту PostgreSQL через оператора вилучення поля 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
 

В основному він перетворює значення в одноелементний масив, а потім запитує перший елемент.

Іншим підходом було б використання цього оператора для вилучення всіх полів по одному. Але для великих масивів це, мабуть, повільніше, оскільки для цього потрібно проаналізувати весь рядок JSON для кожного елемента масиву, що призводить до складності 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

Я перевірив кілька варіантів. Ось мій улюблений запит. Припустимо, у нас є таблиця, що містить поле id та json. Поле json містить масив, який ми хочемо перетворити на масив pg.

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

Він працює в будь-якому місці і швидше, ніж у інших, але виглядає крихким)

Спочатку масив json передається у вигляді тексту, а потім просто змінюємо квадратні дужки на круглі дужки. Нарешті, текст передається як масив потрібного типу.

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

і якщо ви віддаєте перевагу текстові [] масиви

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"}}"Я думаю, ви повинні додати пояснення щодо того, як це має працювати.
dezso

Питання в тому, як перетворити масив JSON (!) В масив pg. Припустимо, у мене є таблиця, що містить стовпці id та jsonb. Стовпець JSONb містить масив json. Потім
FiscalCliff

TRANSLATE (jsonb :: jsonb :: text, '[]', '{}') :: INT [] перетворює масив json в масив pg.
FiscalCliff

SELECT translate('["foo", "bar"]'::jsonb::text, '[]','{}')::INT[]; ERROR: invalid input syntax for integer: "foo"Це не так
захищено від

Подумайте про використання тексту [] для цих масивів
FiscalCliff

0

Ці кілька функцій, взяті з відповідей на це питання , - це те, що я використовую, і вони чудово працюють

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;

У кожному з них, об'єднуючись із порожнім масивом, вони розглядають випадок, який змусив мене трохи переламати мозок, якщо ви спробуєте передати порожній масив з json/ jsonbбез нього, ви не отримаєте нічого, а не порожній масив ( {}), як ви очікували. Я впевнений, що для них є певна оптимізація, але вони залишаються як для простоти в поясненні концепції.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.