【问题标题】:Eliminate duplicate rows in a PostgreSQL SELECT statement消除 PostgreSQL SELECT 语句中的重复行
【发布时间】:2023-03-14 04:18:02
【问题描述】:

这是我的查询:

SELECT autor.entwickler,anwendung.name
  FROM autor 
  left join anwendung
    on anwendung.name = autor.anwendung;

 entwickler |    name     
------------+-------------
 Benutzer 1 | Anwendung 1
 Benutzer 2 | Anwendung 1
 Benutzer 2 | Anwendung 2
 Benutzer 1 | Anwendung 3
 Benutzer 1 | Anwendung 4
 Benutzer 2 | Anwendung 4
(6 rows)

我想为字段 name 中的每个不同值保留一行,并像这样丢弃其他值:

 entwickler |    name     
------------+-------------
 Benutzer 1 | Anwendung 1
 Benutzer 2 | Anwendung 2
 Benutzer 1 | Anwendung 3
 Benutzer 1 | Anwendung 4

在 MySQL 中我会这样做:

SELECT autor.entwickler,anwendung.name
  FROM autor
  left join anwendung
    on anwendung.name = autor.anwendung
 GROUP BY anwendung.name;

但是 PostgreSQL 给了我这个错误:

错误:列“auto.entwickler”必须出现在 GROUP BY 子句中 或用于聚合函数第 1 行:SELECT autor.entwickler FROM autor left join anwendung on an ...

我完全理解错误并假设 mysql 实现比 postgres 实现更不符合 SQL。但是我怎样才能得到想要的结果呢?

【问题讨论】:

  • 您的 MySQL 示例在非标准 SQL 模式下工作,而 PostgreSQL 使用标准 SQL...要比较,您必须在 MySQL 中使用ONLY_FULL_GROUP_BY 模式。即使在 MySQL 中,您也需要一个聚合采样器函数(ANY_VALUE 由 Craig Ringer 评论)...另见 dba.stackexchange.com/a/133747/90651

标签: mysql sql postgresql select duplicates


【解决方案1】:

PostgreSQL 目前不允许模棱两可的 GROUP BY 语句,其中结果取决于扫描表的顺序、使用的计划等。这就是标准所说的它应该如何工作 AFAIK,但某些数据库(如 MySQL 版本5.7 之前)允许更松散的查询,只选择出现在 SELECT 列表中但不在 GROUP BY 中的元素遇到的第一个值。

在 PostgreSQL 中,您应该使用 DISTINCT ON 进行此类查询。

你想写这样的东西:

SELECT DISTINCT ON (anwendung.name) anwendung.name, autor.entwickler
FROM author 
left join anwendung on anwendung.name = autor.anwendung;

(根据后续评论更正语法)

这有点像 MySQL 5.7 的 ANY_VALUE(...) 伪函数 group by,但反过来 - 它表示 distinct on 子句中的值必须是唯一的,并且列可以接受任何值指定。

除非有ORDER BY,否则无法保证选择哪些值。您通常应该有一个ORDER BY 以确保可预测性。

还注意到使用min()max() 之类的聚合会起作用。虽然这是真的 - 并且将导致可靠和可预测的结果,与使用 DISTINCT ON 或模棱两可的 GROUP BY 不同 - 由于需要额外的排序或聚合,它具有性能成本,并且它仅适用于序数数据类型。

【讨论】:

  • 谢谢你让我走上了正确的道路。正确的查询如下: SELECT DISTINCT ON (anwendung.name) anwendung.name,autor.entwickler FROM autor left join anwendung on anwendung.name = autor.anwendung ;
  • 我现在看到它也可以使用 min() 函数
  • “DISTINCT ON”解决方案在某种意义上更有趣。但我认为“MIN”(或 MAX)是解决这个问题的更好的规范解决方案。
  • 我不确定min()max() 是否会比distinct 具有更大的性能成本,因为distinct 似乎无论如何都会对数据进行排序以查找重复项。如果你 explain 上面的查询,你会看到树的顶部有一个 Sort 节点。
【解决方案2】:

Craig 的回答和您在 cmets 中生成的查询具有相同的缺陷:表 anwendung 位于 LEFT JOIN 的右侧,这与您的明显意图相矛盾。你关心anwendung.name 并选择autor.entwickler 任意。我会回到更远的地方。

应该是:

SELECT DISTINCT ON (1) an.name, au.entwickler
FROM   anwendung an
LEFT   JOIN autor au ON an.name = au.anwendung;

DISTINCT ON (1) 只是DISTINCT ON (an.name) 的语法简写。此处允许位置引用。

如果一个应用 (anwendung) 有多个开发者 (entwickler),则会任意选择一个开发者。如果您想要“第一个”(根据您的语言环境按字母顺序排列),则必须添加 ORDER BY 子句:

SELECT DISTINCT ON (1) an.name, au.entwickler
FROM   anwendung an
LEFT   JOIN autor au ON an.name = au.anwendung
ORDER  BY 1, 2;

正如@mdahlman 暗示的那样,一种更规范的方式是:

SELECT an.name, min(au.entwickler) AS entwickler
FROM   autor au
LEFT   JOIN anwendung an ON an.name = au.anwendung
GROUP  BY an.name;

或者,更好的是,清理你的数据模型,正确实现anwendungautor之间的n:m关系,添加代理主键为anwendungautor是几乎不唯一,使用外键约束强制关系完整性并调整您的结果查询:

正确的方法

CREATE TABLE autor (
   autor_id serial PRIMARY KEY -- surrogate primary key
 , autor    text NOT NULL);

INSERT INTO autor  VALUES
   (1, 'mike')
 , (2, 'joe')
 , (3, 'jane')   -- worked on two apps
 , (4, 'susi');  -- has no part in any apps (yet)

CREATE TABLE anwendung (
   anwendung_id serial PRIMARY KEY -- surrogate primary key
 , anwendung    text  UNIQUE);     -- disallow duplicate names

INSERT INTO anwendung  VALUES
   (1, 'foo')    -- has 3 authors linked to it
 , (2, 'bar')
 , (3, 'shark')
 , (4, 'bait');  -- has no authors attached to it (yet).

CREATE TABLE autor_anwendung (  -- you might name this table "entwickler"
   autor_id     integer REFERENCES autor     ON UPDATE CASCADE ON DELETE CASCADE
 , anwendung_id integer REFERENCES anwendung ON UPDATE CASCADE ON DELETE CASCADE
 , PRIMARY KEY (autor_id, anwendung_id)
);

INSERT INTO autor_anwendung VALUES
 (1, 1)
,(2, 1)
,(3, 1)
,(2, 2)
,(3, 3);

此查询检索每个应用程序与一位关联作者(按字母顺序排列的第一个)的一行,如果没有,则为 NULL:

SELECT DISTINCT ON (1) an.anwendung, au.autor
FROM   anwendung an
LEFT   JOIN autor_anwendung au_au USING (anwendung_id)
LEFT   JOIN autor au USING (autor_id)
ORDER  BY 1, 2;

结果:

 name  | entwickler
-------+-----------------
 bait  |
 bar   | joe
 foo   | jane
 shark | jane

【讨论】:

  • DISTINC ON (1) 结构的有趣注释。以前从未见过。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-05-09
  • 2020-09-04
  • 1970-01-01
  • 2012-09-18
  • 1970-01-01
  • 2015-08-25
相关资源
最近更新 更多