Mysql GROUP BY и COUNT для нескольких предложений WHERE - PullRequest
5 голосов
/ 27 октября 2009

Упрощенная структура таблицы:

CREATE TABLE IF NOT EXISTS `hpa` (
  `id` bigint(15) NOT NULL auto_increment,
  `core` varchar(50) NOT NULL,
  `hostname` varchar(50) NOT NULL,
  `status` varchar(255) NOT NULL,
  `entered_date` int(11) NOT NULL,
  `active_date` int(11) NOT NULL,
  PRIMARY KEY  (`id`),
  KEY `hostname` (`hostname`),
  KEY `status` (`status`),
  KEY `entered_date` (`entered_date`),
  KEY `core` (`core`),
  KEY `active_date` (`active_date`)
)

Для этого у меня есть следующий запрос SQL, который просто суммирует все записи с определенным статусом.

SELECT core,COUNT(hostname) AS hostname_count, MAX(active_date) AS last_active
          FROM `hpa`
          WHERE 
          status != 'OK' AND status != 'Repaired'
          GROUP BY core
          ORDER BY core

Этот запрос был упрощен, чтобы удалить ВНУТРЕННИЕ СОЕДИНЕНИЯ с несвязанными данными и дополнительными столбцами, которые не должны влиять на вопрос.

MAX (active_date) одинакова для всех записей определенного дня и всегда должна выбирать самый последний день или разрешать смещение от NOW (). (это поле UNIXTIME)

Мне нужны оба счетчика: (статус! = 'ОК' И статус! = 'Ремонт')

И обратное ... количество: (состояние = 'ОК' ИЛИ ​​состояние = 'Восстановлено')

И первый ответ, деленный на второй, для 'процента_адреса' (вероятно, так же быстро, как и в постобработке)

ЗА последний день или смещение (- 86400 за вчера и т. Д.)

Таблица содержит около 500 тыс. Записей и растет примерно на 5000 в день, поэтому было бы неплохо использовать один SQL-запрос, а не зацикливаться.

Я предполагаю, что некоторые творческие IF могли бы сделать это. Ваш опыт ценится.

РЕДАКТИРОВАТЬ: я открыт для использования другого запроса SQL для текущих данных или данных со смещением.

РЕДАКТИРОВАТЬ: запрос работает, достаточно быстро, но в настоящее время я не могу позволить пользователям сортировать по столбцу процента (тот, который получен из плохих и хороших показателей). Это не шоу-стопор, но я разрешаю им разобраться во всем остальном. ЗАКАЗАТЬ по этому:

SELECT h1.core, MAX(h1.entered_date) AS last_active, 
SUM(CASE WHEN h1.status IN ('OK', 'Repaired') THEN 1 ELSE 0 END) AS good_host_count,  
SUM(CASE WHEN h1.status IN ('OK', 'Repaired') THEN 0 ELSE 1 END) AS bad_host_count 
FROM `hpa` h1 
LEFT OUTER JOIN `hpa` h2 ON (h1.hostname = h2.hostname AND h1.active_date < h2.active_date) 
WHERE h2.hostname IS NULL 
GROUP BY h1.core 
ORDER BY ( bad_host_count / ( bad_host_count + good_host_count ) ) DESC,h1.core

дает мне: # 1247 - Ссылка 'bad_host_count' не поддерживается (ссылка на групповую функцию)

РЕДАКТИРОВАТЬ: Решено для другого раздела. Следующее работает и позволяет мне ORDER BY percentage_dead

SELECT c.core, c.last_active, 
SUM(CASE WHEN d.dead = 1 THEN 0 ELSE 1 END) AS good_host_count,  
SUM(CASE WHEN d.dead = 1 THEN 1 ELSE 0 END) AS bad_host_count,
( SUM(CASE WHEN d.dead = 1 THEN 1 ELSE 0 END) * 100/
( (SUM(CASE WHEN d.dead = 1 THEN 0 ELSE 1 END) )+(SUM(CASE WHEN d.dead = 1 THEN 1 ELSE 0 END) ) ) ) AS percentage_dead
FROM `agent_cores` c 
LEFT JOIN `dead_agents` d ON c.core = d.core
WHERE d.active = 1
GROUP BY c.core
ORDER BY percentage_dead

1 Ответ

3 голосов
/ 27 октября 2009

Если я понимаю, вы хотите получить счетчик состояния «ОК», а не «ОК» имен хостов на дату последнего действия. Правильно? И затем это должно быть сгруппировано по ядру.

SELECT core, MAX(active_date)
  SUM(CASE WHEN status IN ('OK', 'Repaired') THEN 1 ELSE 0 END) AS OK_host_count,
  SUM(CASE WHEN status IN ('OK', 'Repaired') THEN 0 ELSE 1 END) AS broken_host_count
FROM `hpa` h1 LEFT OUTER JOIN `hpa` h2 
  ON (h1.hostname = h2.hostname AND h1.active_date < h2.active_date)
WHERE h2.hostname IS NULL
GROUP BY core
ORDER BY core;

Это вариант проблемы «наибольший n на группу», который я часто вижу в вопросах SQL о StackOverflow.

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

Затем сгруппируйте по ядру и посчитайте строки по статусу.

Это решение для сегодняшней даты (при условии, что ни у одной строки нет active_date в будущем). Чтобы ограничить результат строками N дней назад, необходимо ограничить обе таблицы.

SELECT core, MAX(active_date)
  SUM(CASE WHEN status IN ('OK', 'Repaired') THEN 1 ELSE 0 END) AS OK_host_count,
  SUM(CASE WHEN status IN ('OK', 'Repaired') THEN 0 ELSE 1 END) AS broken_host_count
FROM `hpa` h1 LEFT OUTER JOIN `hpa` h2 
  ON (h1.hostname = h2.hostname AND h1.active_date < h2.active_date
  AND h2.active_date <= CURDATE() - INTERVAL 1 DAY)
WHERE h1.active_date <= CURDATE() - INTERVAL 1 DAY AND h2.hostname IS NULL
GROUP BY core
ORDER BY core; 

Что касается соотношения между ОК и неработающими именами хостов, я бы порекомендовал просто рассчитать это в своем PHP-коде. SQL не позволяет вам ссылаться на псевдонимы столбцов в других выражениях списка выбора, поэтому вам нужно будет обернуть вышеупомянутое как подзапрос, и это сложнее, чем в данном случае.


Я забыл, что вы сказали, что используете метку времени UNIX. Сделайте что-то вроде этого:

SELECT core, MAX(active_date)
  SUM(CASE WHEN status IN ('OK', 'Repaired') THEN 1 ELSE 0 END) AS OK_host_count,
  SUM(CASE WHEN status IN ('OK', 'Repaired') THEN 0 ELSE 1 END) AS broken_host_count
FROM `hpa` h1 LEFT OUTER JOIN `hpa` h2 
  ON (h1.hostname = h2.hostname AND h1.active_date < h2.active_date
  AND h2.active_date <= UNIX_TIMESTAMP() - 86400)
WHERE h1.active_date <= UNIX_TIMESTAMP() - 86400 AND h2.hostname IS NULL
GROUP BY core
ORDER BY core; 
...