upsert和sub select

时间:2014-02-10 17:20:47

标签: postgresql postgresql-9.1 upsert

我有一个upsert语句(http://www.the-art-of-web.com/sql/upsert/)只要一个id不存在的行进行插入,并在该行存在时更新列:

WITH upsert AS 
  (UPDATE foo SET counter=counter+1 WHERE id='bar' RETURNING *) 
  INSERT INTO foo(id, counter) SELECT 'bar', 0
WHERE NOT EXISTS (SELECT * FROM upsert) RETURNING counter;

id是主键列(如预期的那样)。直到这里一切正常。

但是有第3列“位置”可用于自定义排序。 如果有更新,我想保留当前值。

但是insert语句需要一个额外的子查询返回未使用的最低位置:

WITH upsert AS 
  (UPDATE foo SET counter=counter+1 WHERE id='bar' RETURNING *) 
  INSERT INTO foo(id, counter, position) SELECT 'bar', 0, MIN( position)-1 from foo
WHERE NOT EXISTS (SELECT * FROM upsert) RETURNING counter;

使用此语句我收到错误

ERROR:  duplicate key value violates unique constraint "id"

这里有什么不对吗?

1 个答案:

答案 0 :(得分:2)

问题是应用于0行的MIN()返回一行(具有NULL值)

示例:

test=> select min(1) where false;
 min 
-----

(1 row)

这与没有WHERE

的相同min()子句不同
test=> select 1 where false;
 ?column? 
----------
(0 rows)

因此,当在提供MIN()的子查询中使用INSERT时,即使WHERE子句的计算结果为false,它也会插入一个新行,这会破坏此UPSERT的逻辑。 / p>

我认为这可以通过引入另一个子查询来解决:

WITH upsert AS 
  (UPDATE foo SET counter=counter+1 WHERE id='bar' RETURNING *) 
  INSERT INTO foo(id, counter, position) 
   SELECT * FROM (SELECT 'bar', 0, MIN( position)-1 from foo) s
            WHERE NOT EXISTS (SELECT * FROM upsert)
   RETURNING counter;

但请注意,将其塞入单个SQL语句并不能保证在并发运行时系统成功。

了解更多:
How do I do an UPSERT (MERGE, INSERT … ON DUPLICATE UPDATE) in PostgreSQL?