Работа с JSONB в PostgreSQL открывает новые возможности для хранения и обработки сложных структур данных прямо в базе. В этой статье разберём ключевые операторы JSONB, особенности индексации и практические примеры использования в Rails-приложениях — от простых фильтраций до работы с вложенными объектами.
🔞 Псс! Хочешь про JSONB без Rails и магии?
- Готовый docker-compose.yml 🐋
- встроенная генерация на 10К строк 🐘
- все по-взрослому 😏
👉 Как использовать WHERE для JSONB в PostgreSQL (Docker стенд + примеры)
Ты добавил колонку metadata с типом jsonb, сложил туда всё подряд, а потом…
Начал писать вот так:
where("metadata @> ?", { status: "error" }.to_json)
И это работает. Причём быстро — если настроен GIN-индекс.
🧠 Что такое @>?
Это оператор “содержит”: слева JSONB-объект, справа — частичка, которую мы ищем.
-- Найдёт все записи, у которых metadata содержит поле status со значением "error"
SELECT * FROM logs WHERE metadata @> '{"status": "error"}';
Он умеет работать и с вложенными структурами:
metadata @> '{"meta": {"country": "RU"}}'
🚀 Почему это круто?
- Гибкость — не нужно создавать отдельные колонки
- Производительность — с GIN-индексом всё работает шустро
- Поддержка в ActiveRecord — можно передавать в
where(...)
🧱 Но нужен индекс!
Без GIN индекса PostgreSQL будет делать Seq Scan, и это боль:
add_index :logs, :metadata, using: :gin
Для вложенных ключей — лучше ещё и jsonb_path_ops:
CREATE INDEX idx_logs_metadata_gin ON logs USING GIN (metadata jsonb_path_ops);
📦 Типовые примеры
# Лог с определённым статусом
Log.where("metadata @> ?", { status: "ok" }.to_json)
# Вложенная структура
Log.where("metadata @> ?", { meta: { os: "linux" } }.to_json)
😱 Когда это ломается
- Запросы без индекса — боль
- Rails
.where(metadata: { ... })не всегда работает корректно (лучше писать SQL руками) - Вложенные ключи → сложнее дебажить
💡 Лайфхаки
- Избегай
LIKEиILIKEвнутриjsonb - Всегда проверяй
EXPLAIN— там сразу видно, используется ли индекс - Логируй
durationзапросов — JSONB может внезапно просесть на большом объёме
📘 Справочник операторов JSONB
| Оператор | Назначение | Пример | ||
|---|---|---|---|---|
@> |
Содержит (superset) | '{"a":1, "b":2}' @> '{"a":1}' → true |
||
<@ |
Содержится в (subset) | '{"a":1}' <@ '{"a":1, "b":2}' → true |
||
? |
Есть ли ключ | '{"a":1}' ? 'a' → true |
||
? |
Есть ли хотя бы один из ключей | '{"a":1, "b":2}' ? | array['x','a']→true |
||
?& |
Есть ли все указанные ключи | '{"a":1, "b":2}' ?& array['a','b'] → true |
||
#> |
Переход по пути (возврат значения) | '{"a": {"b": "c"}}' #> '{a,b}' → "c" |
||
#>> |
Путь, но вернёт строку, не JSONB | '{"a": {"b": "c"}}' #>> '{a,b}' → 'c' |
||
-> |
Получение по ключу (JSONB) | '{"a":1}' -> 'a' → 1 (тип JSONB) |
||
->> |
Получение по ключу (строка) | '{"a":1}' ->> 'a' → '1' |
||
jsonb_array_elements() |
Разворачивает массив JSONB в строки | SELECT jsonb_array_elements('[1,2,3]') |
📌 Пояснение:
@>и?— идеальны для фильтрации.#>и->— полезны для выборки отдельных вложенных значений.->>возвращает сразу строку, удобно для WHERE + LIKE.
💎 Использование JSONB в Rails
Rails не предоставляет обёртку над всеми JSONB-операторами, но через where(...) и Arel.sql можно многое:
🔍 Примеры с @>
# Проверка, что metadata содержит поле status = "ok"
Log.where("metadata @> ?", { status: "ok" }.to_json)
# Проверка вложенного ключа
Log.where("metadata @> ?", { meta: { browser: "Safari" } }.to_json)
❓ Проверка ключа с ?
# Проверка, что ключ "error" существует
Log.where("metadata ? 'error'")
🔗 Получение вложенных значений
# Выбрать записи, у которых metadata->>'status' = 'ok'
Log.where("metadata ->> 'status' = ?", 'ok')
# Глубокое сравнение: metadata#>>'{meta,browser}' = 'Firefox'
Log.where("metadata #>> '{meta,browser}' = ?", 'Firefox')
💡 Советы
- Используй
Arel.sql(...)для сложных выражений, если ActiveRecord начинает ругаться. - Никогда не передавай сырые строки! — всегда используй плейсхолдеры
?и.to_json. - Проверь, чтобы в
schema.rbmetadataбыла явно указана как:jsonb.
t.jsonb :metadata, default: {}, null: false
📢 Важно: ActiveRecord не знает структуру jsonb, так что:
jsonb + Rails — это как общение через SQL-черепашку. Всё возможно, но требует аккуратности, внимания к индексации и немного SQL-мастерства.
.where(metadata: { ... })может не работать так, как ожидаешь.pluck(:metadata)вернёт весь объект, а не конкретное значение внутри.order("metadata ->> 'score' DESC")работает, но только через строку
📌 Вывод: JSONB — это как магическая сумка: можно сложить всё, но если не разобрался с индексами — тяжело найти что-то внутри.