Выполняю некоторые вычисления в mysql, цифры отключены при использовании GROUP BY

#mysql #group-by

#mysql #group-by

Вопрос:

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

 SELECT hit_paylevel, sum(hit_uniques) as day_unique_hits
       , (sum(hit_uniques)/1000)*hit_paylevel as day_earnings
       , hit_date 
FROM daily_hits 
WHERE hit_user = 'xxx' AND hit_date >= '2011-05-01' AND hit_date < '2011-06-01' 
GROUP BY hit_user
  

Рассматриваемая таблица выглядит следующим образом:

 CREATE TABLE IF NOT EXISTS `daily_hits` (
  `hit_itemid` varchar(255) NOT NULL,
  `hit_mainid` int(11) NOT NULL,
  `hit_user` int(11) NOT NULL,
  `hit_date` date NOT NULL,
  `hit_hits` int(11) NOT NULL DEFAULT '0',
  `hit_uniques` int(11) NOT NULL,
  `hit_embed` int(11) NOT NULL,
  `hit_paylevel` int(1) NOT NULL DEFAULT '1',
  PRIMARY KEY (`hit_itemid`,`hit_date`),
  KEY `hit_user` (`hit_user`),
  KEY `hit_mainid` (`hit_mainid`,`hit_date`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
  

Проблема в вычислении связана с hit_paylevel, который действует как множитель. По умолчанию используется значение one, другой вариант — 2 или 3, что по существу удваивает или утраивает прибыль за этот день.

Если я перебираю дни, значение daily day_earnings верно, просто, когда я их группирую, все вычисляется как paylevel 1. Это происходит, если вначале пользователь был paylevel 1, а позже был повышен до более высокого уровня. если пользователь изначально имеет 2-й уровень оплаты, он также все вычисляет правильно.

Комментарии:

1. Это хороший пример того, почему мне не нравится способ обработки MySQL GROUP BY . hit_paylevel Зависит от строки в вашей группе, и MySQL произвольно выбирает одну для использования.

2. @Joe: на самом деле, это действительно проблема MySQL. PostgreSQL полностью отклонит запрос, жалуясь, что он не знает, какой hit_paylevel выбрать…

3. @Denis: Да, это была моя точка зрения. Мне не нравится, что MySQL считает это законным запросом.

4. @Joe, мне нравится, как MySQL обрабатывает group by, тебе просто нужно быть в курсе этого, чтобы тебя не укусили. Однако у этого есть свое применение.

Ответ №1:

Разве это не должно быть sum(hit_uniques * hit_paylevel) / 1000 ?

Ответ №2:

Как сказал @Denis:

Измените запрос на

 SELECT hit_paylevel, sum(hit_uniques) as day_unique_hits
       , sum(hit_uniques * hit_paylevel) / 1000 as day_earnings
       , hit_date 
FROM daily_hits 
WHERE hit_user = 'xxx' AND hit_date >= '2011-05-01' AND hit_date < '2011-06-01' 
GROUP BY hit_user;
  

Почему это устраняет проблему
Делая hit_paylevel вне суммы, сначала суммирует все hit_uniques , а затем выбирает случайное hit_paylevel значение для умножения на него.
Не то, что вы хотите. Если вы выполните оба столбца внутри суммы, MySQL соединит правильные hit_uniques и hit_paylevels .

Опасности group by
Это важная вещь, которую следует помнить в MySQL.
Это group by предложение работает иначе, чем в других базах данных.
В MSSQL * (или Oracle, или PostgreSQL) вы бы получили сообщение об ошибке

неагрегатное выражение должно отображаться в предложении group by

Или слова на этот счет.

В вашем исходном запросе hit_paylevel нет агрегированного значения (sum), и его также нет в group by предложении, поэтому MySQL просто выбирает значение случайным образом.

Комментарии:

1. Попробовал это. Сумма выглядит намного ближе к значению, которым она должна быть, но она все еще немного отличается от числа, которое я должен иметь, если я вручную добавлю числа, просматривая результаты.

2. @Егор, если либо hit_uniques либо hit_paylevel может быть null, то выражение hit_uniques * hit_paylevel принимает значение 0 (потому что сумма их не учитывает).

3. ни один из них не выполняет. Они будут как минимум равны 1