Как мне (или я могу) ВЫБРАТЬ DISTINCT по нескольким столбцам?

415

Мне нужно извлечь все строки из таблицы, где 2 столбца вместе все разные. Поэтому я хочу, чтобы все продажи, в которых не было других продаж, произошли в тот же день по той же цене. Продажи, которые являются уникальными в зависимости от дня и цены, будут обновлены до активного статуса.

Вот я и думаю:

UPDATE sales
SET status = 'ACTIVE'
WHERE id IN (SELECT DISTINCT (saleprice, saledate), id, count(id)
             FROM sales
             HAVING count = 1)

Но мой мозг болит дальше.

sheats
источник

Ответы:

436
SELECT DISTINCT a,b,c FROM t

это примерно эквивалентно:

SELECT a,b,c FROM t GROUP BY a,b,c

Хорошей идеей будет привыкнуть к синтаксису GROUP BY, так как он более мощный.

По вашему запросу я бы сделал это так:

UPDATE sales
SET status='ACTIVE'
WHERE id IN
(
    SELECT id
    FROM sales S
    INNER JOIN
    (
        SELECT saleprice, saledate
        FROM sales
        GROUP BY saleprice, saledate
        HAVING COUNT(*) = 1 
    ) T
    ON S.saleprice=T.saleprice AND s.saledate=T.saledate
 )
Джоэл Коухорн
источник
117
Этот запрос, хотя и правильный и принятый в течение года, крайне неэффективен и неоправдан. Не используйте это. Я предоставил альтернативу и некоторое объяснение в другом ответе.
Эрвин Брандштеттер,
1
Разве SELECT DISTINCT a, b, c ОТ точно не то же самое, что SELECT a, b, c ОТ ГРУППЫ ПО a, b, c?
famargar
8
@famargar для простого случая, но они семантически имеют разные значения, и они отличаются с точки зрения того, что вы можете сделать для шага при создании запроса большего размера. Кроме того, люди на технических форумах часто могут быть крайне педантичными в отношении вещей, и я считаю, что в такие ситуации полезно добавлять слова-ласки в мои сообщения.
Джоэл Коухорн
344

Если вы соберете ответы, очистите и улучшите их, вы получите этот превосходный запрос:

UPDATE sales
SET    status = 'ACTIVE'
WHERE  (saleprice, saledate) IN (
    SELECT saleprice, saledate
    FROM   sales
    GROUP  BY saleprice, saledate
    HAVING count(*) = 1 
    );

Который намного быстрее, чем любой из них. Снижает производительность принятого в настоящее время ответа в 10-15 раз (в моих тестах на PostgreSQL 8.4 и 9.1).

Но это все еще далеко от оптимального. Используйте NOT EXISTS(анти) полусоединение для еще лучшей производительности. EXISTSявляется стандартным SQL, существует вечно (по крайней мере, с PostgreSQL 7.2, задолго до того, как был задан этот вопрос) и идеально соответствует представленным требованиям:

UPDATE sales s
SET    status = 'ACTIVE'
WHERE  NOT EXISTS (
   SELECT FROM sales s1                     -- SELECT list can be empty for EXISTS
   WHERE  s.saleprice = s1.saleprice
   AND    s.saledate  = s1.saledate
   AND    s.id <> s1.id                     -- except for row itself
   )
AND    s.status IS DISTINCT FROM 'ACTIVE';  -- avoid empty updates. see below

db <> скрипеть здесь
Old SQL Fiddle

Уникальный ключ для идентификации строки

Если у вас нет первичного или уникального ключа для таблицы ( idв примере), вы можете заменить системный столбец ctidдля целей этого запроса (но не для некоторых других целей):

   AND    s1.ctid <> s.ctid

Каждая таблица должна иметь первичный ключ. Добавьте еще один, если у вас его еще не было. Я предлагаю serialили IDENTITYстолбец в Postgres 10+.

Связанные с:

Как это быстрее?

Подзапрос в EXISTSанти-полусоединении может прекратить оценку, как только будет найден первый дублик (нет смысла смотреть дальше). Для базовой таблицы с небольшим количеством дубликатов это немного более эффективно. С большим количеством дубликатов это становится намного более эффективным.

Исключить пустые обновления

Для строк, которые уже имеют status = 'ACTIVE'это обновление, ничего не изменится, но все равно будет вставлена ​​новая версия строки за полную стоимость (применяются незначительные исключения). Обычно вы этого не хотите. Добавьте еще одно WHEREусловие, как показано выше, чтобы избежать этого и сделать его еще быстрее:

Если statusопределено NOT NULL, вы можете упростить до:

AND status <> 'ACTIVE';

Тип данных столбца должен поддерживать <>оператор. Некоторые типы, как jsonнет. Видеть:

Тонкая разница в обработке NULL

Этот запрос (в отличие от принятого в настоящее время ответа Джоэла ) не рассматривает значения NULL как равные. Следующие две строки для (saleprice, saledate)будут квалифицироваться как «отличные» (хотя выглядят идентично человеческому глазу):

(123, NULL)
(123, NULL)

Также передает уникальный индекс и почти где-либо еще, поскольку значения NULL не сравниваются равными в соответствии со стандартом SQL. Видеть:

Ото, GROUP BY, DISTINCTили DISTINCT ON ()значения NULL , как лечить равны. Используйте соответствующий стиль запроса в зависимости от того, чего вы хотите достичь. Вы можете по-прежнему использовать этот более быстрый запрос IS NOT DISTINCT FROMвместо =любого или всех сравнений, чтобы сделать сравнение NULL равным. Больше:

Если все сравниваемые столбцы определены NOT NULL, нет места для разногласий.

Эрвин Брандштеттер
источник
16
Хороший ответ. Я парень с SQL Server, поэтому первое предложение об использовании кортежа с проверкой IN () мне не пришло. Предложение «Не существует» обычно приводит к тому же плану выполнения на сервере sql, что и внутреннее соединение.
Джоэл Коухорн
2
Ницца. Объяснение значительно увеличивает ценность ответа. Я почти испытываю желание провести некоторые тесты с Oracle, чтобы увидеть, как планы сравниваются с Postgres и SQLServer.
Питер
2
@alairock: Где ты это взял? Для Postgres, наоборот . При подсчете всех строк, count(*)является более эффективным , чем count(<expression>). Просто попробуйте. Postgres имеет более быструю реализацию для этого варианта агрегатной функции. Может быть, вы путаете Postgres с какой-то другой RDBMS?
Эрвин Брандштеттер,
6
@alairock: я являюсь соавтором этой страницы, и она не говорит ничего подобного.
Эрвин Брандштеттер
2
@ErwinBrandstetter, вы всегда в курсе ваших ответов по стеку. Вы помогали в течение многих лет почти невообразимым количеством способов. Что касается этого примера, я знал несколько разных способов решения моей проблемы, но я хотел видеть, что кто-то проверил эффективность между возможностями. Спасибо.
WebWanderer
24

Проблема с вашим запросом состоит в том, что при использовании предложения GROUP BY (которое вы по сути делаете с помощью различных) вы можете использовать только столбцы, которые вы группируете или объединяете функции. Вы не можете использовать идентификатор столбца, потому что есть потенциально разные значения. В вашем случае всегда есть только одно значение из-за предложения HAVING, но большинство СУБД недостаточно умны, чтобы это распознать.

Это должно работать, однако (и не нуждается в соединении):

UPDATE sales
SET status='ACTIVE'
WHERE id IN (
  SELECT MIN(id) FROM sales
  GROUP BY saleprice, saledate
  HAVING COUNT(id) = 1
)

Вы также можете использовать MAX или AVG вместо MIN, важно использовать только функцию, которая возвращает значение столбца, если есть только одна совпадающая строка.

Кристиан Берг
источник
1

Я хочу выбрать отдельные значения из одного столбца «GrondOfLucht», но они должны быть отсортированы в порядке, указанном в столбце «сортировка». Я не могу получить отдельные значения только одного столбца, используя

Select distinct GrondOfLucht,sortering
from CorWijzeVanAanleg
order by sortering

Это также даст столбцу «сортировка», и поскольку «GrondOfLucht» И «сортировка» не уникальны, результатом будут ВСЕ строки.

используйте ГРУППУ, чтобы выбрать записи 'GrondOfLucht' в порядке, заданном сортировкой

SELECT        GrondOfLucht
FROM            dbo.CorWijzeVanAanleg
GROUP BY GrondOfLucht, sortering
ORDER BY MIN(sortering)
Франс Айлинг
источник
Это в основном объясняет, что делает принятый ответ, но я бы рекомендовал не использовать такие имена в качестве примера (по крайней мере, перевести их). PS: Я рекомендую всегда называть все на английском во всех проектах, даже если вы голландец.
Кервин Снейдерс,
0

Если ваша СУБД не поддерживает различные с несколькими столбцами, как это:

select distinct(col1, col2) from table

Multi select в целом можно выполнить безопасно следующим образом:

select distinct * from (select col1, col2 from table ) as x

Поскольку это может работать на большинстве СУБД, и ожидается, что это будет быстрее, чем группировка по решению, поскольку вы избегаете функциональности группировки.

Абдулхафет Сартави
источник