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


415

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

所以我在想:

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

但是我的大脑比这更痛。

Answers:


436
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
 )

117
该查询虽然正确并已被接受一年,但它效率极低并且不必要。不要使用这个。我在另一个答案中提供了替代方法和一些解释。
Erwin Brandstetter,2012年

1
是不是SELECT DISTINCT A,B,C FROM t请求正是同样的事情SELECT A,B,C来自T组由A,B,C?
famargar

8
@famargar对于最简单的情况,但是,它们在语义上具有不同的含义,并且在构建更大的查询时,您可以针对该步骤执行的操作也有所不同。另外,技术论坛上的人通常对事情都非常学究,我发现在这种情况下在我的帖子中添加狡猾的单词通常很有用。
Joel Coehoorn

344

如果将到目前为止的答案汇总在一起,进行清理和改进,那么您将得出以下高级查询:

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 <> 在这里拨弄旧的SQL 拨弄

识别行的唯一键

如果您没有该表的主键或唯一键(id在示例中),则可以使用system列代替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)

还传递唯一索引和几乎其他任何地方,因为NULL值根据SQL标准不等于相等。看到:

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

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


16
好答案。我是SQL Server专家,所以我不会想到使用带有IN()检查的元组的第一个建议。不存在的建议通常会在sql服务器中以与内部联接相同的执行计划结束。
Joel Coehoorn 2012年

2
真好 这种解释大大增加了答案的价值。我几乎想对Oracle进行一些测试,以查看该计划与Postgres和SQLServer的比较。
彼得,

2
@alairock:你从哪儿得到的?对于Postgres,情况恰恰相反。虽然计数所有行,count(*)比效率count(<expression>)。去尝试一下。Postgres对于聚合函数的此变体具有更快的实现。也许您将Postgres与其他RDBMS混淆了?
Erwin Brandstetter

6
@alairock:我碰巧是该页面的合著者,它没有说什么。
Erwin Brandstetter

2
@ErwinBrandstetter,您总是会在整个堆栈中找到答案。这些年来,您以几乎无法想象的方式提供了帮助。对于此示例,我知道几种解决问题的方法,但是我想看看有人在各种可能性之间测试了效率。谢谢。
WebWanderer

24

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


1

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

Select distinct GrondOfLucht,sortering
from CorWijzeVanAanleg
order by sortering

它还将给列“排序”,并且因为“ GrondOfLucht”和“排序”不是唯一的,所以结果将是所有行。

使用“组”以“排序”给定的顺序选择“ GrondOfLucht”的记录

SELECT        GrondOfLucht
FROM            dbo.CorWijzeVanAanleg
GROUP BY GrondOfLucht, sortering
ORDER BY MIN(sortering)

这基本上解释了可接受的答案的作用,但我建议您不要使用此类名称作为示例(至少将它们翻译)。PS:我建议您始终在所有项目中用英语命名所有内容,即使您荷兰语也是如此。
Kerwin Sneijders

0

如果您的DBMS不支持像这样的多个列,则:

select distinct(col1, col2) from table

通常,可以安全执行多重选择,如下所示:

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

因为这可以在大多数DBMS上使用,并且由于避免了分组功能,所以预计它比按解决方案分组要快。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.