Postgres DISTINCT 与 DISTINCT ON 有啥区别?

Posted

技术标签:

【中文标题】Postgres DISTINCT 与 DISTINCT ON 有啥区别?【英文标题】:What is the difference between Postgres DISTINCT vs DISTINCT ON?Postgres DISTINCT 与 DISTINCT ON 有什么区别? 【发布时间】:2018-11-23 14:18:10 【问题描述】:

我有一个使用以下语句创建的 Postgres 表。此表由其他服务的数据转储填充。

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

我正在构建的 ETL 中的一个步骤是提取 dimension1 的唯一值并将它们插入到另一个中间表中。 但是,在一些测试中,我发现下面的 2 个命令不会返回相同的结果。我希望两者都返回相同的金额。 与第二个相比,第一个命令返回的结果更多(1466 行对 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;

谢谢!

【问题讨论】:

很奇怪。旁注:从逻辑上讲,distinctgroup by 应该完成相同的事情,因此让它们同时发挥作用是多余的。 @n8。谢谢你指出这一点。我将有更多字段(在 DISTINCT ON 表达式之外,这将要求我从文档中获得 ORDER BY >:请注意,除非使用 ORDER BY 来确保所需的行首先出现,否则每组的“第一行”是不可预测的 这里有一些有趣的东西:medium.com/statuscode/… 我不再使用 PostgreSQL,而且使用的时候也不是很频繁,所以我不知道 DISTINCT 和 DISTINCT ON 之间有什么区别。 【参考方案1】:

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

先说理论

DISTINCT 适用于整个元组。一旦计算出查询结果,DISTINCT 就会从结果中删除所有重复的元组。

例如,假设一个表 R 包含以下内容:

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

(6 行)

SELECT distinct * from R 将导致:

# 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 语言的补充。分组依据相似,但不相同。

它的语法是:

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

例如:

 SELECT DISTINCT ON (a) * from R

它的语义可以描述如下。像往常一样计算查询——没有 DISTINCT ON (a)——但在结果的投影之前,对当前结果进行排序并根据 DISTINCT ON 中的属性列表对其进行分组(类似于 group by)。现在,使用每个组中的第一个元组进行投影并忽略其他元组。

例子:

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

然后对于每个不同的 a 值,取第一个元组。与以下内容相同:

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

一些 DBMS(尤其是 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 的计数(data_table 中维度 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 并做剩下的 询问。这是按维度 1 分组的。因此这部分查询 每个维度 1 的不同值将产生一个元组。

现在,DISTINCT 开启。它再次使用维度1。但是 dimension1 已经是唯一的(由于 group by)。因此 这使得 DISTINCT ON superflouos (它什么都不做)。 最终计数只是 group by 中所有元组的计数。

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

SELECT (DISTINCT ON a) a
FROM R

SELECT a FROM R group by a

SELECT DISTINCT a FROM R

警告

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

一个有趣的方面

Distinct ON 以更简洁的方式模拟了 sqlite 的 bad 行为。假设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

推论

当您想要访问在功能上依赖于 group by 属性的值时,DISTINCT ON 在 group by 中很有用。换句话说,如果您知道对于每组属性,它们始终具有与第三个属性相同的值,那么在该组属性上使用 DISTINCT。否则,您必须进行 JOIN 才能检索第三个属性。

【讨论】:

感谢您的解释。绝对有助于理解这些差异。我通读了文档,显然还不够好。 您好@dmg,再次,我看到您更新了您的答案,提供了更详细的解释,非常感谢您抽出宝贵时间。 像这样学习有什么好的方法,你至少在理论上知道幕后发生了什么。大多数网站只是语法参考,没有提供关于这一切的背景信息,所有这些都在幕后联系在一起。 @dmg【参考方案2】:

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

DISTINCT这个词在

SELECT DISTINCT count(dimension1)
FROM data_table;

没有意义,因为查询返回单行。也许你想要

SELECT count(DISTINCT dimension1)
FROM data_table;

返回dimension1 的不同非空值的数量。请注意,它不一样

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; 显示了它。谢谢!【参考方案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 很高兴听到它有帮助。但我认为主要是 DISTINCT ON(column) 不是标准 SQL 并且特定于 PostgreSQL。这些查询的其余部分在 MySQL 或其他数据库类型上的行为相同。 当然@LukStorms。奇怪的是,这也引起了从 MySQL 替换 Group By 的需要,因为它不需要聚合,因此它也以非标准方式表现 - 当这种情况发生时,它只会获得一行值,即使聚合导致聚合的最后一步返回更多行。 (只是好奇) @tekneee 如果您在谈论 MySql 中的 GROUP BY。我记得与其他数据库类型相比,至少旧版本的 MySql 对 GROUP BY 是特殊的。在任何其他数据库中,都不允许使用 select foo, bar, count(*) from test group by foo 之类的东西。因为您会收到“bar”不在 GROUP BY 中的错误。但在 MySql 中通常是允许的。这有时会导致意想不到的结果。 @tekneee 他们解释了它:here。但是您实际上可以禁用该行为。 F.e.检查this old SO post【参考方案4】:

试试

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

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

【讨论】:

谢谢,但@klin 的回答实际上解决了它。计数之外的DISTINCT 没有做任何事情。

以上是关于Postgres DISTINCT 与 DISTINCT ON 有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

使用count distinct在postgres中进行慢速查询

如何确定 postgres 视图是不是可更新

Postgres distinct union 仅适用于特定列

DAX从入门到精通 3-6-1 了解values和distinct

Django 不使用 Postgres

"HybridDB · 性能优化 · Count Distinct的几种实现方式” 读后感