Устранение повторяющихся строк в инструкции 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), ', ')