должен присутствовать в предложении 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

Как я могу решить эту проблему?

Примечание. Эта таблица представляет собой ПРОСМОТР, созданный в результате предыдущей операции.



Я не понимаю Почему 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
Благодаря синтаксису является основным, но вы должны сравнить значения mx и avg при присоединении
RandomGuy

1
Да, ваш синтаксис правильный и устраняет дубликаты, однако вам нужно m.avg = t.mx в конце (после того, как вы написали JOING), чтобы получить
ожидаемые

1
@Sebas Это можно сделать без присоединения MAX(см. Ответ @ypercube, в моем ответе есть и другое решение), но не так, как вы это делаете. Проверьте ожидаемый результат.
zero323

1
@Sebas Ваше решение только добавляет столбец (MAX avgper cname), но не ограничивает строки результата (как этого хочет OP). Смотреть фактические результаты следует в абзаце вопроса.
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
Это не сработает так, как ожидается, если вы
захотите

@amenzhinsky Что ты имеешь в виду? Если кто-то хочет, чтобы набор результатов был отсортирован в другом порядке, чем BY cname?
ypercubeᵀᴹ

@ypercube, на самом деле psql сначала сортирует, а затем применяет DISTINCT. В случае сортировки по avg мы получим разные результаты для каждой строки минимальных и максимальных значений в зависимости от направления сортировки
amenzhinsky

3
Конечно. Если вы не выполните мой запрос, вы получите другие результаты! Это не то же самое, что «это не будет работать так, как ожидалось» ...
ypercubeᵀᴹ

1
@ Batfan Thnx. Обратите внимание, что, хотя это довольно круто, компактно и легко пишется, это не всегда самый эффективный способ для такого рода запросов.
ypercubeᵀᴹ

27

Проблема с указанием не сгруппированных и неагрегированных полей в group byвыборках заключается в том, что движок не может знать, какое поле записи он должен вернуть в этом случае. Это первое? Это последний? Обычно не существует записей, которые естественно соответствуют агрегированному результату ( minи maxявляются исключениями).

Тем не менее, есть обходной путь: сделайте также обязательные поля агрегированными. В posgres это должно работать:

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

Обратите внимание, что это создает массив всех wnames, упорядоченных по 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;

Заметка

Любой из них сохранит несколько максимальных значений на группу. Если вам нужна только одна запись на группу, даже если существует более одной записи с avg, равным 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, и обнаружил, что изменение порядка операторов whichand 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.