Ограничение GROUP BY на основе значений COUNT () в mySQL

Я регистрирую события в базе данных mySQL и хотел бы получить топ-3 событий для целей мониторинга.

Мой стол eventlog выглядит так:

+----+------------------+---------------------+
| id |    eventname     |      eventdate      |
+----+------------------+---------------------+
|  0 | machine1.started | 2016-09-04 19:22:23 |
|  1 | machine2.reboot  | 2016-09-04 20:23:11 |
|  2 | machine1.stopped | 2016-09-04 20:24:12 |
|  3 | machine1.started | 2016-09-04 20:25:12 |
|  4 | machine1.stopped | 2016-09-04 23:23:16 |
|  5 | machine0.started | 2016-09-04 23:24:00 |
|  6 | machine1.started | 2016-09-04 23:24:16 |
|  7 | machine3.started | 2016-09-04 23:25:00 |
|  8 | machine4.started | 2016-09-04 23:26:00 |
|  9 | cluster.alive    | 2016-09-04 23:30:00 |
| 10 | cluster.alive    | 2016-09-05 11:30:00 |
+----+------------------+---------------------+

Запрос должен в конечном итоге вернуть следующее, держа

  • 3 наиболее часто встречающихся события (на основе столбца eventcounts, который генерируется MySQL COUNT() функция), сгруппированные по eventname
  • только 2 строки где eventcount = 1, но только если 1 находится в верхних 3 eventcounts (так как есть много событий, которые происходят просто
    один раз и поэтому перегрузит мой фронтенд)

Пример желаемого результата, основанный на приведенной выше таблице:

+------------+------------------+
| eventcount |    eventname     |
+------------+------------------+
|          3 | machine1.started |
|          2 | machine1.stopped |
|          2 | cluster.alive    |
|          1 | machine0.started |
|          1 | machine2.started |
+------------+------------------+

Обратите внимание, что мне нужны не только 3 возвращенные строки, а строки с 3 самыми высокими eventcounts.

Я много экспериментировал, возиться со строкой запроса ниже, включая множественный выбор и сомнительные CASE ... WHEN условия, но не смог заставить его работать так, как мне нужно.

SELECT COUNT(id) AS 'eventcount', eventname
FROM eventlog
GROUP BY eventname
ORDER BY eventcount DESC;

Каков наилучший подход, чтобы получить желаемый результат быстрым способом?

5

Решение

вот один из способов сделать это с помощью переменных
SQL Fiddle для этого: http://sqlfiddle.com/#!9/b3458b/16

SELECT
t2.eventcount
,t2.eventname
FROM
(
SELECT
t.eventname
,t.eventcount
,@Rank:=IF(@PrevCount=t.eventcount,@Rank,@Rank+1) Rank
,@CountRownum:=IF(@PrevCount=t.eventcount,@CountRowNum + 1,1) CountRowNum
,@PrevCount:= t.eventcount
FROM
(
SELECT
l.eventname
,COUNT(*) as eventcount
FROM
eventlog l
GROUP BY
l.eventname
ORDER BY
COUNT(*) DESC
) t
CROSS JOIN (SELECT @Rank:=0, @CountRowNum:=0, @PrevCount:=-1) var
ORDER BY
t.eventcount DESC
) t2
WHERE
t2.Rank < 4
AND NOT (t2.eventcount = 1 AND t2.CountRowNum > 2)
2

Другие решения

Эти типы условий в MySQL болезненны. Один метод использует переменные. Вот метод, который не делает:

SELECT el.eventcount, el.eventname
FROM (SELECT COUNT(el.id) AS eventcount, el.eventname
FROM eventlog el
GROUP BY el.eventname
) el JOIN
(SELECT cnt
FROM (SELECT DISTINCT COUNT(el.id) as cnt
FROM eventlog el
GROUP BY el.eventname
) el
ORDER BY cnt DESC
LIMIT 3
) ell
ON ell.cnt = el.eventcount
ORDER BY el.eventcount DESC;

РЕДАКТИРОВАТЬ:

Решение с использованием переменных выглядит следующим образом и включает ограничение на 2 для счетчика 1:

SELECT *
FROM (SELECT e.*,
(@rn1 := if(@c1 = eventcount, @rn1 + 1,
if(@c1 := eventcount, 1, 1)
)
) as rn
FROM (SELECT e.*,
(@rn := if(@c = eventcount, @rn,
if(@c := eventcount, @rn + 1, @rn + 1)
)
) as rank
FROM (SELECT COUNT(el.id) AS eventcount, el.eventname
FROM eventlog el
GROUP BY el.eventname
) e CROSS JOIN
(SELECT @c := 0, @rn := 0) params
ORDER BY eventcount DESC
) e CROSS JOIN
(SELECT @c1 := 0, @rn1 := 0) params
ORDER BY eventcount DESC
) e
WHERE rank <= 3 AND
(eventcount > 1 OR rn <= 2);

Самый внутренний счет перечисляет счет. Второе перечисляет в пределах счета. На самом деле, эти два вопроса, вероятно, могут быть объединены в один подзапрос, но с осторожностью.

2

Это должно быть в состоянии немного рефакторинг, но он возвращает правильный ответ на данный момент:

SELECT eventcount, eventname
FROM
(SELECT el.eventcount, el.eventname
FROM (SELECT COUNT(el.id) AS eventcount, el.eventname
FROM eventlog el
GROUP BY el.eventname
) el JOIN
(SELECT counts
FROM (SELECT DISTINCT COUNT(el.id) as counts
FROM eventlog el
GROUP BY el.eventname
) el
ORDER BY counts DESC
LIMIT 3
) el2
ON el2.counts = el.eventcount
WHERE el.eventcount != 1
UNION ALL
(SELECT el.eventcount, el.eventname
FROM (SELECT COUNT(el.id) AS eventcount, el.eventname
FROM eventlog el
GROUP BY el.eventname
) el JOIN
(SELECT counts
FROM (SELECT DISTINCT COUNT(el.id) as counts
FROM eventlog el
GROUP BY el.eventname
) el
ORDER BY counts DESC
LIMIT 3
) el2
ON el2.counts = el.eventcount AND el2.counts = 1
LIMIT 2)) tmp
ORDER BY tmp.eventcount DESC;

SQL Fiddle: http://sqlfiddle.com/#!9/10f0d/92

0

Если вы можете использовать временные таблицы ..

Предварительно рассчитайте количество событий и сохраните результат во временной таблице:

create temporary table tmp_eventcounts
select eventname, count(1) as eventcount
from eventlog
group by eventname
order by eventcount desc
;

Содержание tmp_eventcounts:

|        eventname | eventcount |
|------------------|------------|
| machine1.started |          3 |
| machine1.stopped |          2 |
|    cluster.alive |          2 |
| machine3.started |          1 |
|  machine2.reboot |          1 |
| machine4.started |          1 |
| machine0.started |          1 |

Выберите первые 3 учетных записи событий и сохраните их в другой временной таблице:

create temporary table tmp_top3counts
select distinct eventcount
from tmp_eventcounts
order by eventcount desc
limit 3
;

Содержание tmp_top3counts:

| eventcount |
|------------|
|          3 |
|          2 |
|          1 |

Теперь выберите все имена событий с верхними 3 счетами событий, но eventcount > 1.
Также можно выбрать до двух имен событий с 3 верхними учетными записями, но eventcount = 1
Используйте UNION, чтобы объединить два результата:

select eventcount, eventname
from tmp_top3counts
join tmp_eventcounts using(eventcount)
where eventcount > 1
union all (
select eventcount, eventname
from tmp_top3counts
join tmp_eventcounts using(eventcount)
where eventcount = 1
limit 2
)
order by eventcount desc;

Результат:

| eventcount |        eventname |
|------------|------------------|
|          3 | machine1.started |
|          2 | machine1.stopped |
|          2 |    cluster.alive |
|          1 |  machine2.reboot |
|          1 | machine3.started |

http://sqlfiddle.com/#!9/b332df/1

Если вы не можете использовать временные таблицы, вы можете заменить их вхождения на их определения и создать крайне нечитаемый, но работающий запрос:

select eventcount, eventname
from (
select distinct eventcount
from (
select eventname, count(1) as eventcount
from eventlog
group by eventname
) tmp_eventcounts
order by eventcount desc
limit 3
) tmp_top3counts
join (
select eventname, count(1) as eventcount
from eventlog
group by eventname
) tmp_eventcounts using(eventcount)
where eventcount > 1
union all (
select eventcount, eventname
from (
select distinct eventcount
from (
select eventname, count(1) as eventcount
from eventlog
group by eventname
) tmp_eventcounts
order by eventcount desc
limit 3
) tmp_top3counts
join (
select eventname, count(1) as eventcount
from eventlog
group by eventname
) tmp_eventcounts using(eventcount)
where eventcount = 1
limit 2
)
order by eventcount desc;

http://sqlfiddle.com/#!9/2eea6/4 😉

Хотя это может показаться сумасшедшим, его легко создать в PHP:

$tmp_eventcounts = "select eventname, count(1) as eventcount
from eventlog
group by eventname
";

$tmp_top3counts = "select distinct eventcount
from ( {$tmp_eventcounts} ) tmp_eventcounts
order by eventcount desc
limit 3
";

$sql = "select eventcount, eventname
from ( {$tmp_top3counts} )  tmp_top3counts
join ( {$tmp_eventcounts} ) tmp_eventcounts using(eventcount)
where eventcount > 1
union all (
select eventcount, eventname
from ( {$tmp_top3counts} )  tmp_top3counts
join ( {$tmp_eventcounts} ) tmp_eventcounts using(eventcount)
where eventcount = 1
limit 2
)
order by eventcount desc
";

Примечание. Похоже, что MySQL необходимо будет снова и снова выполнять одни и те же подзапросы. Но он должен иметь возможность кэшировать результаты и использовать их повторно.

0

Вы можете попробовать это:

SELECT count(eventname), eventname FROM table
group by eventname
HAVING(count(eventname)) > 1
order by count(eventname) DESC
limit 3
-1
По вопросам рекламы [email protected]