Postgres中DISTINCT和DISTINCT ON有什么区别?

47

我有一个使用以下语句创建的Postgres表。该表通过从另一个服务中转储数据来填充。

CREATE TABLE data_table (
    date date DEFAULT NULL,
    dimension1 varchar(64) DEFAULT NULL,
    dimension2 varchar(128) DEFAULT NULL
) TABLESPACE pg_default;

我正在构建一个ETL过程,其中一步是提取dimension1的唯一值,并将它们插入到另一个中间表中。 然而,在进行一些测试时,我发现以下两个命令返回的结果不同。我希望两个命令都能返回相同的总数。 第一个命令返回的结果比第二个命令多(1466行 vs. 1504行)。

-- command 1
SELECT DISTINCT count(dimension1)
FROM data_table;

-- command 2    
SELECT count(*)
FROM (SELECT DISTINCT ON (dimension1) dimension1
FROM data_table
GROUP BY dimension1) AS tmp_table;
这有什么明显的解释吗?除了解释外,是否有任何关于我应该对数据进行哪些检查的建议?
编辑:以下查询都返回1504(与“简单”的DISTINCT相同)。
SELECT count(*)
FROM data_table WHERE dimension1 IS NOT NULL;

SELECT count(dimension1)
FROM data_table;

谢谢!


有点奇怪。顺便说一下:从逻辑上讲,“distinct”和“group by”应该完成相同的任务,因此在使用它们时会产生冗余。 - n8.
@n8,感谢您指出这一点。我将有更多的字段(在DISTINCT ON表达式之外),这将要求我有ORDER BY。从文档中可以看到:请注意,每个集合的“第一行”是不可预测的,除非使用ORDER BY来确保所需的行首先出现。 - tekneee
这里有一些有趣的内容:https://medium.com/statuscode/the-many-faces-of-distinct-in-postgresql-c52490de5954 - n8.
5个回答

78

DISTINCT和DISTINCT ON具有完全不同的语义。

先说理论

DISTINCT适用于整个元组。一旦查询的结果被计算出来,DISTINCT将从结果中删除任何重复的元组。

例如,假设有一个名为R的表格,其内容如下:

#table r;
a | b 
---+---
1 | a
2 | b
3 | c
3 | d
2 | e
1 | a

(共6行)

从R中选择distinct *将得到:

# select distinct * from r;
 a | b 
---+---
 1 | a
 3 | d
 2 | e
 2 | b
 3 | c
(5 rows)

注意,distinct关键字适用于所有投影属性的列表:因此

select distinct * from R

与语义上等价。
select distinct a,b from R

您无法发出

select a, distinct b From R

DISTINCT必须紧跟在SELECT后面,它应用于整个元组,而不是结果的属性。

DISTINCT ON是PostgreSQL对该语言的补充。它类似于group by,但并非完全相同。

它的语法如下:

 SELECT DISTINCT ON (attributeList) <rest as any query>

例如:
 SELECT DISTINCT ON (a) * from R

IT语义如下:通常计算结果(不包括DISTINCT ON(a)),然后在将结果投影之前,对当前结果进行排序并根据DISTINCT ON中的属性列表进行分组(类似于group by)。现在,在每个组中使用第一个元组进行投影,并忽略其他元组。

示例:

select * from r order by a;
     a | b 
    ---+---
     1 | a
     2 | e
     2 | b
     3 | c
     3 | d
    (5 rows)

对于每个不同的a值(在此例中为1、2和3),取第一个元组。这与以下内容相同:

 SELECT DISTINCT on (a) * from r;
  a | b 
 ---+---
 1 | a
 2 | b
 3 | c
 (3 rows)

一些数据库管理系统(尤其是sqlite)允许您运行此查询:

 SELECT a,b from R group by a;

这会给您一个类似的结果。

只有在从a到b存在函数依赖时,Postgresql才允许此查询。换句话说,如果对于关系R的任何实例,每个值或a仅有一个唯一元组(因此选择第一个元组是确定的:只有一个元组),则此查询将有效。

例如,如果R的主键是a,则a->b且:

SELECT a,b FROM R group by a

等同于:

  SELECT DISTINCT on (a) a, b from r;

现在回到你的问题:

第一个查询:

SELECT DISTINCT count(dimension1)
FROM data_table;

计算维度1的数量(数据表中维度1不为空的元组数量)。此查询返回一个元组,始终是唯一的(因此DISTINCT是多余的)。

查询2:

SELECT count(*)
FROM (SELECT DISTINCT ON (dimension1) dimension1
FROM data_table
GROUP BY dimension1) AS tmp_table;

这是一个嵌套的查询。为了更加清晰,让我重写一下:

WITH tmp_table AS (
   SELECT DISTINCT ON (dimension1) 
     dimension1 FROM data_table
     GROUP by dimension1) 
SELECT count(*) from tmp_table

让我们先计算tmp_table。如我上面提到的那样,让我们先忽略DISTINCT ON并执行查询的其余部分。这是按dimension1进行分组。因此查询的这部分将导致每个不同dimension1值的一个元组。

现在,考虑DISTINCT ON。它再次使用dimension1。但是由于已经通过group by唯一确定了dimension1,因此DISTINCT ON变得多余(它什么也不做)。最终结果只是对group by中所有元组的计数。

正如您所看到的,在以下查询中存在等价性(适用于具有属性a的任何关系):

SELECT (DISTINCT ON a) a
FROM R

and
SELECT a FROM R group by a

SELECT DISTINCT a FROM R

警告

在查询中使用DISTINCT ON可能对于数据库的给定实例是不确定性的。 换句话说,对于相同的数据表,查询可能会返回不同的结果。

一个有趣的方面

Distinct ON以更加干净的方式模拟了sqlite的不良行为。假设R有两个属性a和b:

SELECT a, b FROM R group by a

在SQL中,这是一个非法语句。但是,在sqlite上可以运行。它只是从具有相同a值的组中的任何元组中获取b的随机值。 在Postgresql中,此语句是非法的。相反,您必须使用DISTINCT ON并编写:

SELECT DISTINCT ON (a) a,b from R

推论

DISTINCT ON在分组查询中很有用,当您想要访问与分组依据属性相关的值时。换句话说,如果您知道对于每组属性,它们始终具有第三个属性的相同值,则使用DISTINCT ON该属性组。否则,您必须执行JOIN操作来检索第三个属性。


谢谢您的解释。对于理解这些差异确实有帮助。我已经阅读了文档,显然不够仔细。 - tekneee
你好 @dmg,我又看到你更新了答案并提供了更详细的解释,非常感谢你花费时间。 - tekneee
有什么好的方法可以学习这样的编程,让你至少在理论上知道背后发生了什么。大多数网站只是语法参考,并没有提供关于所有背后联系的背景信息。@dmg - Muhammad Umer

3

第一个查询返回dimension1列中非 null 值的数量,而第二个查询返回该列不同值的数量。如果列包含重复项或空值,则这些数字显然是不相等的。

DISTINCT关键字在...

SELECT DISTINCT count(dimension1)
FROM data_table;

这个查询只返回一行,所以没有意义。也许你想要的是:
SELECT count(DISTINCT dimension1)
FROM data_table;

这个函数返回dimension1不同非null值的数量。请注意,这与

SELECT count(*)
FROM (
    SELECT DISTINCT ON (dimension1) dimension1
    FROM data_table
    -- GROUP BY dimension1 -- redundant
    ) AS tmp_table;

最后一个查询返回该列的所有(空或非空)不同值的数量。

该死....将DISTINCT从计数器外部移动到内部(您的代码块SELECT count(DISTINCT dimension1) FROM data_table;显示了它。谢谢! - tekneee

3

为了通过视觉示例来学习和了解发生的事情。
这里有一些在PostgreSQL上执行的SQL代码:

DROP TABLE IF EXISTS test_table;
CREATE TABLE test_table (
    id int NOT NULL primary key,
    col1 varchar(64) DEFAULT NULL
);

INSERT INTO test_table (id, col1) VALUES 
(1,'foo'), (2,'foo'), (3,'bar'), (4,null);

select count(*) as total1 from test_table;
-- returns: 4
-- Because the table has 4 records.

select distinct count(*) as total2 from test_table;
-- returns: 4
-- The count(*) is just one value. Making 1 total unique can only result in 1 total. 
-- So the distinct is useless here.

select col1, count(*) as total3 from test_table group by col1 order by col1;
-- returns 3 rows: ('bar',1),('foo',2),(NULL,1)
-- Since there are 3 unique col1 values. NULL's are included.

select distinct col1, count(*) as total4 from test_table group by col1 order by col1;
-- returns 3 rows: ('bar',1),('foo',2),(NULL,1)
-- The result is already grouped, and therefor already unique. 
-- So again, the distinct does nothing extra here.

select count(distinct col1) as total5 from test_table;
-- returns 2
-- NULL's aren't counted in a count by value. So only 'foo' & 'bar' are counted

select distinct on (col1) id, col1 from test_table order by col1 asc, id desc;
-- returns 3 rows: (2,'a'),(3,'b'),(4,NULL)
-- So it gets the records with the maximum id per unique col1
-- Note that the "order by" matters here. Changing that DESC to ASC would get the minumum id.

select count(*) as total6 from (select distinct on (col1) id, col1 from test_table order by col1 asc, id desc) as q;
-- returns 3. 
-- After seeing the previous query, what else would one expect?

select distinct col1 from test_table order by col1;
-- returns 3 unique values : ('bar'),('foo'),(null)

select distinct id, col1 from test_table order by col1;
-- returns all records. 
-- Because id is the primary key and therefore makes each returned row unique

谢谢@LukStorms,非常有用的东西!我正在将一堆聚合函数从MySQL翻译到Postgres,能够获得更多的示例就更好了。 - tekneee
当然,@LukStorms。有趣的是,这也源于需要替换MySQL中的Group By,因为它也表现出非标准的行为,因为它不需要聚合 - 当发生这种情况时,它只获取一个值行,即使聚合导致聚合的最后一步返回更多行。(只是一个好奇心) - tekneee
如果你在谈论 MySql 中的 GROUP BY,我记得至少旧版本的 MySql 在 GROUP BY 方面与其他数据库类型有些不同。在任何其他数据库中,像 select foo, bar, count(*) from test group by foo 这样的语句都是不允许的。因为你会得到“bar”不在 GROUP BY 中的错误。但在 MySql 中,这通常是被允许的。这有时会导致意外的结果。 - LukStorms
@tekneee他们已经解释了:这里。但是你实际上可以禁用这种行为。例如,请查看这篇旧的SO帖子 - LukStorms
谢谢。我知道那种奇怪的行为。我的问题是,我正在“翻译”的脚本已经在MySQL中正常工作,迁移到Postgres意味着它需要以相同的方式运行。所有给出的答案和贡献肯定有助于实现这一点。 - tekneee
显示剩余2条评论

2

以下是一份更直接的总结,可能对谷歌用户有用,回答了标题但不涉及完整帖子的复杂性:

SELECT DISTINCT

这是一种SQL查询语句,用于从数据库中选择唯一的(不同的)值。

  • availability: ISO

  • behaviour:

    SELECT DISTINCT col1, col2, col3 FROM mytable
    

    returns col1, col2 and col3 and omits any rows in which all of the tuple (col1, col2, col3) are the same. E.g. you could get a result like:

    1 2 3
    1 2 4
    

    because those two rows are not identical due to the 4. But you could never get:

    1 2 3
    1 2 4
    1 2 3
    

    because 1 2 3 appears twice, and both rows are exactly the same. That is what DISTINCT prevents.

  • vs GROUP BY: SELECT DISTINCT is basically a subset of GROUP BY where you can't use aggregate functions: Is there any difference between GROUP BY and DISTINCT

SELECT DISTINCT ON

  • availability: PostgreSQL extension, WONTFIXED by SQLite

  • behavior: unlike DISTINCT, DISTINCT ON allows you to separate

    • what you want to be unique
    • from what you want to return

    E.g.:

    SELECT DISTINCT ON(col1) col2, col3 FROM mytable
    

    returns col2 and col3, and does not return any two rows with the same col1. E.g.:

    1 2 3
    1 4 5
    

    could not happen, because we have 1 twice on col1.

    And e.g.:

    SELECT DISTINCT ON(col1, col2) col2, col3 FROM mytable
    

    would prevent any duplicated (col1, col2) tuples, e.g. you could get:

    1 2 3
    1 4 3
    

    as it has different (1, 2) and (1, 4) tuples, but not:

    1 2 3
    1 2 4
    

    where (1, 2) happens twice, only one of those two could appear.

    We can uniquely determine which one of the possible rows will be selected with ORDER BY which guarantees that the first match is taken, e.g.:

    SELECT DISTINCT ON(col1, col2) col2, col3 FROM mytable
      ORDER BY col1 DESC, col2 DESC, col3 DESC
    

    would ensure that among:

    1 2 3
    1 2 4
    

    only 1 2 4 would be picked as it happens first on our DESC sorting.

  • vs GROUP BY: DISTINCT ON is not a subset of GROUP BY because it allows you to access extra rows not present in the GROUP BY, which is generally not allowed in GROUP BY, unless:

    This makes DISTINCT ON extremely useful to fulfill the common use case of "find the full row that reaches the maximum/minimum of some column": Is there any difference between GROUP BY and DISTINCT

    E.g. to find the city of each country that has the most sales:

    SELECT DISTINCT ON ("country") "country", "city", "amount"
    FROM "Sales"
    ORDER BY "country" ASC, "amount" DESC, "city" ASC
    

    or equivalently with * if we want all columns:

    SELECT DISTINCT ON ("country") *
    FROM "Sales"
    ORDER BY "country" ASC, "amount" DESC, "city" ASC
    

    Here each country appears only once, within each country we then sort by amount DESC and take the first, and therefore highest, amount.

RANKROW_NUMBER窗口函数

这些基本上可以用作DISTINCT ON的超集,并且已经在SQLite 3.34和PostgreSQL 14.3中实现并测试。我强烈建议您也了解一下它们,例如: 如何选择一个列的不同值并获取其他值?

以下是使用ROW_NUMBER编写的“每个国家最高金额的城市”查询:

SELECT *
FROM (
    SELECT
      ROW_NUMBER() OVER (
        PARTITION BY "country"
        ORDER BY "amount" DESC, "city" ASC
      ) AS "rnk",
      *
    FROM "Sales"
  ) sub
WHERE
  "sub"."rnk" = 1
ORDER BY
  "sub"."country" ASC

0

尝试

SELECT count(dimension1a)
FROM (SELECT DISTINCT ON (dimension1) dimension1a
FROM data_table
ORDER BY dimension1) AS tmp_table;

DISTINCT ON 似乎是 GROUP BY 的同义词。


1
谢谢,但是@klin的答案实际上解决了问题。DISTINCT在计数外面没有起到任何作用。 - tekneee
“DISTINCT ON appears to be synonymous with GROUP BY”:DISTINCT ONGROUP BY 的超集,因为它允许您选择未分组的列,详情请参见:https://dev59.com/A1UL5IYBdhLWcg3wHU0t#72997494。 - Ciro Santilli OurBigBook.com

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接