Как превратить массив JSON в массив Postgres?

69

У меня есть столбец, dataкоторый содержит jsonдокумент примерно так:

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

Я хотел бы превратить вложенный tagsмассив в объединенную строку ( foo, bar). Это было бы легко возможно с array_to_string()функцией в теории. Однако эта функция не работает с jsonмассивами. Поэтому мне интересно, как превратить этот jsonмассив в Postgres array?

Christoph
источник
Это json_extract_path_text(your_column, 'tags') то, что вы ищете?
a_horse_with_no_name
1
@a_horse_with_no_name: остающаяся проблема: элементы массива все еще заключены в кавычки для формата JSON. Текст извлечен неправильно ...
Эрвин Брандштеттер

Ответы:

94

Postgres 9.4 или новее

Postgres 9.4, очевидно вдохновленный этим постом , добавил недостающие функции:
спасибо Laurence Rowe за патч и Andrew Dunstan за коммит!

Развернуть массив 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;

дБ <> скрипка здесь


Postgres 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преобразуется в текстовое значение «ноль», в отличие от выше. Неправильно, строго говоря, и потенциально неоднозначно.

Бедный мужчина не цитирует 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;

Получить одну строку из таблицы:

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 Fiddle .
дБ <> скрипка здесь.

Связанный:

Примечания (устарели с 9.4)

Нам понадобится json_array_elements_text(json)двойник json_array_elements(json)для возврата правильных textзначений из массива JSON. Но этого, похоже, не хватает в предоставленном арсенале функций JSON . Или какая-то другая функция для извлечения textзначения из скалярного JSONзначения. Кажется, мне тоже не хватает этого.
Так что я импровизировал trim(), но это не удастся для нетривиальных случаев ...

Эрвин Брандштеттер
источник
Хороший пост, как всегда, но с вашими знаниями о внутренностях, почему там нет приведения из array-> jsonb. Я могу понять, не реализуя другой приведение, потому что sql-массив более строго типизирован. Это просто потому, что PostgreSQL не хочет автоматически генерировать код для приведения (int [], bigint [], text []) и т. Д.
Эван Кэрролл
3
@Evan: вы бы использовали to_jsonb()для преобразования массива-> JSONB.
Эрвин Брандштеттер
SELECT ARRAY(SELECT json_array_elements_text(_js))Действительно ли гарантируется, что порядок массива сохранен? Разве оптимизатору не разрешено теоретически изменять порядок строк, выходящих из json_array_elements_text?
Феликс Гейзендёрфер
@ Феликс: в стандарте SQL нет формальной гарантии. (опять же, функции возврата набора даже не допускаются в списке SELECT в стандартном SQL для начала.) Но в руководстве Postgres есть неформальное утверждение. см .: dba.stackexchange.com/a/185862/3684 Чтобы быть в явном виде - за счет незначительного штрафа за производительность - см: dba.stackexchange.com/a/27287/3684 . Лично я на 100% уверен, что это конкретное выражение работает, как и ожидалось, в каждой настоящей и будущей версии 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);
andrew.carpenter
источник
Я добавил несколько быстрых выражений в свой ответ и более простую функцию. Это может быть существенно дешевле.
Эрвин Брандштеттер
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[];
FiscalCliff
источник
2
SELECT TRANSLATE('{"name": "foo", "tags": ["foo", "bar"]}'::jsonb::text, '[]','{}')::INT[]; ERROR: malformed array literal: "{"name": "foo", "tags": {"foo", "bar"}}"Я думаю, что вы должны добавить некоторые объяснения о том, как это должно работать.
Дезсо
Вопрос в том, как превратить массив 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без него, вы ничего не получите, вместо пустой массив ( {}), как и следовало ожидать. Я уверен, что есть некоторая оптимизация для них, но они оставлены для простоты объяснения концепции.

Джоэл Б
источник