488

我需要从一个表中检索所有行,其中 2 列组合起来都不同。所以我想要所有在同一天以相同价格发生的没有任何其他销售的销售。基于日期和价格的唯一销售将更新为活动状态。

所以我在想:

UPDATE sales
SET status = 'ACTIVE'
WHERE id IN (SELECT DISTINCT (saleprice, saledate), id, count(id)
             FROM sales
             HAVING count = 1)

但是我的大脑比这更痛苦。

4

5 回答 5

513
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
 )
于 2008-09-10T15:36:09.417 回答
376

如果您将到目前为止的答案放在一起,进行清理和改进,您将得到这个高级查询:

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

db<>fiddle here
旧 SQL Fiddle

标识行的唯一键

如果您没有表的主键或唯一键(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 值不比较相等。看:

OTOH, GROUP BY,DISTINCTDISTINCT ON ()将 NULL 值视为相等。根据您想要实现的目标使用适当的查询样式。您仍然可以使用这个更快的查询来IS NOT DISTINCT FROM代替=任何或所有比较,以使 NULL 比较相等。更多的:

如果定义了所有被比较的列NOT NULL,则没有分歧的余地。

于 2012-09-28T00:50:06.250 回答
27

您的查询的问题在于,当使用 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,如果只有一个匹配行,则使用返回列值的函数很重要。

于 2008-09-10T16:17:13.937 回答
4

如果您的 DBMS 不支持具有多个列的 distinct,如下所示:

select distinct(col1, col2) from table

多选通常可以安全地执行如下:

select distinct * from (select col1, col2 from table ) as x

因为这可以在大多数 DBMS 上工作,并且由于您避免了分组功能,因此预计这比按解决方案分组更快。

于 2019-01-31T08:52:09.597 回答
2

我想从一列“GrondOfLucht”中选择不同的值,但它们应该按照“排序”列中给出的顺序进行排序。我无法使用仅获得一列的不同值

Select distinct GrondOfLucht,sortering
from CorWijzeVanAanleg
order by sortering

它还将给出“sortering”列,因为“GrondOfLucht”和“sortering”不是唯一的,结果将是所有行。

使用 GROUP 按“sortering”给出的顺序选择“GrondOfLucht”的记录

SELECT        GrondOfLucht
FROM            dbo.CorWijzeVanAanleg
GROUP BY GrondOfLucht, sortering
ORDER BY MIN(sortering)
于 2018-01-13T07:56:09.033 回答