Должен появиться в предложении GROUP BY или использоваться в агрегатной функции
У меня есть таблица, которая выглядит как этот вызывающий 'makerar'
cname | wmname | avg
--------+-------------+------------------------
canada | zoro | 2.0000000000000000
spain | luffy | 1.00000000000000000000
spain | usopp | 5.0000000000000000
И я хочу выбрать максимальное avg для каждого cname.
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
Как я могу решить эту проблему?
Примечание. Эта таблица представляет собой ПРОСМОТР, созданный из предыдущей операции.
Ответы
Ответ 1
Да, это общая проблема агрегации. Перед 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
[*]: Интересно, что, несмотря на то, что spec-тип позволяет выбирать негрупповые поля, основным двигателям, похоже, это не очень нравится. Oracle и SQLServer просто не позволяют этого вообще. Mysql используется для его разрешения по умолчанию, но теперь, начиная с 5.7, администратор должен включить эту опцию (ONLY_FULL_GROUP_BY
) вручную в конфигурации сервера, чтобы эта функция поддерживалась...
Ответ 2
В Postgres вы также можете использовать специальный синтаксис DISTINCT ON (expression)
:
SELECT DISTINCT ON (cname)
cname, wmname, avg
FROM
makerar
ORDER BY
cname, avg DESC ;
Ответ 3
Проблема с указанием несгруппированных и неагрегированных полей в 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).
Ответ 4
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.
Ответ 5
Для меня это не "общая проблема агрегации", а просто неправильный запрос 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 такой же "счет".
Ответ 6
Недавно я столкнулся с этой проблемой при попытке подсчета с помощью 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
Ответ 7
Это тоже работает
SELECT *
FROM makerar m1
WHERE m1.avg = (SELECT MAX(avg)
FROM makerar m2
WHERE m1.cname = m2.cname
)