Многие знают, что в PostgreSQL есть тип данных JSONB. Но не все понимают, зачем он нужен в реляционной базе и какие задачи помогает решать.

Сегодня на примере тестовых таблиц интернет-магазина мы разберём:

  • где JSONB выигрывает у обычных колонок и массивов;

  • как с ним работать эффективно;

  • основные операции: извлечение, фильтрация, обновление.


Исходная структура: категории и товары

У нас есть две таблицы.

Категории — с иерархической структурой (например, «Электроника» → «Смартфоны» → «Apple»).

Товары — с базовыми полями и массивом tags для хранения простых меток:

sql
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name TEXT,
    category_id INT,
    tags TEXT[]
);

Массив tags удобен для хранения простых меток: {'смартфон', 'apple', 'флагман'}.


Когда массивов достаточно (и даже хорошо)

Запрос 1: найти все товары с тегом «apple»

sql
SELECT * FROM products WHERE 'apple' = ANY(tags);

Запрос 2: найти товары с тегами «смартфон» и «apple» одновременно

sql
SELECT * FROM products WHERE tags @> ARRAY['смартфон', 'apple'];

Благодаря GIN-индексу такие запросы выполняются быстро даже на больших объёмах:

sql
CREATE INDEX idx_products_tags ON products USING GIN(tags);

📌 Массивы — отличный инструмент для простых списков строк.


Когда массивов становится недостаточно

Представьте, что для тега «популярный» мы хотим хранить:

  • дату, когда товар стал популярным;

  • вес тега для поисковой выдачи.

Массив строк этого не умеет. Пришлось бы создавать отдельную таблицу product_tags с колонками tag, date, weight. Это усложняет схему и запросы.

Другой пример — характеристики товаров:

  • у смартфонов: процессор, память, объём батареи;

  • у наушников: время работы, тип подключения;

  • у одежды: размер, материал.

В классической реляционной модели это решается через:

  • таблицу атрибутов (EAV);

  • или множество колонок, большинство из которых будут NULL.

Оба подхода — громоздкие и медленные.


Решение: JSONB

Добавим в таблицу товаров колонку properties типа JSONB:

sql
ALTER TABLE products ADD COLUMN properties JSONB;

Теперь заполним её характеристиками в зависимости от категории.

Для смартфонов

sql
UPDATE products SET properties = '{
    "processor": "A17 Bionic",
    "ram": 8,
    "storage": 256,
    "battery": 4000,
    "water_resistance": "IP68"
}' WHERE category_id = 1;

Для наушников

sql
UPDATE products SET properties = '{
    "battery_life": 30,
    "connection_type": "Bluetooth 5.3",
    "noise_cancelling": true
}' WHERE category_id = 2;

Для книг

sql
UPDATE products SET properties = '{
    "author": "Лев Толстой",
    "pages": 1225,
    "genres": ["роман", "классика"]
}' WHERE category_id = 3;

Основные операции с JSONB

1. Извлечение значения (оператор ->>)

sql
SELECT
    name,
    properties ->> 'processor' AS cpu,
    properties ->> 'ram' AS memory
FROM products
WHERE properties ? 'processor';

2. Фильтрация по вложенному значению

Найти все товары с влагозащитой IP68:

sql
SELECT * FROM products
WHERE properties ->> 'water_resistance' = 'IP68';

3. Фильтрация по диапазону чисел

Найти смартфоны с батареей выше 4500 мАч:

sql
SELECT * FROM products
WHERE (properties ->> 'battery')::int > 4500;

📌 Обратите внимание на приведение типов: ->> возвращает текст.

Более эффективный вариант для фильтрации — использовать @> или создать индекс по выражению.

4. Поиск внутри массива, хранящегося в JSONB

Найти товары, у которых в жанрах указано «роман»:

sql
SELECT * FROM products
WHERE properties -> 'genres' ? 'роман';

Или любой из жанров:

sql
SELECT * FROM products
WHERE properties -> 'genres' ?| ARRAY['роман', 'основы'];

Изменение JSONB-данных без перезаписи всего объекта

PostgreSQL позволяет точечно обновлять JSONB.

Добавить новый цвет в массив colors

sql
UPDATE products
SET properties = jsonb_set(
    properties,
    '{colors}',
    COALESCE(properties -> 'colors', '[]'::jsonb) || '["черный"]'::jsonb
)
WHERE id = 1;

Добавить новое поле

sql
UPDATE products
SET properties = properties || '{"warranty_months": 12}'::jsonb
WHERE id = 1;

Удалить поле

sql
UPDATE products
SET properties = properties - 'warranty_months'
WHERE id = 1;

Индексы для JSONB

Для ускорения запросов используйте GIN-индексы:

sql
-- индекс для операторов ?, ?|, ?&, @>
CREATE INDEX idx_products_properties ON products USING GIN(properties);

-- индекс для конкретного пути (например, battery)
CREATE INDEX idx_products_battery ON products ((properties ->> 'battery'));

Итог: когда использовать JSONB, а когда — нет

Задача Решение
Простой список строк (теги, метки) TEXT[] + GIN
Ключ-значение с разными типами значений JSONB
Динамические атрибуты (характеристики товаров) JSONB
Вложенные структуры (объекты в объектах) JSONB
Данные, которые нужно часто фильтровать и индексировать JSONB + GIN
Строгая схема с фиксированными колонками Обычные колонки INT, TEXT и т.д.

Главное правило: JSONB не заменяет реляционную модель, а дополняет её там, где гибкость важнее строгости схемы.


Что мы сегодня разобрали

  • ✅ Чем JSONB отличается от массивов и когда его применять.

  • ✅ Как добавить JSONB в существующую таблицу и заполнить данными.

  • ✅ Основные операции: извлечение, фильтрация, обновление.

  • ✅ Индексация для быстрых запросов.


 

Статья подготовлена по материалам обучающего видео для вашего удобства.