我需要检索表中的所有行,其中2列组合都是不同的.因此,我希望所有在同一天没有任何其他销售的销售以相同的价格出售.基于日期和价格的唯一销售将更新为活动状态.
所以我在想:
UPDATE sales SET status = 'ACTIVE' WHERE id IN (SELECT DISTINCT (saleprice, saledate), id, count(id) FROM sales HAVING count = 1)
但是我的大脑比那更远了.
SELECT DISTINCT a,b,c FROM t
是大致等效于:
SELECT a,b,c FROM t GROUP BY a,b,c
习惯GROUP BY语法是个好主意,因为它更强大.
对于您的查询,我会这样做:
UPDATE sales SET status='ACTIVE' WHERE id IN ( SELECT id FROM sales S INNER JOIN ( SELECT saleprice, saledate FROM sales GROUP BY saleprice, saledate HAVING COUNT(*) = 1 ) T ON S.saleprice=T.saleprice AND s.saledate=T.saledate )
如果你把目前为止的答案放在一起,清理和改进,你会得到这个优越的问题:
UPDATE sales SET status = 'ACTIVE' WHERE (saleprice, saledate) IN ( SELECT saleprice, saledate FROM sales GROUP BY saleprice, saledate HAVING count(*) = 1 );
这是很多比任何人更快.以10-15的因子(在我对PostgreSQL 8.4和9.1的测试中)来说明当前接受的答案的性能.
但这仍远未达到最佳状态.使用NOT EXISTS
(反)半连接可获得更好的性能.EXISTS
是标准的SQL,已经永远存在(至少自PostgreSQL 7.2以来,很久才提出这个问题)并完全符合所提出的要求:
UPDATE sales s SET status = 'ACTIVE' WHERE NOT EXISTS ( SELECT FROM sales s1 -- SELECT list can be empty for EXISTS WHERE s.saleprice = s1.saleprice AND s.saledate = s1.saledate AND s.id <> s1.id -- except for row itself ) AND s.status IS DISTINCT FROM 'ACTIVE'; -- avoid empty updates. see below
SQL小提琴.
如果您没有表的主键或唯一键(id
在示例中),则可以使用系统列替换ctid
此查询(但不是出于其他目的):
AND s1.ctid <> s.ctid
每个表都应该有一个主键.如果还没有,请添加一个.我推荐Postgres 10+中的一个serial
或一个IDENTITY
专栏.
有关:
有序序列生成
自动增量表列
EXISTS
反半连接中的子查询可以在找到第一个欺骗时立即停止评估(没有必要进一步查看).对于具有少量重复的基表,这只是稍微更有效.随着大量重复的这成为方式更有效.
如果已有一些或多行status = 'ACTIVE'
,您的更新将不会更改任何内容,但仍会以全部成本插入新行版本(适用次要例外).通常,你不想要这个.添加WHERE
上面演示的另一个条件,使其更快:
如果status
已定义NOT NULL
,则可以简化为:
AND status <> 'ACTIVE';
此查询(与Joel当前接受的答案不同)不会将NULL值视为相等.这两行将(saleprice, saledate)
符合"不同"(虽然看起来与人眼相同):
(123, NULL) (123, NULL)
还传入一个唯一的索引,几乎在任何其他地方,因为根据SQL标准,NULL值不能相等.看到:
使用空列创建唯一约束
OTOH,GROUP BY
或DISTINCT
或DISTINCT ON ()
将NULL值视为相等.根据您要实现的目标使用适当的查询样式.您仍然可以使用这种更快的查询样式,IS NOT DISTINCT FROM
而不是使用=
任何或所有比较来使NULL比较相等.更多:
如何删除没有唯一标识符的重复行
如果定义了所有比较的列NOT NULL
,则没有分歧的余地.
您的查询的问题是,当使用GROUP BY子句(您实际上通过使用distinct)时,您只能使用分组的列或聚合函数.您不能使用列ID,因为可能存在不同的值.在你的情况下,由于HAVING子句,总是只有一个值,但大多数RDBMS都不够聪明,无法识别它.
这应该工作(并且不需要连接):
UPDATE sales SET status='ACTIVE' WHERE id IN ( SELECT MIN(id) FROM sales GROUP BY saleprice, saledate HAVING COUNT(id) = 1 )
您也可以使用MAX或AVG而不是MIN,如果只有一个匹配的行,则使用返回列值的函数非常重要.