Это решение о реализации. Это описано в документации Postgres, WITH
Запросы (общие выражения таблиц) . Есть два параграфа, связанные с проблемой.
Во-первых, причина наблюдаемого поведения:
Подвыражения в WITH
выполняются одновременно друг с другом и с основным запросом . Следовательно, при использовании операторов, изменяющих данные WITH
, порядок, в котором на самом деле происходят указанные обновления, непредсказуем. Все операторы выполняются с одним и тем же снимком (см. Главу 13), поэтому они не могут «видеть» влияние друг друга на целевые таблицы. Это смягчает последствия непредсказуемости фактического порядка обновлений строк и означает, что RETURNING
данные - это единственный способ сообщать об изменениях между различными WITH
под-утверждениями и основным запросом. Примером этого является то, что в ...
После того, как я опубликовал предложение в pgsql-docs , Марко Тииккая объяснил (что соответствует ответу Эрвина):
Случаи вставки-обновления и вставки-удаления не работают, поскольку UPDATE и DELETE не имеют возможности видеть строки INSERTed, поскольку их моментальный снимок был сделан до того, как произошла INSERT. В этих двух случаях нет ничего непредсказуемого.
Таким образом, причина, по которой ваше утверждение не обновляется, может быть объяснена в первом абзаце выше (о «снимках»). Когда вы изменяете 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 оператора. Один, который вставляет и второй, чтобы обновить.
Существуют и другие, похожие ситуации, например, если в операторе есть a INSERT
и затем a DELETE
в тех же строках. Удаление не удастся по тем же причинам.
Некоторые другие случаи с update-update и update-delete и их поведением объясняются в следующем абзаце на той же странице документов.
Попытка обновить одну и ту же строку дважды в одном операторе не поддерживается. Происходит только одна из модификаций, но не легко (а иногда и невозможно) надежно предсказать, какая именно. Это также относится к удалению строки, которая уже была обновлена в том же операторе: выполняется только обновление. Поэтому, как правило, не следует пытаться модифицировать одну строку дважды в одном выражении. В частности, избегайте написания вложенных операторов WITH, которые могут повлиять на те же строки, измененные основным оператором или дочерним вложенным оператором. Последствия такого заявления не будут предсказуемыми.
И в ответе Марко Тииккая:
Случаи update-update и update-delete явно не вызваны одной и той же базовой деталью реализации (как случаи insert-update и insert-delete).
Случай обновления-обновления не работает, потому что он внутренне выглядит как проблема Хэллоуина, и Postgres не может знать, какие кортежи можно было бы обновить дважды, а какие могли бы снова вызвать проблему Хэллоуина.
Таким образом, причина одна и та же (как реализованы модифицирующие CTE и как каждый CTE видит один и тот же снимок), но детали отличаются в этих двух случаях, поскольку они более сложны, и результаты могут быть непредсказуемыми в случае обновления-обновления.
В случае вставки-обновления (как в вашем случае) и аналогичной вставки-удаления результаты предсказуемы. Происходит только вставка, так как вторая операция (обновление или удаление) не может видеть и влиять на вновь вставленные строки.
Тем не менее, предлагаемое решение одинаково для всех случаев, которые пытаются изменить одни и те же строки более одного раза: не делайте этого. Либо напишите операторы, которые изменяют каждую строку один раз, либо используйте отдельные (2 или более) операторов.