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;
谢谢!
【问题讨论】:
很奇怪。旁注:从逻辑上讲,distinct
和 group 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 distinct union 仅适用于特定列