повинні з'являтися в пункті GROUP BY або використовуватися в сукупній функції


276

У мене є таблиця, яка виглядає як "makerar", що телефонує

 cname  | wmname |          avg           
--------+-------------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | luffy  | 1.00000000000000000000
 spain  | usopp  |     5.0000000000000000

І я хочу вибрати максимальну середню для кожного імені.

SELECT cname, wmname, MAX(avg)  FROM makerar GROUP BY cname;

але я отримаю помилку,

ERROR:  column "makerar.wmname" must appear in the GROUP BY clause or be used in an   aggregate function 
LINE 1: SELECT cname, wmname, MAX(avg)  FROM makerar GROUP BY cname;

тому я роблю це

SELECT cname, wmname, MAX(avg)  FROM makerar GROUP BY cname, wmname;

однак це не дасть намічених результатів, і наведений нижче неправильний результат.

 cname  | wmname |          max           
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | luffy  | 1.00000000000000000000
 spain  | usopp  |     5.0000000000000000

Фактичні результати повинні бути

 cname  | wmname |          max           
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | usopp  |     5.0000000000000000

Як я можу вирішити цю проблему?

Примітка. Ця таблиця - VIEW, створена за допомогою попередньої операції.



Я не розумію. Чому wmname="usopp"очікується, а не для прикладу wmname="luffy"?
AndreKR

Відповіді:


226

Так, це поширена проблема агрегації. Перед SQL3 (1999) вибрані поля повинні з'являтися в GROUP BYпункті [*].

Щоб вирішити цю проблему, ви повинні обчислити сукупність у підзапиті, а потім приєднати його до себе, щоб отримати додаткові стовпці, які потрібно було б показати:

SELECT m.cname, m.wmname, t.mx
FROM (
    SELECT cname, MAX(avg) AS mx
    FROM makerar
    GROUP BY cname
    ) t JOIN makerar m ON m.cname = t.cname AND t.mx = m.avg
;

 cname  | wmname |          mx           
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | usopp  |     5.0000000000000000

Але ви також можете використовувати віконні функції, що виглядає простіше:

SELECT cname, wmname, MAX(avg) OVER (PARTITION BY cname) AS mx
FROM makerar
;

Єдине, що при цьому методі - це те, що він покаже всі записи (віконні функції не групуються). Але він відображатиме правильний (тобто розміщений на cnameрівні) MAXдля країни в кожному рядку, тож саме від вас:

 cname  | wmname |          mx           
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | luffy  |     5.0000000000000000
 spain  | usopp  |     5.0000000000000000

Рішення, мабуть, менш елегантне, щоб показати єдині (cname, wmname)кортежі, що відповідають максимальному значенню, це:

SELECT DISTINCT /* distinct here matters, because maybe there are various tuples for the same max value */
    m.cname, m.wmname, t.avg AS mx
FROM (
    SELECT cname, wmname, avg, ROW_NUMBER() OVER (PARTITION BY avg DESC) AS rn 
    FROM makerar
) t JOIN makerar m ON m.cname = t.cname AND m.wmname = t.wmname AND t.rn = 1
;


 cname  | wmname |          mx           
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | usopp  |     5.0000000000000000

[*]: Цікаво, що навіть незважаючи на те, що специфікація дозволяє вибирати негруповані поля, основні двигуни, здається, не дуже подобаються. Oracle і SQLServer просто не дозволяють цього зробити. Mysql використовував для дозволу це за замовчуванням, але тепер з 5.7 адміністратору потрібно включити цю опцію ( ONLY_FULL_GROUP_BY) вручну в налаштуваннях сервера, щоб ця функція підтримувалася ...


1
Синтаксис подяки є corect, але, ви повинні порівняти значення mx та avg під час приєднання
RandomGuy

1
Так, ваш синтаксис правильний і виключає дублікати, однак вам потрібен m.avg = t.mx врешті-решт (після того, як ви написали JOING), щоб отримати
бажані

1
@Sebas Це можна зробити без приєднання MAX(див. Відповідь @ypercube, у моїй відповіді є також інше рішення), але не так, як ви це зробите. Перевірте очікуваний вихід.
zero323

1
@Sebas Ваше рішення додає лише стовпець (MAX avgper cname), але він не обмежує рядки результату (як хоче ОП). Дивіться Фактичні результати повинні бути абзацом у питанні.
ypercubeᵀᴹ

1
Включення вимикання ONLY_FULL_GROUP_BY в MySQL 5.7 НЕ активує шлях в SQL стандарт визначає , коли стовпчики можуть бути виключені з group by(або робить MySQL поводяться як Postgres). Він просто повертається до старої поведінки, коли MySQL натомість повертає випадкові (= "невизначені") результати.
a_horse_with_no_name

126

У Postgres також можна використовувати спеціальний DISTINCT ON (expression)синтаксис:

SELECT DISTINCT ON (cname) 
    cname, wmname, avg
FROM 
    makerar 
ORDER BY 
    cname, avg DESC ;

5
Це не спрацює, як очікується, якщо хочеться сортувати стовпчики на зразок avg
amenzhinsky

@amenzhinsky Що ти маєш на увазі? Якщо ви хочете, щоб набір результатів був відсортований в іншому порядку, ніж BY cname?
ypercubeᵀᴹ

@ypercube, насправді psql спочатку сортує, а потім застосовує DISTINCT. У разі сортування за середньою категорією ми отримаємо різні результати для кожного ряду мінімальних та максимальних значень залежно від напрямку сортування
аменжинський

3
Звичайно. Якщо ви не запустите запит, який я опублікував, ви отримаєте різні результати! Це не те саме, що "це не буде працювати, як очікувалося" ...
ypercubeᵀᴹ

1
@Batfan thnx. Зауважте, що, хоча це досить круто, компактно і легко писати, це не часто найефективніший спосіб для такого роду запитів.
ypercubeᵀᴹ

27

Проблема із зазначенням негрупованих та несукупних полів у group byселектах полягає в тому, що двигун не може знати, яке поле запису він повинен повернути в цьому випадку. Це перше? Це останнє? Зазвичай не існує запису, який би природно відповідав сукупному результату ( minі maxє винятками).

Однак є вирішення: також зробіть необхідне поле зведеним. У позиграх це має працювати:

SELECT cname, (array_agg(wmname ORDER BY avg DESC))[1], MAX(avg)
FROM makerar GROUP BY cname;

Зауважте, що це створює масив усіх імен, упорядкованих avg, і повертає перший елемент (масиви в postgres засновані на 1).


Гарна думка. Хоча здається можливим, що БД може виконати зовнішнє з'єднання, щоб зв’язати непоєднані поля з кожного рядка до агрегованого результату, до якого вніс рядок. Мені часто цікаво, чому вони не мають для цього варіанту. Хоча я міг просто бути невідомим цього варіанту :)
Бен Сіммонс

16
SELECT t1.cname, t1.wmname, t2.max
FROM makerar t1 JOIN (
    SELECT cname, MAX(avg) max
    FROM makerar
    GROUP BY cname ) t2
ON t1.cname = t2.cname AND t1.avg = t2.max;

Використання rank() функції вікна :

SELECT cname, wmname, avg
FROM (
    SELECT cname, wmname, avg, rank() 
    OVER (PARTITION BY cname ORDER BY avg DESC)
    FROM makerar) t
WHERE rank = 1;

Примітка

Будь-яке з них збереже кілька максимальних значень на групу. Якщо ви хочете лише один запис на групу, навіть якщо є більше одного запису із середньою середньою величиною, яка дорівнює max, слід перевірити відповідь @ ypercube.


16

Для мене мова не йде про "загальну проблему агрегації", а просто про неправильний запит SQL. Єдина правильна відповідь для "вибрати максимальну середню для кожного імені ..." є

SELECT cname, MAX(avg) FROM makerar GROUP BY cname;

Результатом буде:

 cname  |      MAX(avg)
--------+---------------------
 canada | 2.0000000000000000
 spain  | 5.0000000000000000

Цей результат загалом відповідає на питання "Який найкращий результат для кожної групи?" . Ми бачимо, що найкращий результат для Іспанії - 5, а для Канади - найкращий результат 2. Це правда, і помилок немає. Якщо нам також потрібно відобразити wmname , нам доведеться відповісти на запитання: "Яке ПРАВИЛО потрібно вибрати wmname з отриманого набору?" Давайте трохи змінимо вхідні дані, щоб уточнити помилку:

  cname | wmname |        avg           
--------+--------+-----------------------
 spain  | zoro   |  1.0000000000000000
 spain  | luffy  |  5.0000000000000000
 spain  | usopp  |  5.0000000000000000

Який результат ви очікуєте при запуску цього запиту SELECT cname, wmname, MAX(avg) FROM makerar GROUP BY cname;:? Це повинно бути spain+luffyчи spain+usopp? Чому? У запиті не визначено, як вибрати "краще" wmname, якщо кілька підходять, тому результат також не визначається. Ось чому інтерпретатор SQL повертає помилку - запит невірний.

Іншим словом, немає правильної відповіді на питання "Хто найкращий у spainгрупі?" . Луффі не кращий за usopp, тому що usopp має однаковий "бал".


Це рішення спрацювало і для мене. У мене виникли проблеми із запитом, оскільки мій ORM також включав пов'язаний первинний ключ, в результаті чого з'явився такий неправильний запит:, SELECT cname, id, MAX(avg) FROM makerar GROUP BY cname;який дав цю помилкову помилку.
Роберто

1

Це, здається, працює також

SELECT *
FROM makerar m1
WHERE m1.avg = (SELECT MAX(avg)
                FROM makerar m2
                WHERE m1.cname = m2.cname
               )

0

Нещодавно я зіткнувся з цією проблемою, намагаючись рахувати використання case when, і виявив, що зміна порядку whichі countоператорів усуває проблему:

SELECT date(dateday) as pick_day,
COUNT(CASE WHEN (apples = 'TRUE' OR oranges 'TRUE') THEN fruit END)  AS fruit_counter

FROM pickings

GROUP BY 1

Замість використання - в останньому, де у мене виникли помилки, що яблука та апельсини повинні з’являтися в сукупних функціях

CASE WHEN ((apples = 'TRUE' OR oranges 'TRUE') THEN COUNT(*) END) END AS fruit_counter

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