Почему вы не можете смешивать значения Aggregate и Non-Aggregate в одном SELECT?
Я знаю, что если у вас есть одна агрегированная функция в инструкции SELECT, то все остальные значения в инструкции должны быть либо агрегатными функциями, либо перечислены в предложении GROUP BY. Я не понимаю, почему это дело.
Если я это сделаю:
SELECT Name, 'Jones' AS Surname FROM People
Я получаю:
NAME SURNAME
Dave Jones
Susan Jones
Amy Jones
Итак, СУБД взяла значение из каждой строки и добавила к нему единственное значение в наборе результатов. Это здорово. Но если это работает, почему я не могу:
SELECT Name, COUNT(Name) AS Surname FROM People
Похоже на ту же идею, взять значение из каждой строки и добавить одно значение. Но вместо:
NAME SURNAME
Dave 3
Susan 3
Amy 3
Я получаю:
Вы попытались выполнить запрос, который не включает указанное выражение "ContactName" как часть агрегатной функции.
Я знаю, что это не разрешено, но два обстоятельства кажутся настолько похожими, что я не понимаю, почему. Это сделать СУБД более простой в реализации? Если кто-нибудь может объяснить мне, почему это не работает, как я думаю, должно быть, я был бы очень благодарен.
Ответы
Ответ 1
Агрегаты не работают с полным результатом, они работают только с группой в результате.
Рассмотрим таблицу, содержащую:
Person Pet
-------- --------
Amy Cat
Amy Dog
Amy Canary
Dave Dog
Susan Snake
Susan Spider
Если вы используете запрос, который группируется в Person, он будет делить данные на следующие группы:
Amy:
Amy Cat
Amy Dog
Amy Canary
Dave:
Dave Dog
Susan:
Susan Snake
Susan Spider
Если вы используете aggreage, чтобы скопировать счетчик, он даст один результат для каждой группы:
Amy:
Amy Cat
Amy Dog
Amy Canary count(*) = 3
Dave:
Dave Dog count(*) = 1
Susan:
Susan Snake
Susan Spider count(*) = 2
Итак, запрос select Person, count(*) from People group by Person
дает вам одну запись для каждой группы:
Amy 3
Dave 1
Susan 2
Если вы попытаетесь также получить поле Pet в результате, это не сработает, потому что в каждой группе может быть несколько значений для этого поля.
(Некоторые базы данных, такие как MySQL, все равно позволяют это и просто возвращают любое случайное значение из группы, и ваша ответственность - знать, является ли результат разумным или нет.)
Если вы используете агрегат, но не указываете какую-либо группировку, запрос все равно будет сгруппирован, а весь результат будет одной группой. Таким образом, запрос select count(*) from Person
будет создавать одну группу, содержащую все записи, и агрегат может подсчитывать записи в этой группе. Результат содержит одну строку из каждой группы, а так как есть только одна группа, в результате будет одна строка.
Ответ 2
Подумайте об этом так: когда вы вызываете COUNT без группировки, он "сворачивает" таблицу в одну группу, что делает невозможным доступ к отдельным элементам внутри группы в предложении select.
Вы можете получить результат с помощью подзапроса или перекрестного соединения:
SELECT p1.Name, COUNT(p2.Name) AS Surname FROM People p1 CROSS JOIN People p2 GROUP BY p1.Name
SELECT Name, (SELECT COUNT(Name) FROM People) AS Surname FROM People
Ответ 3
Как объяснили другие, когда у вас есть GROUP BY
или вы используете агрегированную функцию типа COUNT()
в списке SELECT
, вы делаете группировку строк и, следовательно, сворачиваете соответствующие строки в одну для каждой группы.
Когда вы используете только агрегированные функции в списке SELECT
, без GROUP BY
, подумайте об этом, поскольку у вас есть GROUP BY 1
, поэтому все строки сгруппированы, свернуты в один. Итак, если у вас есть сто строк, база данных не может показать вам имя, так как их сотни.
Однако для РСУБД, которые имеют функции "окна", то, что вы хотите, возможно. Например. используйте агрегатные функции без GROUP BY
.
Пример для SQL-Server, где подсчитываются все строки (имена) в таблице:
SELECT Name
, COUNT(*) OVER() AS cnt
FROM People
Как это работает?
-
Он показывает Name
как
COUNT(*) OVER() AS cnt
не
существуют и
-
Он показывает COUNT(*)
, как если бы он делал полную группировку
таблица.
Другой пример. Если в таблице есть поле Surname
, вы можете иметь что-то подобное, чтобы отображать все строки, сгруппированные по фамилии, и подсчет количества людей, имеющих одну и ту же фамилию:
SELECT Name
, Surname
, COUNT(*) OVER(PARTITION BY Surname) AS cnt
FROM People
Ответ 4
Ваш запрос неявно запрашивает разные типы строк в вашем наборе результатов, и это недопустимо. Все возвращаемые строки должны быть одного типа и иметь одинаковые столбцы.
'SELECT name, surname' хочет вернуть строку для каждой строки в таблице.
'SELECT COUNT (*)' хочет вернуть одну строку, объединяющую результаты всех строк в таблице.
Я думаю, вы правы, что в этом случае база данных могла бы просто выполнять оба запроса, а затем копировать результат "SELECT COUNT (*)" в каждый результат. Одна из причин, почему вы не делаете этого, это то, что это будет хитом производительности в режиме невидимости: вы эффективно выполняете дополнительное самостоятельное объединение, не объявляя его нигде.
Другие ответы объяснили, как написать рабочую версию этого запроса, поэтому я не буду вдаваться в это.
Ответ 5
Агрегатная функция и предложение group by не являются отдельными вещами, они являются частями того же самого объекта, которые появляются в разных местах запроса. Если вы хотите агрегировать в столбце, вы должны сказать, какую функцию использовать для агрегации; если вы хотите иметь функцию агрегации, она должна применяться к некоторому столбцу.
Ответ 6
Функция агрегата принимает значения из нескольких строк с определенным условием и объединяет их в одно значение. Это условие определяется GROUP BY
в вашем заявлении. Таким образом, вы не можете использовать агрегатную функцию без GROUP BY
С
SELECT Name, 'Jones' AS Surname FROM People
вы просто выбираете дополнительный столбец с фиксированным значением... но с
SELECT Name, COUNT(Name) AS Surname FROM People GROUP BY Name
вы сообщаете СУБД, чтобы выбрать Имена, помните, как часто каждое Имя происходило в таблице и сворачивало их в одну строку. Поэтому, если вы опускаете GROUP BY
, СУБД не может сказать, как свернуть записи