Значення jsonb - одне ціле: «змінити поле на місці» неможливо, будь-яка зміна створює новий документ. Але PostgreSQL має функції й оператори, які збирають новий документ за вас:
-- встановити чи замінити значення за шляхом
UPDATE users SET settings = jsonb_set(settings, '{notify,email}', 'false')
WHERE id = 1;
-- jsonb_set створює лише останній ключ шляху: якщо об'єкта ui ще немає,
-- документ повернеться без змін. Проміжний об'єкт збирають явно:
UPDATE users SET settings = jsonb_set(
settings, '{ui}', coalesce(settings -> 'ui', '{}') || '{"sidebar": "collapsed"}'
);
-- злити об'єкти: ключі правого перезапишуть лівий
UPDATE users SET settings = settings || '{"theme": "light", "lang": "uk"}';
-- видалити ключ / шлях / елемент масиву
UPDATE users SET settings = settings - 'beta_features';
UPDATE users SET settings = settings #- '{notify,sms}';
-- вставити в масив
UPDATE users SET settings = jsonb_insert(settings, '{tags,0}', '"laravel"');
Нюанси:
- Значення в
jsonb_set- теж JSON: рядок пишуть з подвійними лапками всередині ('"collapsed"'), число йtrue/false- без. jsonb_setзNULLяк новим значенням (не JSON-null, а SQLNULL) повернеNULLдля всього документа - і стерте налаштування. Для безпечної роботи зNULLєjsonb_set_lax()(PostgreSQL 13+): за замовчуванням він запише JSON-null.||зливає лише верхній рівень: вкладений об'єкт праворуч повністю замінить вкладений об'єкт ліворуч, а не зіллється з ним.- Конкурентні оновлення: два запити, що одночасно змінюють різні ключі одного документа через
jsonb_set, не конфліктують - кожен перечитує актуальне значення вUPDATE. А от «прочитати весь JSON у застосунок → змінити → записати назад» дає загублене оновлення.
У Laravel:
$user->update(['settings->notify->email' => false]); // перетвориться на jsonb_set
Сигнал до зміни схеми: якщо якесь поле JSON постійно оновлюється й фільтрується окремо, - йому, ймовірно, місце в окремій колонці.