SQLinfo.ru - Все о MySQL

Форум пользователей MySQL

Задавайте вопросы, мы ответим

Вы не зашли.

#1 14.11.2012 16:12:09

vasya
Архат
MySQL Authorized Developer
Откуда: Орел
Зарегистрирован: 07.03.2007
Сообщений: 5829

распространенная ошибка в запросах с group by

В запросе с группировкой в части перечисления полей (то, что идет после SELECT) по стандарту SQL можно указывать ТОЛЬКО те поля, по которым идет группировка, или которые используются с групповыми функциями (sum, max, group_concat, ...)
MySQL не генерирует ошибку, если запрос не удовлетворяет данному правилу. Однако результат часто бывает не тот, который ожидается, так как для полей без групповых функций и не указанных в части GROUP BY выбирается произвольная строка из группы.

Для примера рассмотрим таблицу сообщений, имеющую поля (`user_id`, `post`, `time`), в которой хранится id пользователя, текст сообщения и время добавления сообщения. Пусть мы хотим выбрать последнее сообщение каждого юзера. Порой можно увидеть такие конструкции:

SELECT * FROM `таблица` GROUP BY user_id ORDER BY `time` DESC;
или
SELECT `user_id`, `post`, max(`time`) FROM `таблица` GROUP BY user_id;

Оба этих запроса неверные. В первом для каждого user_id будет выбрана произвольная строка с данным user_id после чего эти строки будут отсортированы по времени. Во втором, для каждого user_id будет выбрано max(`time`) и значение поля `post` из случайной строки с данным user_id, а не из той, которая соответствует max(`time`). А если результат правильный, то это не более чем случайность.

Проиллюстрируем ситуацию на простом примере.
MariaDB [test]> select user_id, post, `time` from test_table;
+---------+--------+---------------------+
| user_id | post   | time                |
+---------+--------+---------------------+
|       1 | post 1 | 2012-10-14 16:51:26 |
|       1 | post 2 | 2012-11-12 16:51:26 |
|       1 | post 3 | 2012-11-15 16:51:26 |
+---------+--------+---------------------+
3 rows in set (0.14 sec)

MariaDB [test]> select user_id, post, max(`time`) from test_table group by user_id;
+---------+--------+---------------------+
| user_id | post   | max(`time`)         |
+---------+--------+---------------------+
|       1 | post 1 | 2012-11-15 16:51:26 |
+---------+--------+---------------------+

Как видим значение поля `post` выбрано "неверно". В кавычках потому что неверно с человеческой точки зрения - мы ожидали увидеть значение, соответствующее максимальному времени, т.е. 'post 3'. А сервер MySQL взял значение поля post из первой попавшейся строки с user_id =1, так как никаких указаний относительно этого поля ему не поступало.


Правильным решением данной задачи будет найти сначала комбинации (`user_id`,`time`), а по ним уже выбрать недостающее поле `post`. Для простоты предположим, что у пользователя не может быть двух одновременных сообщений, т.е. комбинация (`user_id`,`time`) однозначно идентифицирует строку. Тогда нужный нам запрос будет выглядеть так:

MariaDB [test]> SELECT t.`user_id`, t.`post`, t.`time` FROM `test_table` t JOIN
(SELECT `user_id`, max(`time`) as `mtime` FROM `test_table` GROUP BY `user_id`)
as t1 ON t.`user_id`= t1.`user_id` AND t.`time` = t1.`mtime`;
+---------+--------+---------------------+
| user_id | post   | time                |
+---------+--------+---------------------+
|       1 | post 3 | 2012-11-15 16:51:26 |
+---------+--------+---------------------+



При определенных условиях существует способ решить задачу без использования JOIN. Пусть поле `time` имеет тип данных DATETIME, т.е. принимает значения вида '2012-02-15 01:47:19'. Тогда мы знаем, что значение этого поля всегда будет иметь ширину в 19 символов. В этом случае мы можем искать максимум не от поля `time`, а от строки, склеивающей значения полей `time` и `post`, а потом результат разбить назад на составляющие.

SELECT `user_id`, SUBSTR(MAX(CONCAT(`time`, `post`)), 1, 19) as `time`, SUBSTR(MAX(CONCAT(`time`, `post`)), 20) as `post` FROM `таблица` GROUP BY `user_id`;

Такой метод может быть необходим в случае, если группирующая функция берется не от значения поля, а от результата каких-то вычислений над полем. Тогда первый метод через JOIN не применим, так как после выполнения группировки у нас будет значение `user_id` и max( от каких-то вычислений с полем `time` ), а не значение самого поля `time`. Т.е. обратится вновь к таблице мы сможем только по полю `user_id`, а это не позволит нам идентифицировать нужную строку.
Пример подобной задачи разобран в теме http://sqlinfo.ru/forum/viewtopic.php?id=6102


Третий способ решения - пользовательские переменные. Пример подобного решения см в конце темы http://sqlinfo.ru/forum/viewtopic.php?id=3839

Неактивен

 

#2 23.01.2014 12:52:38

vasya
Архат
MySQL Authorized Developer
Откуда: Орел
Зарегистрирован: 07.03.2007
Сообщений: 5829

Re: распространенная ошибка в запросах с group by

Более подробно вопрос рассмотрен в статье Группировка в MySQL

Неактивен

 

Board footer

Работает на PunBB
© Copyright 2002–2008 Rickard Andersson