我如何(或我可以)在多列上选择 DISTINCT?

Posted

技术标签:

【中文标题】我如何(或我可以)在多列上选择 DISTINCT?【英文标题】:How do I (or can I) SELECT DISTINCT on multiple columns? 【发布时间】:2010-09-08 10:32:20 【问题描述】:

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

所以我在想:

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

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

【问题讨论】:

【参考方案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
 )

【讨论】:

这个查询虽然正确并且已经被接受了一年,但它效率极低而且没有必要。不要使用这个。我在另一个答案中提供了替代方案和一些解释。 isn't SELECT DISTINCT a,b,c FROM t 精确与 SELECT a,b,c FROM t GROUP BY a,b,c 一样吗?跨度> @famargar 对于简单的情况,但是,它们在语义上具有不同的含义,并且它们在构建更大查询时可以为该步骤执行的操作方面是不同的。此外,技术论坛上的人们通常对事物非常迂腐,我发现在这种情况下,在我的帖子中添加狡猾的词通常很有用。【参考方案2】:

如果您将迄今为止的答案汇总起来,进行清理和改进,您将得到这个出色的查询:

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小提琴hereOld SQL Fiddle

识别行的唯一键

如果您没有表的主键或唯一键(示例中为id),则可以使用系统列ctid 替换此查询(但不能用于其他某些目的) :

   AND    s1.ctid <> s.ctid

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

相关:

In-order sequence generation Auto increment table column

如何更快?

EXISTS anti-semi-join 中的子查询可以在发现第一个欺骗后立即停止评估(没有必要进一步查看)。对于几乎没有重复的基表,这只是稍微更有效。如果有很多重复,这会变得方式更有效率。

排除空更新

对于已经具有status = 'ACTIVE' 的行,此更新不会改变任何内容,但仍会以全部成本插入新的行版本(适用少数例外情况)。通常,您不希望这样。添加另一个WHERE 条件,如上所示,以避免这种情况并使其更快:

如果status 定义为NOT NULL,则可以简化为:

AND status <> 'ACTIVE';

列的数据类型必须支持&lt;&gt; 运算符。像json 这样的一些类型不会。见:

How to query a json column for empty objects?

NULL 处理的细微差别

此查询(与currently accepted answer by Joel 不同)不会将 NULL 值视为相等。 (saleprice, saledate) 的以下两行将被视为“不同”(虽然看起来与人眼相同):

(123, NULL)
(123, NULL)

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

Create unique constraint with null columns

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

How to delete duplicate rows without unique identifier

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

【讨论】:

好答案。我是一个 sql server 人,所以我不会想到使用带有 IN ( ) 检查的元组的第一个建议。不存在的建议通常会在 sql server 中得到与内部连接相同的执行计划。 不错。解释大大增加了答案的价值。我几乎想用 Oracle 进行一些测试,看看这些计划与 Postgres 和 SQLServer 相比如何。 @alairock:你从哪里得到的?对于 Postgres,相反 是正确的。在计算所有行时,count(*)count(&lt;expression&gt;) 效率更高。就试一试吧。 Postgres 对聚合函数的这种变体有更快的实现。也许您将 Postgres 与其他一些 RDBMS 混淆了? @alairock:我碰巧是该页面的合著者,但它没有说任何类似的内容。 @ErwinBrandstetter ,你总是在堆栈中回答你的问题。这些年来,您以几乎难以想象的方式提供了帮助。至于这个例子,我知道解决我的问题的几种不同方法,但我想看看有人测试了可能性之间的效率。谢谢。【参考方案3】:

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

【讨论】:

【参考方案4】:

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

select distinct(col1, col2) from table

多选一般可以安全执行如下:

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

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

【讨论】:

【参考方案5】:

我想从“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)

【讨论】:

这基本上解释了接受的答案的作用,但我建议不要使用这样的名称作为示例(至少翻译它们)。 PS:我建议在所有项目中始终用英文命名,即使你是荷兰人。

以上是关于我如何(或我可以)在多列上选择 DISTINCT?的主要内容,如果未能解决你的问题,请参考以下文章

在JPA上选择DISTINCT

在 id 上选择 distinct 以返回一行,但能够访问其他列值(rails 关联)

在一列上选择 DISTINCT,返回多个其他列(SQL Server)

在一列上选择 DISTINCT,返回多个其他列(SQL Server)

使用 T-SQL 中的 OVER 子句在除一列之外的所有列上选择 DISTINCT

如何在数据基础上选择分区名称