消除PostgreSQL SELECT语句中的重复行

时间:2011-12-04 03:40:15

标签: mysql sql postgresql select duplicates

这是我的疑问:

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给了我这个错误:

  

错误:列“autor.entwickler”必须出现在GROUP BY子句中   或者在聚合函数中使用LINE 1:SELECT autor.entwickler   从autor左边加入anwendung ......

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

2 个答案:

答案 0 :(得分:34)

PostgreSQL当前不允许含糊不清的GROUP BY语句,其结果取决于扫描表的顺序,使用的计划等。标准表示它应该如何工作AFAIK,但有些数据库(如5.7之前的MySQL版本允许更宽松的查询,只选择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不同 - 由于需要额外的排序或聚合,它具有性能成本,并且它仅适用于序数数据类型。

答案 1 :(得分:12)

Craig的回答和评论中的结果查询共享相同的缺陷:表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)的语法简写。这里允许进行位置参考。

如果某个应用(entwickler)有多个开发人员(anwendung),则会选择任意一个开发人员。如果你想要“第一个”(按字母顺序根据你的语言环境),你必须添加一个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关系,将代理主键添加为{{1} }和anwendung几乎不是唯一的,使用外键约束强制执行关系完整性并调整生成的查询:

正确的方法

autor

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

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

结果:

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;