Сумма суммы перекрывающихся диапазонов даты и времени в MySQL - PullRequest
4 голосов
/ 18 июля 2009

У меня есть таблица событий, каждое из которых имеет StartTime и EndTime (как тип DateTime) в таблице MySQL.

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

Какой самый эффективный / простой способ выполнить этот запрос в MySQL?

CREATE TABLE IF NOT EXISTS `events` (
  `EventID` int(10) unsigned NOT NULL auto_increment,
  `StartTime` datetime NOT NULL,
  `EndTime` datetime default NULL,
  PRIMARY KEY  (`EventID`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=37 ;


INSERT INTO `events` (`EventID`, `StartTime`, `EndTime`) VALUES
(10001, '2009-02-09 03:00:00', '2009-02-09 10:00:00'),
(10002, '2009-02-09 05:00:00', '2009-02-09 09:00:00'),
(10003, '2009-02-09 07:00:00', '2009-02-09 09:00:00');


# if the query was run using the data above,
# the table below would be the desired output

# Number of Overlapped Events | Total Amount of Time those events overlapped.
1, 03:00:00
2, 02:00:00
3, 02:00:00

Целью этих результатов является создание счета за использованные часы. (если у вас запущено одно мероприятие, вы можете заплатить 10 долларов в час. Но если два события запущены, вам нужно заплатить только 8 долларов в час, но только за период времени, когда у вас было два события.)

Ответы [ 3 ]

4 голосов
/ 25 января 2010

Попробуйте это:

SELECT `COUNT`, SEC_TO_TIME(SUM(Duration))
FROM (
    SELECT
        COUNT(*) AS `Count`,
        UNIX_TIMESTAMP(Times2.Time) - UNIX_TIMESTAMP(Times1.Time) AS Duration
    FROM (
        SELECT @rownum1 := @rownum1 + 1 AS rownum, `Time`
        FROM (
            SELECT DISTINCT(StartTime) AS `Time` FROM events
            UNION
            SELECT DISTINCT(EndTime) AS `Time` FROM events
        ) AS AllTimes, (SELECT @rownum1 := 0) AS Rownum
        ORDER BY `Time` DESC
    ) As Times1
    JOIN (
        SELECT @rownum2 := @rownum2 + 1 AS rownum, `Time`
        FROM (
            SELECT DISTINCT(StartTime) AS `Time` FROM events
            UNION
            SELECT DISTINCT(EndTime) AS `Time` FROM events
        ) AS AllTimes, (SELECT @rownum2 := 0) AS Rownum
        ORDER BY `Time` DESC
    ) As Times2
    ON Times1.rownum = Times2.rownum + 1
    JOIN events ON Times1.Time >= events.StartTime AND Times2.Time <= events.EndTime
    GROUP BY Times1.rownum
) Totals
GROUP BY `Count`

Результат:

1, 03:00:00
2, 02:00:00
3, 02:00:00

Если это не делает то, что вы хотите, или вам нужны объяснения, пожалуйста, дайте мне знать. Это можно сделать быстрее, сохранив повторный подзапрос AllTimes во временной таблице, но, надеюсь, он будет работать достаточно быстро.

0 голосов
/ 19 июля 2009

Я бы предложил структуру в памяти, которая имеет время начала, время окончания, # события ... (Это упрощается как время (часы), но использование времени unix дает вторую точность)

Для каждого события вы вставляете новое событие как есть, если нет перекрытия, в противном случае найдите перекрытие и разделите событие на (до 3) частей, которые могут перекрываться, например, данные вашего примера, начиная первое событие:

Событие 1 начинается в 3 часа ночи и заканчивается в 10 часов утра. Просто добавьте событие, так как нет совпадений:

    3,10,1

Событие 2 начинается в 5:00 и заканчивается в 9:00: перекрытия, поэтому разделите оригинал и добавьте новый с дополнительным «#events»

    3,5,1
    5,9,2
    9,10,1

Событие 3 начинается в 7:00 и заканчивается в 9:00: также перекрывается, проделайте то же самое со всеми периодами:

    3,5,1
    5,7,2
    7,9,3
    9,10,1

Итак, вычисляем часы перекрытия на # события:

1 event= (5-3)+(10-9)=3 hours
2 events = 7-5 = 2 hours
3 events = 9-7 = 2 hours

Имеет смысл запустить это как фоновый процесс, если есть много событий для сравнения.

0 голосов
/ 18 июля 2009

Начните с таблицы, которая содержит одно поле даты и времени в качестве первичного ключа, и заполните эту таблицу каждым значением времени, которое вас интересует. В високосных годах 527040 минут (31622400 секунд), поэтому эта таблица может стать большой, если ваши события охватывают несколько лет.

Теперь присоединяйтесь к этой таблице, делая что-то вроде

SELECT i.dt as instant, count(*) as events
FROM instant i JOIN event e ON i.dt BETWEEN e.start AND e.end
GROUP BY i.dt
WHERE i.dt BETWEEN ? AND ?

Наличие индекса для instant.dt может позволить вам отказаться от ORDER BY.

Если события добавляются не часто, это может быть то, что вы хотите предварительно рассчитать, запустив запрос в автономном режиме, заполнив отдельную таблицу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...