Почему вы не можете смешивать агрегированные значения и неагрегированные значения в одном SELECT? - PullRequest
20 голосов
/ 07 мая 2011

Я знаю, что если у вас есть одна агрегатная функция в операторе 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» как часть агрегатной функции.

Я знаю, что это не разрешено, но эти два обстоятельства кажутся настолько похожими, что я не понимаю, почему. Это сделать СУБД проще в реализации? Если бы кто-нибудь мог объяснить мне, почему это не работает, как я думаю, я должен был бы быть очень благодарен.

Ответы [ 6 ]

17 голосов
/ 07 мая 2011

Агрегаты не работают с полным результатом, они работают только с группой в результате.

Рассмотрим таблицу, содержащую:

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

8 голосов
/ 07 мая 2011

Подумайте об этом так: когда вы вызываете 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
6 голосов
/ 07 мая 2011

Как объяснили другие, когда у вас есть GROUP BY или вы используете агрегатную функцию, например COUNT() в списке SELECT, вы группируете строки и, следовательно, объединяете соответствующие строки в одну для каждой группы.

Когда вы используете только агрегатные функции в списке SELECT, без GROUP BY, думайте о нем как о GROUP BY 1, поэтому все строки сгруппированы, свернуты в одну. Так что, если у вас есть сто строк, база данных не может действительно показать вам имя, поскольку их сотни.

Однако для РСУБД, которые имеют функции «оконного управления», то, что вы хотите, выполнимо. Например. использовать агрегатные функции без GROUP BY.

Пример для SQL-сервера, где подсчитываются все строки (имена) в таблице:

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
2 голосов
/ 07 мая 2011

Ваш запрос неявно запрашивает разные типы строк в вашем наборе результатов, что недопустимо.Все возвращаемые строки должны быть одного типа и иметь столбцы одинакового типа.

«SELECT name, фамилия» хочет вернуть строку для каждой строки в таблице.

'SELECT COUNT(*) 'хочет вернуть одну строку, объединяющую результаты всех строк в таблице.

Я думаю, вы правы, что в этом случае база данных может просто выполнить оба запроса, а затем скопировать результат«SELECT COUNT (*)» в каждом результате.Одна из причин, по которой это не будет сделано, заключается в том, что это будет невидимым ударом по производительности: вы фактически будете выполнять дополнительное самостоятельное объединение, нигде не объявив его.

Другие ответы объясняли, как написать рабочую версию этогозапрос, поэтому я не буду вдаваться в подробности.

1 голос
/ 07 мая 2011

Агрегатная функция принимает значения из нескольких строк с определенным условием и объединяет их в одно значение. Это условие определяется GROUP BY в вашем утверждении. Таким образом, вы не можете использовать агрегатную функцию без GROUP BY

С

SELECT Name, 'Jones' AS Surname FROM People  

вы просто выбираете дополнительный столбец с фиксированным значением ... но с

SELECT Name, COUNT(Name) AS Surname FROM People GROUP BY Name

вы говорите СУБД выбирать имена, помните, как часто каждое имя встречается в таблице, и сворачиваете их в одну строку. Так что, если вы опустите GROUP BY, СУБД не может сказать, как свернуть записи

1 голос
/ 07 мая 2011

Агрегатная функция и предложение group by - это не отдельные вещи, а части одного и того же, которые появляются в разных местах запроса.Если вы хотите агрегировать по столбцу, вы должны указать, какую функцию использовать для агрегирования;если вы хотите иметь функцию агрегирования, она должна быть применена к некоторому столбцу.

...