Если JSONB — это волшебный сундук, куда мы кладём данные, то || и jsonb_concat() — инструменты, позволяющие объединять такие сундуки или изменять их содержимое. В реальной жизни вам может потребоваться объединить несколько JSON-объектов, добавить данные из одного в другой или объединить массивы в единый список.
Например, представьте, что у вас есть два JSONB-объекта:
{"name": "Alice", "age": 25}
и
{"city": "Wonderland", "hobbies": ["reading", "chess"]}
Вы хотите получить:
{"name": "Alice", "age": 25, "city": "Wonderland", "hobbies": ["reading", "chess"]}
Или объединить два массива JSONB:
[1, 2, 3]
и
[4, 5, 6]
чтобы результат выглядел как:
[1, 2, 3, 4, 5, 6]
Всё это делается с помощью || или jsonb_concat(). Давайте разбираться как.
Оператор || для объединения JSONB
Оператор || позволяет объединять два JSONB-объекта или массива в PostgreSQL. Он простой, быстрый и лёгкий в использовании. Вот основные правила его работы:
- Если объединяются два JSONB-объекта, то результирующий объект будет содержать ключи и значения обоих объектов.
- Если ключи пересекаются, значение из правого операнда заменяет значение из левого.
- Если объединяются JSONB-массивы, то элементы левого и правого массива объединяются в один массив.
Пример 1: Объединение двух JSONB-объектов
SELECT '{"name": "Alice", "age": 25}'::jsonb || '{"city": "Wonderland", "hobbies": ["reading", "chess"]}'::jsonb AS merged_object;
Результат:
{"name": "Alice", "age": 25, "city": "Wonderland", "hobbies": ["reading", "chess"]}
Пример 2: Обновление значений при совпадении ключей
SELECT '{"name": "Alice", "age": 25}'::jsonb || '{"age": 30, "city": "Wonderland"}'::jsonb AS updated_object;
Результат:
{"name": "Alice", "age": 30, "city": "Wonderland"}
Обратите внимание, что значение ключа "age" из правого объекта заменило значение из левого.
Пример 3: Объединение массивов
SELECT '[1, 2, 3]'::jsonb || '[4, 5, 6]'::jsonb AS merged_array;
Результат:
[1, 2, 3, 4, 5, 6]
Функция jsonb_concat() для объединения JSONB
Функция jsonb_concat() работает аналогично оператору ||, но предоставляет гибкость, если вам нужно использовать её в функциях, триггерах или динамических запросах. Она принимает два аргумента типа JSONB и возвращает объединённый результат.
Пример: использование jsonb_concat()
SELECT jsonb_concat('{"a": 1, "b": 2}'::jsonb, '{"b": 3, "c": 4}'::jsonb) AS combined;
Результат:
{"a": 1, "b": 3, "c": 4}
Объединение объектов и массивов: особенности и нюансы
При объединении объектов с совпадающими ключами следует понимать, что значения из правого объекта имеют приоритет.
Например:
SELECT '{"key1": "value1"}'::jsonb || '{"key1": "value2"}'::jsonb AS result;
Результат:
{"key1": "value2"}
Если вы хотите избежать замены значений, следует хранить такие данные в отдельных ключах или использовать другой подход (например, массив).
А вот массивы всегда объединяются путём добавления элементов из правого массива к левому. Например:
SELECT '["a", "b"]'::jsonb || '["c", "d"]'::jsonb AS result;
Результат:
["a", "b", "c", "d"]
Если внутри массива есть объекты, порядок объектов сохраняется:
SELECT '[{"id": 1}, {"id": 2}]'::jsonb || '[{"id": 3}]'::jsonb AS result;
Результат:
[{"id": 1}, {"id": 2}, {"id": 3}]
Практические примеры
Обновление профиля пользователя. Предположим, у нас есть таблица users, где профили хранятся в формате JSONB:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
profile JSONB
);
INSERT INTO users (profile) VALUES ('{"name": "Alice", "age": 25}');
Теперь мы хотим добавить город проживания:
UPDATE users
SET profile = profile || '{"city": "Wonderland"}'
WHERE id = 1;
Результат запроса:
{"name": "Alice", "age": 25, "city": "Wonderland"}
Объединение данных о заказах. Пусть у нас есть таблица orders:
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
details JSONB
);
INSERT INTO orders (details) VALUES ('{"items": [{"product": "laptop", "quantity": 1}]}');
Теперь мы добавляем ещё один продукт в заказ:
UPDATE orders
SET details = jsonb_set(
details,
'{items}',
details->'items' || '[{"product": "mouse", "quantity": 2}]'::jsonb
)
WHERE id = 1;
Результат запроса:
{"items": [{"product": "laptop", "quantity": 1}, {"product": "mouse", "quantity": 2}]}
Различия между || и jsonb_concat()
Функционально оператор || и функция jsonb_concat() идентичны. Используйте ||, если пишете простые запросы, так как запись более лаконична. Функция jsonb_concat() удобна в случаях, когда вам нужно явно вызывать её внутри программы или триггера.
Типичные ошибки и способы их предотвращения
Ошибка: попытка объединить несовместимые типы.
SELECT '{"key": "value"}'::jsonb || '["value"]'::jsonb;
Результат:
ERROR: cannot concatenate jsonb objects and arrays
Здесь слева находится объект, а справа массив — PostgreSQL не может их просто так объединить. Чтобы операция сработала, оба операнда должны быть одного типа: либо два объекта, либо два массива.
Упущение: отсутствие индексов при работе с JSONB
Если вы часто фильтруете данные по значениям внутри JSONB-полей, а индексов нет — запросы могут сильно тормозить. Это не ошибка в классическом смысле, но последствия для производительности — очень ощутимы. Не забудьте использовать GIN-индексы:
CREATE INDEX idx_profile_data ON employees USING gin(profile);
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ