RETURNING повертає дані рядків, які щойно вставлено, оновлено чи видалено, - тим самим запитом, без додаткового SELECT.
INSERT INTO orders (user_id, total) VALUES (7, 1999)
RETURNING id, created_at;
Типова потреба: дізнатися згенерований id, значення за замовчуванням (created_at, uuid), результат тригера.
З UPDATE і DELETE:
UPDATE accounts SET balance = balance - 500
WHERE id = 7 AND balance >= 500
RETURNING balance;
-- 0 рядків у відповіді - коштів не вистачило
DELETE FROM sessions WHERE last_activity < now() - interval '30 days'
RETURNING user_id;
PostgreSQL 18: старі й нові значення в одному запиті:
UPDATE products SET price = price * 1.1 WHERE category_id = 3
RETURNING id, old.price AS old_price, new.price AS new_price;
Раніше для «було - стало» потрібен був окремий запит до оновлення чи тригер.
З ON CONFLICT - дізнатися, чи рядок вставлено, чи оновлено:
INSERT INTO prices (sku, amount) VALUES ('A-1', 100)
ON CONFLICT (sku) DO UPDATE SET amount = EXCLUDED.amount
RETURNING id, (xmax = 0) AS inserted;
(xmax = 0 - поширений прийом, що спирається на внутрішню деталь MVCC, а не на гарантований інтерфейс.)
Чому це важливо:
- на один запит менше - менше затримки, особливо коли база на іншому сервері;
- атомарність: окремий
SELECTпісляUPDATEможе побачити вже змінений іншим запитом рядок, аRETURNINGповертає саме те, що записав ваш запит; - черга задач:
UPDATE ... WHERE id = (SELECT ... FOR UPDATE SKIP LOCKED) RETURNING *- взяти задачу й позначити її зайнятою одним запитом.
У Laravel: при Model::create() на PostgreSQL Eloquent отримує id саме через INSERT ... RETURNING "id". Для решти сценаріїв - сирий запит:
$rows = DB::select('UPDATE accounts SET balance = balance - ? WHERE id = ? AND balance >= ? RETURNING balance', [500, 7, 500]);
Докладніше в документації: Повернення даних зі змінених рядків