如果您整合目前的答案并进行清理和改进,您将得到以下更好的查询:
UPDATE sales
SET status = 'ACTIVE'
WHERE (saleprice, saledate) IN (
SELECT saleprice, saledate
FROM sales
GROUP BY saleprice, saledate
HAVING count(*) = 1
);
这比它们中的任何一个都要快得多。在我的PostgreSQL 8.4和9.1测试中,性能因子为10-15而超过了当前接受的答案。
但是这仍然远非最佳选择。使用NOT EXISTS
(反)半连接可以获得更好的性能。 EXISTS
是标准SQL,已经存在很长时间(至少自PostgreSQL 7.2以来,早于此问题的提出),并完全符合所呈现的要求:
UPDATE sales s
SET status = 'ACTIVE'
WHERE NOT EXISTS (
SELECT FROM sales s1
WHERE s.saleprice = s1.saleprice
AND s.saledate = s1.saledate
AND s.id <> s1.id
)
AND s.status IS DISTINCT FROM 'ACTIVE';
db<>fiddle 这里
旧版sqlfiddle
用于标识行的唯一键
如果你的表中没有主键或唯一键(例如示例中的id
),你可以使用系统列ctid
来代替,以便在此查询中使用(但不能用于其他用途):
AND s1.ctid <> s.ctid
每个表都应该有一个主键。如果您还没有,请添加一个。我建议在Postgres 10+中使用
serial
或
IDENTITY
列。
相关:
这样做为什么更快?
EXISTS
反半连接子查询可以在找到第一个重复项后停止评估(没有必要再查找)。对于具有少量重复项的基本表,这只是略微提高了效率。但是对于具有大量重复项的表,效率会显著提高。
排除空更新
对于已经有
status = 'ACTIVE'
的行,此更新不会改变任何内容,但仍会以全价插入新的行版本(有少量例外)。通常情况下,您不希望这样做。像上面演示的那样添加另一个
WHERE
条件来避免这种情况并使其更快:
如果定义了
status
为
NOT NULL
,则可以简化为:
AND status <> 'ACTIVE';
该列的数据类型必须支持
<>
运算符。一些类型如
json
不支持。请参见:
NULL处理的微妙差别
与Joel目前接受的答案不同,此查询不将NULL值视为相等。对于(saleprice, saledate)
的以下两行将被视为"不同"(尽管在人眼看来相同):
(123, NULL)
(123, NULL)
同时传递一个唯一的索引,几乎可以在任何地方使用,因为根据SQL标准,NULL值不相等。参见:
另一方面,
GROUP BY
、
DISTINCT
或
DISTINCT ON ()
将NULL值视为相等。根据您想要实现的目标使用适当的查询样式。仍然可以使用
IS NOT DISTINCT FROM
代替
=
进行任何或所有比较,使NULL比较相等,从而使用更快的查询。更多信息:
如果所有被比较的列都定义为
NOT NULL
,则没有分歧的余地。