MYSQL SUM продолжительности в течение последовательных часов - PullRequest
1 голос
/ 05 марта 2020

Использование более старой версии MySQL, где предложение WITH недопустимо.

Начиная с таблицы:

+--------+---------------------+---------------------+
| person | start_time          | end_time            |
+--------+---------------------+---------------------+
| Alice  | 2020-02-27 20:00:00 | 2020-02-27 20:59:59 |
| Alice  | 2020-02-27 23:45:00 | 2020-02-27 23:59:59 |
| Alice  | 2020-02-28 00:00:00 | 2020-02-28 00:59:59 |
| Alice  | 2020-02-28 01:00:00 | 2020-02-28 01:59:59 |
| Bob    | 2020-02-27 23:45:00 | 2020-02-27 23:59:59 |
| Cindy  | 2020-02-28 02:00:00 | 2020-02-28 02:59:59 |
| Cindy  | 2020-02-28 03:00:00 | 2020-02-28 03:36:59 |
+--------+---------------------+---------------------+

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

+--------+---------------------+---------------------+----------+
| person | start_time          | end_time            | duration |
+--------+---------------------+---------------------+----------+
| Alice  | 2020-02-27 20:00:00 | 2020-02-27 20:59:59 |     3599 |
| Alice  | 2020-02-27 23:45:00 | 2020-02-28 01:59:59 |     8064 |
| Bob    | 2020-02-27 23:45:00 | 2020-02-27 23:59:59 |      899 |
| Cindy  | 2020-02-28 02:00:00 | 2020-02-28 03:36:59 |     5806 |
+--------+---------------------+---------------------+----------+

Ответы [ 3 ]

3 голосов
/ 06 марта 2020

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

DROP TABLE IF EXISTS my_table;

CREATE TABLE my_table
(person VARCHAR(12) NOT NULL
,start_time DATETIME NOT NULL
,end_time DATETIME NOT NULL
,PRIMARY KEY(person,start_time)
);

INSERT INTO my_table VALUES
('Alice','2020-02-27 20:00:00','2020-02-27 20:59:59'),
('Alice','2020-02-27 23:45:00','2020-02-27 23:59:59'),
('Alice','2020-02-28 00:00:00','2020-02-28 00:59:59'),
('Alice','2020-02-28 01:00:00','2020-02-28 01:59:59'),
('Bob','2020-02-27 23:45:00','2020-02-27 23:59:59'),
('Cindy','2020-02-28 02:00:00','2020-02-28 02:59:59'),
('Cindy','2020-02-28 03:00:00','2020-02-28 03:36:59');

SELECT person
     , MIN(start_time) start_time
     , MAX(end_time) end_time
     , SUM(TIME_TO_SEC(TIMEDIFF(end_time,start_time))) delta 
  FROM 
     ( SELECT x.*
            , CASE WHEN person = @prev_person 
                   THEN CASE WHEN start_time <= @prev_end_time + INTERVAL 1 HOUR 
                             THEN @i:=@i 
                             ELSE @i:=@i+1 END 
                   ELSE @i:=1 END i
            , @prev_person := person
            , @prev_end_time := end_time
         FROM my_table x
            , (SELECT @prev_person := null, @prev_end_time := null, @i:=0) vars 
        ORDER 
           BY person
            , start_time
     ) a
 GROUP  
    BY person,i;
+--------+---------------------+---------------------+-------+
| person | start_time          | end_time            | delta |
+--------+---------------------+---------------------+-------+
| Alice  | 2020-02-27 20:00:00 | 2020-02-27 20:59:59 |  3599 |
| Alice  | 2020-02-27 23:45:00 | 2020-02-28 01:59:59 |  8097 |
| Bob    | 2020-02-27 23:45:00 | 2020-02-27 23:59:59 |   899 |
| Cindy  | 2020-02-28 02:00:00 | 2020-02-28 03:36:59 |  5818 |
+--------+---------------------+---------------------+-------+

FWIW, я думаю, что переписывание запроса таким образом приводит к его «версии зависимости» 1007 * ', то есть непроницаемым для справедливого обвинения, что порядок оценки элементов не гарантируется - но я могу ошибаться. Несмотря на это, в MySQL 8.0+ нижеследующее может быть переписано с расширенной функциональностью, предоставляемой этой версией.

SELECT person
     , MIN(start_time) start_time
     , MAX(end_time) end_time
     , SUM(TIME_TO_SEC(TIMEDIFF(end_time,start_time))) delta 
  FROM 
  ( SELECT * FROM
     ( SELECT x.*
            , CASE WHEN person = @prev_person 
                   THEN CASE WHEN start_time <= @prev_end_time + INTERVAL 1 HOUR 
                             THEN @i:=@i 
                             ELSE @i:=@i+1 END 
                   ELSE @i:=1 END i
            , @prev_person := person
            , @prev_end_time := end_time
         FROM my_table x
            , (SELECT @prev_person := null, @prev_end_time := null, @i:=0) vars 
     ) k
      ORDER 
                BY person
            , start_time
     ) a
 GROUP  
    BY person,i;
1 голос
/ 06 марта 2020

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

SELECT t.person,t.start_time,t.end_time,
SUM(TIMESTAMPDIFF(SECOND,t.start_time,t.end_time)) AS duration,
IF( EXISTS (SELECT * FROM test t1
WHERE t1.start_time=TIMESTAMPADD(SECOND,1,t.end_time) 
OR TIMESTAMPDIFF(SECOND,t.start_time,t1.end_time)=-1),1,0) AS continuous
FROM test t
WHERE TIMESTAMPDIFF(SECOND,t.start_time,t.end_time) 
BETWEEN 0 AND 3599 
GROUP BY t.person,continuous
ORDER BY t.person,t.start_time;

То же, что и

SELECT t.person,t.start_time,t.end_time,
SUM(TIMESTAMPDIFF(SECOND,t.start_time,t.end_time)) AS duration,
IF( EXISTS (SELECT * FROM test t1
WHERE t1.start_time=TIMESTAMPADD(SECOND,1,t.end_time) 
OR TIMESTAMPDIFF(SECOND,t1.end_time,t.start_time)=1),1,0) AS continuous
FROM test t
WHERE TIMESTAMPDIFF(SECOND,t.start_time,t.end_time) 
BETWEEN 0 AND 3599 
GROUP BY t.person,continuous
ORDER BY t.person,t.start_time;

Проверьте оба запроса в этом SQL Скрипка

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

На основании комментария @ Strawberry вышеуказанные запросы необходимо переписать с небольшим изменением.

SELECT t.person,t.start_time,t.end_time,
SUM(TIMESTAMPDIFF(SECOND,t.start_time,t.end_time)) AS duration,
IF( EXISTS (SELECT * FROM test t1
WHERE t1.start_time=TIMESTAMPADD(SECOND,1,t.end_time) 
OR TIMESTAMPDIFF(SECOND,t.start_time,t1.end_time)=-1),1,0) AS continuous
FROM test t
GROUP BY t.person,continuous
ORDER BY t.person,t.start_time;

Что совпадает с

SELECT t.person,t.start_time,t.end_time,
SUM(TIMESTAMPDIFF(SECOND,t.start_time,t.end_time)) AS duration,
IF( EXISTS (SELECT * FROM test t1
WHERE t1.start_time=TIMESTAMPADD(SECOND,1,t.end_time) 
OR TIMESTAMPDIFF(SECOND,t1.end_time,t.start_time)=1),1,0) AS continuous
FROM test t
GROUP BY t.person,continuous
ORDER BY t.person,t.start_time;

Проверьте оба запроса в этом SQL Fiddle

0 голосов
/ 06 марта 2020

Попытка в одном запросе была для меня нелегкой, но я сделал это с помощью собственной LEFT JOIN таблицы и набора условий в ON условии

SELECT A.Person,
       MIN(A.start_time) AS start_time,
       MAX(A.end_time) AS end_time,
       TIME_TO_SEC(TIMEDIFF(MAX(A.end_time),MIN(A.start_time)))  Duration,
       CASE WHEN B.person IS NULL THEN 0 ELSE 1 END AS chk 
FROM my_table A
LEFT JOIN my_table B 
ON A.person=B.person 
AND A.start_time - INTERVAL 1 HOUR < B.end_time -- when A.start_time minus 1 hour is smaller than B.end_time
AND A.end_time + INTERVAL 1 HOUR > B.start_time -- when A.end_time plus 1 hour is bigger than B.start_time 
AND A.start_time <> B.start_time -- when A.start_time is not same as B.start_time 
GROUP BY A.person,chk;

Базовый запрос был таким:

SELECT *,CASE WHEN b.person IS NULL THEN 0 ELSE 1 END AS chk
FROM my_table a LEFT JOIN my_table b 
ON a.person=b.person 
AND a.start_time - INTERVAL 1 HOUR < b.end_time
AND a.end_time + INTERVAL 1 HOUR > b.start_time
AND a.start_time <> b.start_time;

, которые возвращают следующий результат:

+ ------ + ------------------- + ------------------- + ------ + ------------------- + ------------------- + --- +
| person | start_time          | end_time            | person | start_time          | end_time            | chk |
+ ------ + ------------------- + ------------------- + ------ + ------------------- + ------------------- + --- +
| Alice  | 2020-02-27 20:00:00 | 2020-02-27 20:59:59 | NULL   |        NULL         |        NULL         | 0   |
| Alice  | 2020-02-28 00:00:00 | 2020-02-28 00:59:59 | Alice  | 2020-02-27 23:45:00 | 2020-02-27 23:59:59 | 1   |
| Alice  | 2020-02-27 23:45:00 | 2020-02-27 23:59:59 | Alice  | 2020-02-28 00:00:00 | 2020-02-28 00:59:59 | 1   |
| Alice  | 2020-02-28 01:00:00 | 2020-02-28 01:59:59 | Alice  | 2020-02-28 00:00:00 | 2020-02-28 00:59:59 | 1   |
| Alice  | 2020-02-28 00:00:00 | 2020-02-28 00:59:59 | Alice  | 2020-02-28 01:00:00 | 2020-02-28 01:59:59 | 1   |
| Bob    | 2020-02-27 23:45:00 | 2020-02-27 23:59:59 | NULL   |        NULL         |        NULL         | 0   |
| Cindy  | 2020-02-28 03:00:00 | 2020-02-28 03:36:59 | Cindy  | 2020-02-28 02:00:00 | 2020-02-28 02:59:59 | 1   |
| Cindy  | 2020-02-28 02:00:00 | 2020-02-28 02:59:59 | Cindy  | 2020-02-28 03:00:00 | 2020-02-28 03:36:59 | 1   |
+ ------ + ------------------- + ------------------- + ------ + ------------------- + ------------------- + --- +

P / S: Спасибо Strawberry за пример структуры таблицы и данных.

Редактировать: После комментария Строберри я согласен с тем, что мой предыдущий запрос фактически не вычисляет правильную длительность, потому что я просто беру TIMEDIFF между MAX(end_date) и MIN(start_date). Я внес некоторые изменения, и обновленный запрос приведен ниже:

SELECT person,
       MIN(CASE WHEN starttime=0 THEN start_time ELSE starttime END) AS starttime,
       MAX(CASE WHEN endtime=0 THEN end_time ELSE endtime END) AS endtime,
       SUM(duration) AS duration,
       CASE WHEN starttime=0 THEN 0 ELSE 1 END AS chk 
FROM
 (SELECT a.person, a.start_time,a.end_time,
         ANY_VALUE(CASE WHEN b.start_time > a.end_time + INTERVAL 1 HOUR THEN 0 
                        WHEN b.start_time IS NULL THEN a.start_time
                        ELSE a.start_time END) starttime,
         ANY_VALUE(CASE WHEN b.start_time > a.end_time + INTERVAL 1 HOUR THEN 0
                        WHEN b.start_time IS NULL THEN a.end_time
                        ELSE a.end_time END) endtime,
         TIME_TO_SEC(TIMEDIFF(a.end_time,a.start_time)) duration
    FROM my_table a 
LEFT JOIN my_table b ON a.person=b.person AND b.start_time > a.end_time
GROUP BY a.person,a.start_time,a.end_time) TT
GROUP BY person,chk;

Вот скрипка: https://www.db-fiddle.com/f/8XHWhfhCYSj8zcFcmo2KUo/1

P / S: я добавил Еще одна запись «Боба» в скрипке для целей тестирования.

Это немного похоже на предыдущую, только на этот раз я переместил большую часть условия ON в SELECT. Я также использую ANY_VALUE, чтобы обойти sql_mode=only_full_group_by. С другой стороны, если sql_mode выключен, ANY_VALUE() не требуется. Обратите внимание, что если вы используете MariaDB, он не поддерживает ANY_VALUE().

...