Странное поведение с MySQL GroupBy на нескольких столбцах - PullRequest
0 голосов
/ 30 сентября 2019

Я пишу запрос, который группирует таблицу пользователей по (height, sex), а затем получает имя пользователя с минимальным весом для этой комбинации height и sex.

Если выхочу перейти к фактическому запросу, вот SQLFiddle с минимальным представительным примером.

Если вы не хотите открывать этот URL, вот о чем я говорю. Имеется таблица типа:

| height | sex    | name    | weight |
|-------:|--------|---------|--------|
| 100    | female | Alice   | 150    |
| 100    | female | Barbara | 130    |
| 100    | female | Candice | 100    |

и запрос, который делает это следующим образом:

SELECT name, min(weight) from users
group by height, sex

Почему вывод запроса:

|  name | min(weight) |
|------:|-------------|
| Alice | 100         |

Что яна самом деле хотеть это Candice, 100 не Alice, 100.

Я понял, что выбирает Алису, потому что это первый ряд, но почему он это делает? Это действительно странно и неожиданно.

Ответы [ 3 ]

1 голос
/ 30 сентября 2019

Я бы использовал коррелированный подзапрос, но не с причудливой логикой:

select u.*
from users u
where u.weight = (select min(u2.weight)
                  from users u2
                  where u2.height = u.height and u2.sex = u.sex
                 );

То есть возвращаем пользователей, чей вес является минимальным для комбинации height / sex.

1 голос
/ 30 сентября 2019

Вы не используете GROUP BY правильно. В большинстве СУБД существует общее правило, согласно которому все неагрегированные столбцы должны появляться в предложении GROUP BY. Только старые версии MySQL позволяют нарушать это правило (в более новых версиях вам нужно отключить опцию ONLY_FULL_GROUP_BY), но это может привести к непредсказуемым результатам, как вы видите здесь.

Чтобы показать пользователя, который имеетнаименьшее значение weight в группах записей с одинаковыми значениями height и sex, одним из решений будет использование условия NOT EXISTS с коррелированным подзапросом, например:

SELECT *
FROM users u
WHERE NOT EXISTS (
    SELECT 1
    FROM users u1
    WHERE 
        u1.height = u.height 
        AND u1.sex = u.sex
        AND u1.weight < u.weight
)

Обоснование:Условие NOT EXITS в сочетании с подзапросом исключает записи, для которых существует другая запись с теми же height и sex, но с меньшим weight, оставляя в наборе результатов только запись, имеющую наименьшее значение weight для каждогоheight/sex кортеж. Мы используем SELECT 1, потому что нам не нужен подзапрос для возврата фактического результата, мы просто хотим знать, возвращает ли он что-то .

0 голосов
/ 30 сентября 2019

С этими данными

CREATE TABLE users
(`height` varchar(8), `sex` varchar(8), `name` varchar(9), `weight` varchar(8))
;

INSERT INTO users
(`height`, `sex`, `name`, `weight`)
VALUES
('100', 'female', 'Alice', '150'),
('100', 'female', 'Barbara', '130'),
('100', 'female', 'Candice', '100'),
('190', 'male', 'John', '185'),
('190', 'male', 'Bert', '130'),
('190', 'male', 'John', '113')
;

И с этим оператором sql

Select name, u.weight
from users u inner join 
(Select min(weight) weight ,sex,height
From users
Group by height, sex ) u1 
On u.sex= u1.sex and u.height = u1.height
and u.weight = u1.weight
GROUP By u.height,u.sex;

Вы получаете следующий результат

name        weight
Candice     100
John        113

Второй оператор выбора u1 получает всеминимальные веса для всех групп: пол и рост, остальное - простое внутреннее объединение, в котором выбираются только соответствующие имена

...