INSERT ON CONFLICT DO UPDATE在与RETURNING语句一起使用时返回意外值。

huangapple go评论59阅读模式
英文:

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_atupdated_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.

huangapple
  • 本文由 发表于 2023年5月30日 04:02:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/76360052.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定