У меня есть таблица журналов в MySQL (v5.7), в которой записываются запросы пользователей, из которых я извлекаю разбивку активности, показывающую количество пользователей и общее количество обращений за каждый месяц, например:

Date            Users   Hits
September 2018  20      1,839
August 2018     23      2,723
July 2018       21      1,632
June 2018       22      2,981

В настоящее время это достигается с помощью следующего запроса:

SELECT month(l.time) m, year(l.time) y, date_format(l.time, '%M %Y') monthyear, 
  (select count(distinct userid) from log lm 
    where month(lm.time) = month(l.time) and year(lm.time) = year(l.time)) users,
  count(u.name) hits
FROM log l left join users u on u.id=l.userid
group by date_format(l.time, '%M %Y')
order by l.time desc, l.id desc

Этот SQL не работает с включенным only_full_group_by, как сейчас по умолчанию в MySQL, потому что не все выражения находятся в предложении GROUP BY. Решения, которые я нашел, обычно включают либо использование агрегатной функции, такой как MAX (), либо добавление всех выражений в предложение GROUP BY, но подзапрос 'users' делает эти подходы проблематичными: я не могу использовать подход MAX () ( недопустимый синтаксис) и добавление его в предложение GROUP BY приводит к тому, что запрос становится настолько медленным, что я еще не видел завершения теста.

Я чувствую, что, вероятно, есть элегантное и производительное решение, не прибегая к отключению only_full_group_by, но я не в моих силах с SQL.

1
Blackmyre 24 Сен 2018 в 19:29

2 ответа

Лучший ответ

Вот упрощенный запрос:

SELECT DATE_FORMAT(l.time, '%M %Y') AS monthyear, 
  COUNT(DISTINCT l.userid) AS users,
  COUNT(*) AS hits
FROM log l
GROUP BY monthyear

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

Вам вообще не нужно присоединяться к таблице users, если только вы не собираетесь подсчитывать только обращения к пользователям со столбцом name, отличным от NULL (COUNT игнорирует NULL, и я предполагаю, что вы хотите подсчитать все совпадения из журнала, что означает, что вы должны использовать COUNT(*) вместо COUNT(u.name).

Я удалил предложение ORDER BY, поскольку оно ссылается на столбцы, которых нет в результате. Если вы хотите сделать заказ по месяцам, вам следует подумать о том, чтобы отформатировать месяц и год таким образом, чтобы упорядочить его так, как вы хотите:

SELECT DATE_FORMAT(l.time, '%Y-%m') AS monthyear, 
  COUNT(DISTINCT l.userid) AS users,
  COUNT(*) AS hits
FROM log l
GROUP BY monthyear

GROUP BY по умолчанию упорядочивает группы по значению.

1
Bill Karwin 24 Сен 2018 в 16:41

Я не уверен, зачем вам использовать для этого подзапрос. Разве это не то, что вы хотите?

SELECT month(l.time) as m, year(l.time) as y, date_format(l.time, '%M %Y') as monthyear, 
       count(distinct l.userid) as users,
       count(u.name) as hits
FROM log l left join
     users u
     on u.id = l.userid
GROUP BY m, y, monthyear
ORDER BY max(l.time) desc, l.id desc;
1
Gordon Linoff 24 Сен 2018 в 16:32