英文:
INSERT ON CONFLICT DO UPDATE returns unexpected values when used with RETURNING statement
问题
这个问题可能与 PostgreSQL 的行为有关。根据你的描述,似乎在执行 UPSERT 查询后,RETURNING
子句返回的值不符合你的预期。这可能是 PostgreSQL 特定行为的结果,可能与如何处理默认值以及如何应用 UPSERT 的逻辑有关。
如果你想了解更多关于这个问题的详细信息,最好咨询 PostgreSQL 社区或查阅最新的 PostgreSQL 文档,以确定这是否是 PostgreSQL 的已知行为或潜在问题。这有助于你更好地理解这个情况并找到可能的解决方案。
不过,你也提到了有其他解决方法可以避免这个问题,所以如果这个问题不容易解决,你可以考虑使用这些替代方法来满足你的需求。
英文:
I have a users
table with id
, name
, created_at
, and updated_at
. Both the created_at
, and updated_at
have default values that default to the current timestamp. I am attempting to upsert into this table as follows.
INSERT INTO users (
id,
name,
created_at,
updated_at
) VALUES (
'0e22e5b9-3826-401c-824b-ba12b1b4eb0a'
'Some User',
NOW(),
NOW()
) ON CONFLICT (
id
) DO UPDATE SET
name = excluded."name",
updated_at = excluded."updated_at"
RETURNING
"id",
"name",
"created_at",
"updated_at";
I want to make sure that when an insert happens, both the "created_at" and "updated_at" are set, but when there's a conflict and only an update happens, then only the "updated_at" is updated, and I want to return the final row. I expect that if I had an existing row like
id | name | created_at | updated_at |
---|---|---|---|
1 | 0e22e5b9-3826-401c-824b-ba12b1b4eb0a | Some name | 03-01-2023 |
and I updated the name two days later, I'd get back something like this from the return statement where only the updated_at
has changed
id | name | created_at | updated_at |
---|---|---|---|
1 | 0e22e5b9-3826-401c-824b-ba12b1b4eb0a | Abcd name | 03-01-2023 |
But instead I get back results where both the created_at
and updated_at
have been changed
id | name | created_at | updated_at |
---|---|---|---|
1 | 0e22e5b9-3826-401c-824b-ba12b1b4eb0a | Abcd name | 03-03-2023 |
This problem is only with the returned value after the query runs, the actual value in the table is still correct. I assume this behavior has something to do with how the RETURNING
command interacts with the upsert query, but I couldn't find documentation on this behavior anywhere. I know there are workarounds to this, but I'm just wondering why this specific method doesn't work. Is this a bug or expected behavior for PostreSQL?
答案1
得分: 1
你正在经历的行为在使用INSERT ... ON CONFLICT ... DO UPDATE
语句时,与RETURNING
子句结合使用时,对于PostgreSQL来说是预期的。这种行为不是特定于RETURNING
子句,而是关于ON CONFLICT ... DO UPDATE
语句的工作方式。
当发生冲突并触发DO UPDATE
子句时,所有在SET
子句中指定的列都将被更新,无论它们在EXCLUDED
行中是否实际更改。这是DO UPDATE
子句的默认行为。
在你的情况下,created_at
和updated_at
都在SET
子句中指定,因此即使created_at
保持不变,它仍将被更新为来自EXCLUDED
的值。在你的情况下,这等同于NOW()
。
为了实现所需的行为,即在更新期间保持created_at
不变,你可以将查询修改如下:
INSERT INTO users (
id,
name,
created_at,
updated_at
) VALUES (
'0e22e5b9-3826-401c-824b-ba12b1b4eb0a',
'Some User',
NOW(),
NOW()
) ON CONFLICT (id) DO UPDATE SET
name = excluded."name",
updated_at = excluded."updated_at",
created_at = users.created_at -- 保持created_at的原始值
RETURNING
"id",
"name",
"created_at",
"updated_at";
通过明确设置created_at = users.created_at
,你确保在更新期间created_at
列保留其原始值,只有updated_at
列会被更新。
这种行为不是PostgreSQL实现ON CONFLICT ... DO UPDATE
语句的错误,而是一个设计选择,旨在提供在冲突期间更新列的灵活性,即使值没有更改也可以进行更新。
英文:
The behavior you're experiencing is expected for PostgreSQL when using the RETURNING
clause in combination with an INSERT ...
ON CONFLICT ...
DO UPDATE
statement. This behavior is not specific to the RETURNING
clause but rather how the ON CONFLICT ...
DO UPDATE
statement works.
When a conflict occurs and the DO UPDATE
clause is triggered, all the columns specified in the SET
clause will be updated, regardless of whether they were actually changed in the EXCLUDED
row. This is the default behavior of the DO UPDATE
clause.
In your case, both created_at and updated_at are specified in the SET
clause, so even if created_at remains the same, it will still be updated with the value from excluded."created_at", which is the same as NOW()
in your case.
To achieve the desired behavior, where created_at
remains unchanged during an update, you can modify your query as follows:
INSERT INTO users (
id,
name,
created_at,
updated_at
) VALUES (
'0e22e5b9-3826-401c-824b-ba12b1b4eb0a',
'Some User',
NOW(),
NOW()
) ON CONFLICT (id) DO UPDATE SET
name = excluded."name",
updated_at = excluded."updated_at",
created_at = users.created_at -- Keep the original value of created_at
RETURNING
"id",
"name",
"created_at",
"updated_at";
By explicitly setting created_at = users.created_at
, you ensure that the created_at
column retains its original value during an update and only the updated_at
column gets updated.
This behavior is not a bug but rather a design choice in PostgreSQL's implementation of the ON CONFLICT ...
DO UPDATE
statement. It's designed to provide flexibility in updating columns during conflicts, even if the values are not changing.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论