PostgreSQL + JSONB: магия `@>` и запросы к вложенным данным

Работа с 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"}}'

🚀 Почему это круто?

  1. Гибкость — не нужно создавать отдельные колонки
  2. Производительность — с GIN-индексом всё работает шустро
  3. Поддержка в 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.rb metadata была явно указана как :jsonb.
t.jsonb :metadata, default: {}, null: false

📢 Важно: ActiveRecord не знает структуру jsonb, так что:

jsonb + Rails — это как общение через SQL-черепашку. Всё возможно, но требует аккуратности, внимания к индексации и немного SQL-мастерства.

  • .where(metadata: { ... }) может не работать так, как ожидаешь
  • .pluck(:metadata) вернёт весь объект, а не конкретное значение внутри
  • .order("metadata ->> 'score' DESC") работает, но только через строку

📌 Вывод: JSONB — это как магическая сумка: можно сложить всё, но если не разобрался с индексами — тяжело найти что-то внутри.

🗓 Дата публикации: 04.02.2025, но это не точно...

JSONB PostgreSQL Rails индексация фильтрация вложенные объекты