Устранение повторяющихся строк в инструкции SELECT PostgreSQL

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

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

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

Ответы

Ответ 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 - у него есть стоимость исполнения из-за необходимости дополнительной сортировки или агрегации, и он работает только для порядковых типов данных.

Ответ 2

Ответ Крэйга и ваш итоговый запрос в комментариях имеют один и тот же недостаток: Таблица 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;

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

Правильный способ

Демо использует временные таблицы, поэтому вы можете легко попробовать это дома:

CREATE TEMP 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 three apps
,(4, 'susi');  -- has no part in any apps (yet)


CREATE TEMP TABLE anwendung (
 anwendung_id serial PRIMARY KEY -- surrogate primary key
,anwendung text);

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 TEMP TABLE autor_anwendung (  -- you might name this table "entwickler"
 autor_id integer
          REFERENCES autor (autor_id) ON UPDATE CASCADE ON DELETE CASCADE
,anwendung_id integer
  REFERENCES anwendung (anwendung_id) ON UPDATE CASCADE ON DELETE CASCADE
,PRIMARY KEY (autor_id, anwendung_id)
);

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

Запрос получает все имена приложений со всеми ассоциированными авторами, собранными в строке, разделенной запятыми:

SELECT an.name, string_agg(au.autor, ', ') AS entwickler
FROM   anwendung an
LEFT   JOIN autor_anwendung USING (anwendung_id)
LEFT   JOIN autor au USING (autor_id)
GROUP  BY 1
ORDER  BY 1;

Результат:

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

string_agg() требует PostgreSQL 9.0+. Для более старых версий замените:

array_to_string(array_agg(au.autor), ', ')