我如何(或可以)在多列上选择不同的?

问题描述 投票:0回答:5

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

所以我在想:

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

但是再走得更远我的大脑就会受伤。

sql postgresql sql-update duplicates distinct
5个回答
639
投票
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
 )

432
投票

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

UPDATE sales
SET    status = 'ACTIVE'
WHERE  (saleprice, saledate) IN (
    SELECT saleprice, saledate
    FROM   sales
    GROUP  BY saleprice, saledate
    HAVING count(*) = 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

小提琴
sqlfiddle

识别行的唯一键

如果您没有表的主键或唯一键(示例中的

id
),您可以使用系统列
ctid
tableoid
来替换以用于此查询(但不适用于其他某些查询)目的):

   AND   (s1.ctid <> s.ctid OR s1.tableoid <> s.tableoid)

tableoid
确保它即使在继承或分区的情况下也能正常工作。参见:

每个表都应该有一个主键。如果您还没有,请添加一个。我建议在 Postgres 10+ 中使用

serial
IDENTITY
列。

相关:

这怎么更快?

EXISTS
反半连接中的子查询一旦找到第一个重复项就可以停止评估(没有必要继续寻找)。对于具有很少重复项的基表,这只会稍微提高效率。有了很多重复项,这就会变得方式更加高效。

排除空更新

对于已经具有

status = 'ACTIVE'
的行,此更新不会改变任何内容,但仍然以(大部分)全额成本插入新的行版本。通常,您不希望这样。添加另一个像上面演示的
WHERE
条件以避免这种情况并使其更快:

如果定义了

status
NOT NULL
,则可以简化为:

AND status <> 'ACTIVE';

列的数据类型必须支持

<>
运算符。有些类型如
json
则不然。参见:

NULL 处理的细微差别

当前接受的答案不同,此查询不会将 NULL 值视为相等。以下两行

(saleprice, saledate)
将被视为“不同”(尽管与人眼看起来相同):

(123, NULL)
(123, NULL)

还传入唯一索引以及几乎任何其他地方,因为根据 SQL 标准,NULL 值比较不相等。参见:

OTOH、

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

如果要比较的所有列都已定义

NOT NULL
,则没有分歧的余地。


28
投票

查询的问题在于,当使用 GROUP BY 子句(本质上是通过使用 unique 来完成)时,您只能使用分组依据或聚合函数的列。您不能使用列 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,只有在只有一个匹配行时才使用返回列值的函数才重要。


11
投票

如果您的 DBMS 不支持多列的不同,如下所示:

select distinct(col1, col2) from table

多选一般可以安全地执行,如下所示:

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

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


3
投票

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

仅获取一列的不同值
Select distinct GrondOfLucht,sortering
from CorWijzeVanAanleg
order by sortering

它还将给出“排序”列,并且由于“GrondOfLucht”和“排序”不唯一,因此结果将是所有行。

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

SELECT        GrondOfLucht
FROM            dbo.CorWijzeVanAanleg
GROUP BY GrondOfLucht, sortering
ORDER BY MIN(sortering)
© www.soinside.com 2019 - 2024. All rights reserved.