Все столбцы в списке SELECT должны появляться в предложении GROUP BY

Мой лектор заявил:

Все имена столбцов в списке SELECT должны отображаться в предложении GROUP BY, если только имя не используется только в агрегатной функции

Я просто хочу подтвердить это, так как не могу представить логического объяснения, почему это должно быть правдой...

Ответы

Ответ 1

Представьте себе следующее:

    A    B    C
   Cat   10   False
   Dog   25   True
   Dog   20   False
   Cat   5    False

Если вы выбираете A, B и Group By Only A - каков будет ваш выход? У вас будет только две строки (или кортежи), потому что у вас есть два значения для A, но как он отображает B?

Если вы группируете A, B, вы получите четыре строки, без проблем. Если вы группируете A и выполняете функцию на B, как SUM (B), вы снова получаете две строки:

    Cat    15
    Dog    45

Но если вы выберите A, B и только группу по A - он не знает, что делать. Честно говоря, я считаю, что есть некоторые базы данных, которые будут выбирать случайное значение для B в этом случае, и я считаю, что есть некоторые, которые дадут вам сообщение об ошибке.

Ответ 2

Это исторически верно. Опускание неагрегированных столбцов приводит к неопределенному поведению. SQL нацелен на полностью определенное поведение.

Но стандарты SQL недавно изменились, чтобы вы могли исключить из столбцов GROUP BY, которые функционально зависят от столбцов, входящих в GROUP BY. PostgreSQL следует за более поздними стандартами SQL. (Это не единственный). Поведение все еще полностью определено.

create table a (
  a_id integer primary key,
  xfr_date date not null
);

create table b (
  a_id integer not null references a (a_id),
  recd_date date not null,
  units_recd integer not null 
    check (units_recd >= 0),
  primary key (a_id, recd_date)
);

select a.a_id, a.xfr_date, sum(b.units_recd)
from a
inner join b on a.a_id = b.a_id
group by a.a_id; -- The column a.xfr_date is functionally dependent 
                 -- on a.a_id; it doesn't have to appear in the 
                 -- GROUP BY clause.

Значительное отклонение от стандартов SQL - это MySQL. Он позволяет полностью исключить все из группы GROUP BY. Но этот выбор дизайна делает его поведение неопределенным, когда вы опускаете столбцы, которые находятся в списке SELECT.

Ответ 3

Собственно, в MySQL вам не нужно группировать все столбцы. Вы можете просто группировать любые столбцы, которые вы хотите. Проблема в том, что он просто вытащит случайное значение (из набора доступных строк в группе) для полей, которые не находятся в группе. Если вы знаете, что вы группируете что-то уникальное, нет смысла группировать остальные поля, так как они все равно будут иметь одинаковое значение. Он может фактически ускорить его до того, чтобы не группироваться по каждому полю, когда он совершенно не нужен.

Ответ 4

Если вы группируете что-то, вы не можете видеть отдельные значения негрупповых столбцов, потому что в каждой группе может быть несколько значений. Все, что вы можете сделать - это отчет о совокупных функциях (сумма, счет, мин и т.д.) - они могут объединить несколько значений в одну ячейку в результате.

Ответ 5

Есть исключения, как отметил Сэм Шаффрон, но в целом то, что сказал ваш лектор, истинно.

Если я выберу 3 столбца и группу по 2, что СУБД делать с третьим столбцом?

Разработчики РСУБД могут принять решение о том, как обращаться с дополнительным колом (как он выглядит у разработчиков MySQL), но это решение, которое я бы сделал, или тот, который я хочу при написании выбора? Будет ли решение всегда иметь силу? Я, конечно, предпочитаю подход, похожий на Oracle, заставляющий меня четко указывать, что должно произойти.

Если я выберу 3 столбца и группу по 2, если группа RDBS по всем 3, выберите случайное значение из 3-го, самого большого или самого маленького, наиболее распространенного?

Ответ 6

Итак, простой ответ: это зависит. Mysql позволяет это, Vertica - нет.

На самом деле существует допустимый прецедент для исключения, и именно тогда вы уже выбираете слово с MIN().

Вот пример использования отслеживания событий. При визуализации у вас есть события кредитования и покупки.

Для простоты мы говорим: a = credit, b, c, d - это какое-то событие покупки, а время отслеживается с бегущим числом. Теперь вы хотите найти дату первой покупки после каждого кредита. У нас также есть только один клиент 0:

create table events (user_id int ,created_at int, event varchar(255));
insert into events values (0,0, 'a');
insert into events values (0,1, 'b');
insert into events values (0,2, 'c');
insert into events values (0,3, 'a');
insert into events values (0,4, 'c');
insert into events values (0,5, 'b');
insert into events values (0,6, 'a');
insert into events values (0,7, 'a');
insert into events values (0,8, 'd');

mysql> SELECT user_id, MAX(purchased) AS purchased, spent, event FROM (SELECT e1.User_ID AS user_id, e1.created_at AS purchased, MIN(e2.created_at) AS spent, e2.event AS event FROM events e1, events e2 WHERE e1.user_id = e2.user_id AND e1.created_at <= e2.created_at AND e1.Event = 'a' AND e2.Event != 'a' GROUP BY e1.user_id, e1.created_at) e3 GROUP BY user_id, spent;
+---------+-----------+-------+-------+
| user_id | purchased | spent | event |
+---------+-----------+-------+-------+
|       0 |         0 |     1 | b     |
|       0 |         3 |     4 | c     |
|       0 |         7 |     8 | d     |
+---------+-----------+-------+-------+
3 rows in set (0.00 sec)

отлично смотрится в mysql, не работает в Vertica:

ОШИБКА 2640: Столбец "e2.event" должен появиться в предложении GROUP BY или использоваться в агрегатной функции

если я опускаю столбец событий, он работает в обоих случаях, но я действительно хочу знать, какое значение имеет значение для строки, выбранной min.

Итак, мой ответ заканчивается просьбой о комментариях:) Любые идеи?

Ответ 7

Это ответ на пример Майкла Уилла/вопрос.

SELECT 
    e3.user_id,
    MAX(e3.purchased) AS purchased, 
    e3.spent, 
    e.event
FROM 
    events e
INNER JOIN
(SELECT 
    e1.user_id AS user_id, 
    MIN(e1.created_at) as spent,
    e2.created_at as purchased
 FROM
    events e1
 INNER JOIN
    (SELECT e.user_id, e.created_at from events e WHERE e.event = 'a') e2   
 ON e1.user_id = e2.user_id 
 AND e1.created_at >= e2.created_at 
 AND e1.event != 'a'
 GROUP BY e1.User_ID, e2.created_at
) e3 
ON e.user_id = e3.user_id AND e.created_at = e3.spent
GROUP BY e3.user_id, e3.spent, e.event;