Как эффективно выбрать среднюю сумму нескольких сумм, рассчитываемых на основе разных временных отметок в SQL? - PullRequest
1 голос
/ 08 февраля 2012

У меня есть таблица базы данных, похожая на следующую:

id | macaddr | load | timestamp
=========================================
 1 | 0011111 |   17 | 2012-02-07 10:00:00
 1 | 0011111 |    6 | 2012-02-07 12:00:00
 2 | 0022222 |    3 | 2012-02-07 12:00:03
 3 | 0033333 |    9 | 2012-02-07 12:00:04
 4 | 0022222 |    4 | 2012-02-07 12:00:06
 5 | 0033333 |    8 | 2012-02-07 12:00:10
...

Теперь я хотел бы рассчитать среднюю нагрузку по всем устройствам (= mac-адресам) за разные периоды времени, например, на сегодня, вчера, на этой неделе, в этом месяце.

Среднюю нагрузку можно рассчитать, сначала определив общую сумму нагрузки в разные моменты времени (даты выборки), а затем рассчитав среднее значение сумм нагрузки для этих дат выборки. Например, если бы я хотел получить среднюю загрузку за последние десять секунд (а сейчас 2012-02-07 12:00:10), я мог бы принять решение о датах выборки в 12:00:02, 12:00: 04, 12:00:06, 12:00:08 и 12:00:10. Затем я вычислил бы суммы нагрузки, суммируя самые последние значения нагрузки для каждого устройства:

2012-02-07 12:00:02 |  6  [= load(id=2)]
2012-02-07 12:00:04 | 18  [= load(id=2) + load(id=3) + load(id=4)]
2012-02-07 12:00:06 | 19  [= load(id=2) + load(id=4) + load(id=5)]
2012-02-07 12:00:08 | 19  [= load(id=2) + load(id=4) + load(id=5)]
2012-02-07 12:00:10 | 18  [= load(id=2) + load(id=5) + load(id=6)]

Значение загрузки устройства игнорируется, если оно старше, например, час (здесь произошло с id = 1). Среднее значение будет 16 в этом случае.

В настоящее время я генерирую довольно сложный запрос со многими операторами «UNION ALL», который очень медленно работает:

SELECT avg(l.load_sum) as avg_load
FROM (
    SELECT sum(so.load) AS load_sum 
    FROM (
        SELECT * 
        FROM (
            SELECT si.macaddr, si.load 
            FROM sensor_data si WHERE si.timestamp > '2012-02-07 11:00:10' AND si.timestamp < '2012-02-07 12:00:10'
            ORDER BY si.timestamp DESC 
        ) AS sm
        GROUP BY macaddr
    ) so
    UNION ALL
    [THE SAME THING AGAIN WITH OTHER TIMESTAMPS]
    UNION ALL
    [AND AGAIN]
    UNION ALL
    [AND AGAIN]
    ...
) l

А теперь представьте, что я хотел бы рассчитать среднюю нагрузку за целый месяц. С почасовыми выборками дат мне нужно объединить 30x24 = 720 запросов, используя оператор UNION ALL Полный запрос занимает около минуты на моем компьютере. Я уверен, что есть гораздо лучшее решение без заявления UNION ALL. Тем не менее, я не нашел ничего полезного в Интернете. Поэтому я был бы очень благодарен за вашу помощь!

Ответы [ 3 ]

1 голос
/ 08 февраля 2012

Используйте часть метки времени Unix: Сначала мы сформулируем среднечасовые (3600 секунд) средние значения:

SELECT
  macaddr, 
  sum(CAST(load AS float))/CAST(count(*) AS float) AS loadavg,
  FLOOR(UNIX_TIMESTAMP(`timestamp`)/3600) AS hourbase
FROM sensor_data
GROUP BY macaddr,FLOOR(UNIX_TIMESTAMP(`timestamp`)/3600)

Затем мы усредняем их за месяц

SELECT 
  avg(loadavg) as monthlyavg,
  macaddr
FROM (
    SELECT
      macaddr, 
      sum(CAST(load AS float))/CAST(count(*) AS float) AS loadavg,
      FLOOR(UNIX_TIMESTAMP(`timestamp`)/3600) AS hourbase
    FROM sensor_data
    WHERE `timestamp` BETWEEN '2012-01-07 12:00:00' AND '2012-02-07 11:59:59'
    GROUP BY macaddr,FLOOR(UNIX_TIMESTAMP(`timestamp`)/3600)
) AS hourlies
GROUP BY macaddr, hourbase
0 голосов
/ 08 февраля 2012

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

mysql> SELECT * FROM `test`;
+----+-----+------+------------+
| id | mac | load | when       |
+----+-----+------+------------+
|  1 |   1 |   10 | 2012-02-01 |
|  2 |   1 |   20 | 2012-01-01 |
|  3 |   2 |   60 | 2011-09-01 |
+----+-----+------+------------+

mysql> SELECT avg(`sum_load`)
    -> FROM 
    -> (
    ->    SELECT sum( `load` ) as sum_load
    ->    FROM `test`
    ->    WHERE `when` > '2011-01-15'
    ->    GROUP BY `mac`
    -> ) as t1;
+-----------------+
| avg(`sum_load`) |
+-----------------+
|         45.0000 |
+-----------------+

mysql> SELECT avg(`sum_load`)
    -> FROM 
    -> (
    ->    SELECT sum( `load` ) as sum_load
    ->    FROM `test`
    ->    WHERE `when` > '2011-01-15' AND `when` < '2012-01-15'
    ->    GROUP BY `mac`
    -> ) as t1;
+-----------------+
| avg(`sum_load`) |
+-----------------+
|         40.0000 |
+-----------------+
0 голосов
/ 08 февраля 2012

Чтобы упростить себе задачу, вы должны создать функцию «часа», которая возвращает дату и время без значащих цифр после части часа. Так что прямо сейчас (2/2/2012 5:05 вечера) будет 2012-02-07 17:00. Вот код для вашей часовой функции:

select dateadd(hh, DATEPART(hh, current_timestamp), DATEADD(dd, 0, datediff(dd, 0, current_timestamp)))

(замените current_timestamp в приведенном выше коде параметром datetime вашей часовой функции. Я предполагаю, что вы создали его как dbo.fnHour (), и он принимает параметр datetime.

Затем вы можете использовать dbo.fnHour () в качестве функции разделения для запроса того, что вы хотите. Ваш sql будет выглядеть примерно так:

select 
    avg(load) as avg_load
from (
    select dbo.fnHour(si.timestamp) [hour], macaddr, sum(load) as [load]
    from 
        sensor_data si 
    where 
        si.timestamp >= dateadd(mm, -1, current_timestamp)
    group by 
        dbo.fnHour(si.timestamp), macaddr
) as f

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

...