Устранить повторяющиеся строки в операторе PostgreSQL SELECT - PullRequest
18 голосов
/ 04 декабря 2011

Это мой запрос:

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 left join anwendung на ...

Я полностью понимаю ошибку и предполагаю, что реализация mysql менее соответствует SQL, чем реализация postgres.Но как мне получить желаемый результат?

Ответы [ 2 ]

34 голосов
/ 04 декабря 2011

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;

(синтаксис исправлен на основе последующего комментария)

Это немного похоже на псевдофункцию ANY_VALUE(...) в MySQL 5.7 для group by, но наоборот - она ​​говорит, что значения в предложении distinct on должны быть уникальными, и любое значение является приемлемым для столбцов. не указано.

Если нет ORDER BY, нет гарантии, какие значения выбраны. У вас обычно должно быть ORDER BY для предсказуемости.

Также было отмечено, что использование агрегата, подобного min() или max(), будет работать. Хотя это действительно так - и приведет к надежным и предсказуемым результатам, в отличие от использования DISTINCT ON или неоднозначного GROUP BY - оно имеет затраты на производительность из-за необходимости дополнительной сортировки или агрегирования и работает только для порядковых типов данных.

12 голосов
/ 05 декабря 2011

Ответ Крэйга и ваш результирующий запрос в комментариях имеют один и тот же недостаток: таблица 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, если хотите, чтобы "first" (в алфавитном порядке в соответствии с вашей локалью):

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;

Или, что еще лучше, очистите модель данных, правильно внедрите отношение n: m между anwendung и autor, добавьте суррогатные первичные ключи как anwendung и autor вряд ли уникальны, обеспечивают целостность отношений с ограничениями внешнего ключа и адаптируют ваш результирующий запрос:

Правильный путь

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

Этот запрос извлекает одну строку на приложение с одним связанным автором (1-й в алфавитном порядке) или 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
...