Це рішення про реалізацію. Це описано в документації Postgres, WITH
Запити (Загальні вирази таблиць) . Є два абзаци, пов'язані з проблемою.
По-перше, причина спостережуваної поведінки:
Суб-виписка в WITH
виконуються паралельно один з одним і з основним запитом . Тому при використанні операторів, що змінюють дані WITH
, порядок, в якому фактично відбуваються вказані оновлення, є непередбачуваним. Усі висловлювання виконуються з одним і тим же знімком (див. Главу 13), тому вони не можуть "побачити" ефекти один одного на цільових таблицях. Це зменшує наслідки непередбачуваності фактичного порядку оновлень рядків і означає, що RETURNING
дані - це єдиний спосіб повідомляти про зміни між різними WITH
підрекламами та основним запитом. Прикладом цього є те, що в ...
Після того як я опублікував пропозицію разом із pgsql-docs , Марко Тііккая пояснив (що згоден з відповіддю Ервіна):
Випадки вставки-оновлення та вставлення-видалення не працюють, тому що ОНОВЛЕННЯ та ВИДАЛЕННЯ не мають змоги побачити ВСТАНОВЛЕНІ рядки через їх знімок, зроблений до того, як сталося ВСТАВЛЕННЯ. У цих двох випадках немає нічого непередбачуваного.
Тож причину, через яку ваше твердження не оновлюється, можна пояснити першим пунктом вище (про "знімки"). Що відбувається, коли ви змінюєте CTE, це те, що всі вони та основний запит виконуються та "бачать" той самий знімок даних (таблиць), як вони були безпосередньо перед виконанням оператора. CTE можуть передавати інформацію про те, що вони вставили / оновили / видалили один до одного та до основного запиту, використовуючи RETURNING
пункт, але вони не бачать зміни в таблицях безпосередньо. Тож давайте подивимось, що відбувається у вашій заяві:
WITH newval AS (
INSERT INTO tbl(val) VALUES (1) RETURNING id
) UPDATE tbl SET val=2 FROM newval WHERE tbl.id=newval.id;
У нас є 2 частини, CTE ( newval
):
-- newval
INSERT INTO tbl(val) VALUES (1) RETURNING id
та основний запит:
-- main
UPDATE tbl SET val=2 FROM newval WHERE tbl.id=newval.id
Потік виконання виглядає приблизно так:
initial data: tbl
id │ val
(empty)
/ \
/ \
/ \
newval: \
tbl (after newval) \
id │ val \
1 │ 1 |
|
newval: returns |
id |
1 |
\ |
\ |
\ |
main query
Як результат, коли основний запит приєднується до tbl
(як видно на знімку) із newval
таблицею, він приєднується до порожньої таблиці з таблицею в 1 рядок. Очевидно, він оновлює 0 рядків. Тож заява насправді ніколи не надходила для зміни щойно вставленого рядка, і це те, що ви бачите.
Рішення у вашому випадку полягає в тому, щоб або переписати оператор, щоб ввести правильні значення в першу чергу, або використовувати 2 твердження. Один, який вставляє, а другий для оновлення.
Існують і інші подібні ситуації, наприклад, якщо у висловлюванні було вказано а, INSERT
а потім а DELETE
на одних і тих же рядках. Видалення не вдалося б із абсолютно тих самих причин.
Деякі інші випадки, пов’язані з оновленням-оновленням та оновленням-видаленням, та їх поведінка пояснюються у наступному параграфі на тій же сторінці документів.
Спроба оновити один і той же рядок двічі в одному операторі не підтримується. Відбувається лише одна з модифікацій, але надійно передбачити, яку з них неможливо (а іноді й неможливо). Це стосується також видалення рядка, який уже був оновлений у тому самому операторі: виконується лише оновлення. Тому слід уникати спроб змінити один рядок двічі в одному операторі. Зокрема, уникайте написання підрепортажів СИ, які можуть впливати на ті самі рядки, змінені в основному операторі або підзаговорниках побратимів. Наслідки такого твердження не будуть передбачуваними.
А у відповідь Марко Тііккая:
Випадки оновлення-оновлення та оновлення-видалення явно не викликаються тими ж основними деталями реалізації (як випадки вставлення-оновлення та вставки-видалення).
Випадок оновлення-оновлення не працює, оскільки він всередині виглядає як проблема Хеллоуїна, і Postgres не може знати, які кортежі було б добре оновлювати двічі, а які могли б знову ввести проблему Хеллоуїна.
Отже, причина однакова (як реалізуються модифікації CTE, і як кожен CTE бачить однаковий знімок), але деталі відрізняються в цих двох випадках, оскільки вони складніші, і результати можуть бути непередбачуваними у випадку оновлення-оновлення.
У вставці-оновленні (як у вашому випадку) та подібному вставці-видаленні результати передбачувані. Лише вставка відбувається, оскільки друга операція (оновлення або видалення) не має можливості бачити та впливати на щойно вставлені рядки.
Пропоноване рішення, однак, однакове для всіх випадків, які намагаються змінити одні й ті ж рядки не один раз: Не робіть цього. Або пишіть заяви, які змінюють кожен рядок один раз, або використовуйте окремі (2 або більше) операторів.