Як я можу (чи можу я) ВИБІР ВІДПОВІДЬ у кількох стовпцях?


415

Мені потрібно отримати всі рядки з таблиці, де поєднуються 2 стовпчики, всі вони різні. Тому я хочу, щоб усі продажі, які не мають інших продажів, які відбулися в той же день, за тією ж ціною. Унікальні продажі на основі дня та ціни оновлюються до активного статусу.

Тому я думаю:

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

Але мій мозок боляче йде далі, ніж це.

Відповіді:


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
НЕ ВИБІРТЕ ДИСТАНТИ a, b, c Від t точно те саме, що і SELECT a, b, c ВІД t ГРУПИ 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 <> fiddle тут
Стара 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, тому перша пропозиція використовувати кортеж з IN () чеком не зіткнеться зі мною. Пропозиція, що не існує, зазвичай збирається з тим самим планом виконання на сервері sql, що і внутрішній приєднання.
Joel Coehoorn

2
Приємно. Пояснення значно збільшує значення відповіді. Я майже спокусився запустити кілька тестів з Oracle, щоб побачити, як плани порівнюються з Postgres і SQLServer.
Петро

2
@alairock: Де ти це взяв? Для Postgres - навпаки . Хоча підрахунок усіх рядків, count(*)це більш ефективно, ніж count(<expression>). Просто спробуйте. Postgres має більш швидку реалізацію для цього варіанту функції сукупності. Можливо, ви плутаєте Postgres з деякими іншими RDBMS?
Ервін Брандстеттер

6
@alairock: Я, мабуть, є співавтором цієї сторінки, і вона нічого подібного не говорить.
Ервін Брандстеттер

2
@ErwinBrandstetter, ти завжди наголошений на своїх відповідях по всій стеці. Ви протягом багатьох років допомагали майже немислимою кількістю способів. Щодо цього прикладу, я знав кілька різних способів вирішити свою проблему, але хотів побачити, що хтось перевіряв ефективність між можливостями. Дякую.
WebWanderer

24

Проблема з вашим запитом полягає в тому, що при використанні пункту GROUP BY (який, по суті, ви робите, використовуючи різні), ви можете використовувати лише стовпці, які ви групуєте, або агрегувати функції. Ви не можете використовувати ідентифікатор стовпця, оскільки можливі різні значення. У вашому випадку завжди є лише одне значення через пункт HAVING, але більшість RDBMS недостатньо розумні, щоб визнати це.

Це має працювати, однак (і не потрібно приєднання):

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" І "сортування" не є унікальним, результатом будуть ВСІ рядки.

використовуйте GROUP для вибору записів "GrondOfLucht" у порядку, заданому сортуванням

SELECT        GrondOfLucht
FROM            dbo.CorWijzeVanAanleg
GROUP BY GrondOfLucht, sortering
ORDER BY MIN(sortering)

Це в основному пояснює, що робить прийнята відповідь, але я б рекомендував не використовувати такі імена для прикладу (принаймні перекладати їх). PS: Я рекомендую завжди називати все англійською мовою у всіх проектах, навіть якщо ви голландці.
Кервін

0

Якщо ваша СУБД не підтримує розрізнення з декількома стовпцями, як це:

select distinct(col1, col2) from table

Мультиселекція в цілому може бути виконана безпечно наступним чином:

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

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.