Общее время без наложения времени в SQL Server - PullRequest
0 голосов
/ 27 сентября 2019

Я использую SQL Server 2017.

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

данные:

========================================================
Group  | FromDate              |   ToDate
  1    | 2019-09-30 11:13:00   | 2019-09-30 11:13:50 
  1    | 2019-09-30 11:13:20   | 2019-09-30 11:14:10 
  2    | 2019-09-30 11:20:00   | 2019-09-30 11:20:20 
  1    | 2019-09-30 11:20:10   | 2019-09-30 11:20:20 
  3    | 2019-09-30 11:25:00   | 2019-09-30 11:25:30 
=========================================================

результат (в секундах):

========================
Group  | DurationTime
   1   | 80
   2   | 60
   3   | 30 
=========================

Я уже решил вычисление общей продолжительности, используя DATEDIFF.

Но я пропустил перекрывающееся время, как данные группы 1.

group  | FromDate              |   ToDate
  1    | 2019-09-30 11:13:00   | 2019-09-30 11:13:50 
  1    | 2019-09-30 11:13:20   | 2019-09-30 11:14:10 

Как рассчитать длительность без наложения времени?

Сложно и легко выполнять запросы с хорошей производительностью.

Ответы [ 6 ]

2 голосов
/ 27 сентября 2019

Со ссылкой на SQL-запросы для перекрывающихся периодов времени на SQL Server и на их основе ...

При сравнении двух периодов времени T1 и T2 существует пять возможных вариантов:

  1. T1 и T2 не пересекаются, они не перекрываются.
  2. T1 полностью охватывает T2.
  3. T2 полностью охватывает T1.
  4. T1 перекрываетначало T2.
  5. T2 перекрывает начало T1.

Это составляется, когда T3 вводится и может перекрывать любой, все или ни один из T1 и T2.

Начиная с данных вашего примера:

declare @Durations table (
    [Group] int not null,
    FromDate datetime not null,
    ToDate datetime not null
);
insert @Durations values
    (1, '2019-09-30 11:13:00', '2019-09-30 11:13:50'),
    (1, '2019-09-30 11:13:20', '2019-09-30 11:14:10'),
    (2, '2019-09-30 11:20:00', '2019-09-30 11:20:20'),
    (1, '2019-09-30 11:20:10', '2019-09-30 11:20:20'),
    (3, '2019-09-30 11:25:00', '2019-09-30 11:25:30');
select * from @Durations;
Group       FromDate                ToDate
----------- ----------------------- -----------------------
1           2019-09-30 11:13:00.000 2019-09-30 11:13:50.000
1           2019-09-30 11:13:20.000 2019-09-30 11:14:10.000
2           2019-09-30 11:20:00.000 2019-09-30 11:20:20.000
1           2019-09-30 11:20:10.000 2019-09-30 11:20:20.000
3           2019-09-30 11:25:00.000 2019-09-30 11:25:30.000

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

;with Chronologies as (
    select [Group],
        FromDate,
        ToDate,
        Chronology = row_number() over (partition by [Group] order by FromDate, ToDate)
    from @Durations
), CTE as (
    select  [Group], FromDate, ToDate, Chronology, 1 as Span
    from Chronologies
    where Chronology = 1

    union all

    select  p2.[Group],
        p2.FromDate,
        p2.ToDate,
        p2.Chronology,
        Span = case when
              (p1.FromDate between p2.FromDate and p2.ToDate) or
              (p1.ToDate between p2.FromDate and p2.ToDate) or
              (p1.FromDate < p2.FromDate and p1.ToDate > p2.ToDate) or
              (p1.FromDate > p2.FromDate and p1.ToDate < p2.ToDate)
              then p1.Span else (1 + p1.Span) end
    from CTE p1
    inner join Chronologies p2 on p2.[Group]=p1.[Group] and p2.Chronology=(1 + p1.Chronology)
)
select *
from CTE
order by [Group], Chronology;
Group       FromDate                ToDate                  Chronology           Span
----------- ----------------------- ----------------------- -------------------- -----------
1           2019-09-30 11:13:00.000 2019-09-30 11:13:50.000 1                    1
1           2019-09-30 11:13:20.000 2019-09-30 11:14:10.000 2                    1
1           2019-09-30 11:20:10.000 2019-09-30 11:20:20.000 3                    2
2           2019-09-30 11:20:00.000 2019-09-30 11:20:20.000 1                    1
3           2019-09-30 11:25:00.000 2019-09-30 11:25:30.000 1                    1

Мы можем использовать столбец Span для объединения периодов времени группы, то есть: group by [Group], Span позволяет нам использовать min(FromDate) и max(ToDate) для вычисления продолжительности данного периода с помощью datediff(), и мы можем sum() эти длительности, чтобы достичь вашего DurationTime результата ...

;with Chronologies as (
    select [Group],
        FromDate,
        ToDate,
        Chronology = row_number() over (partition by [Group] order by FromDate, ToDate)
    from @Durations
), CTE as (
    select  [Group], FromDate, ToDate, Chronology, 1 as Span
    from Chronologies
    where Chronology = 1

    union all

    select  p2.[Group],
        p2.FromDate,
        p2.ToDate,
        p2.Chronology,
        Span = case when
              (p1.FromDate between p2.FromDate and p2.ToDate) or
              (p1.ToDate between p2.FromDate and p2.ToDate) or
              (p1.FromDate < p2.FromDate and p1.ToDate > p2.ToDate) or
              (p1.FromDate > p2.FromDate and p1.ToDate < p2.ToDate)
              then p1.Span else (1 + p1.Span) end
    from CTE p1
    inner join Chronologies p2 on p2.[Group]=p1.[Group] and p2.Chronology=(1 + p1.Chronology)
)
select [Group], DurationTime = sum(datediff(second, FromDate, ToDate))
from (
    select  [Group], Span, FromDate=min(FromDate), ToDate=max(ToDate)
    from CTE
    group by [Group], Span
) Coalesced
group by [Group]
order by [Group];

Что дает нам окончательный результат:

Group       DurationTime
----------- ------------
1           80
2           20
3           30
1 голос
/ 27 сентября 2019

Вы можете попробовать метод, который называется "гапсы и острова":

declare @tbl table ([Group] int, FromDate datetime, ToDate datetime);
insert into @tbl values
(1,'2019-09-30 11:13:00','2019-09-30 11:13:50'), 
(1,'2019-09-30 11:13:20','2019-09-30 11:14:10'), 
(2,'2019-09-30 11:20:00','2019-09-30 11:20:20'), 
(1,'2019-09-30 11:20:10','2019-09-30 11:20:20'), 
(3,'2019-09-30 11:25:00','2019-09-30 11:25:30');

select [Group], sum(sec) from (
    select [Group], datediff(second, min(FromDate), max(ToDate)) sec
    from (
        select *,
               ROW_NUMBER() over (order by FromDate) -
                 ROW_NUMBER() over (partition by [Group] order by FromDate) grp
        from @tbl
    ) a group by [Group], grp
) a group by [Group]

Результат:

enter image description here

0 голосов
/ 27 сентября 2019

Алгоритм Марзулло (https://stackoverflow.com/a/58133814/12130544) является лучшим из моего опыта. Не только для расчета продолжительности события, но особенно для идентификации непересекающихся интервалов действия. Успешно использовал этот алгоритм для определения периодов, когда SKU являетсяв ассортименте магазина в рознице и для определения даты начала и окончания мобильной передачи данных клиента в телекоммуникациях. И для многих других сценариев. Настоятельно рекомендуем.

0 голосов
/ 27 сентября 2019

Я бы отнесся к этому как к проблеме пробелов и островов.Вы можете идентифицировать «острова», выполнив следующие действия:

  • Определите, где начинается «остров», что будет отрывом от всех предыдущих записей.
  • Выполните накопительную суммуиз них начинает идентифицировать группировку.
  • Агрегировать.

Вы можете сделать это с помощью оконных функций:

select groupid, min(fromdate), max(todate)
from (select t.*,
             sum(case when todate > prev_fromdate then 0 else 1 end) over
                 (partition by groupid order by fromdate) as grp
      from (select t.*,
                   max(todate) over (partition by groupid
                                     order by fromdate
                                     rows between unbounded preceding and 1 preceding
                                    ) as prev_fromdate
            from t
           ) t
     ) t
group by groupid, grp;

Затем вы можете агрегировать это с помощью groupid:

select groupid, sum(datediff(second, fromdate, todate))
from (select groupid, min(fromdate) as fromdate, max(todate) as todate
      from (select t.*,
                   sum(case when todate > prev_fromdate then 0 else 1 end) over
                       (partition by groupid order by fromdate) as grp
            from (select t.*,
                         max(todate) over (partition by groupid
                                           order by fromdate
                                           rows between unbounded preceding and 1 preceding
                                          ) as prev_fromdate
                  from t
                 ) t
           ) t
      group by groupid, grp
     ) t
group by groupid;

Здесь - это дБ <> скрипка.

0 голосов
/ 27 сентября 2019

SQL-реализация алгоритма Марзулло: https://en.wikipedia.org/wiki/Marzullo%27s_algorithm.

declare @tbl table ([Group] tinyint, FromDate datetime2(0), ToDate datetime2(0));
insert into @tbl values
(1, '2019-09-30 11:13:00', '2019-09-30 11:13:50'),
(1, '2019-09-30 11:13:20', '2019-09-30 11:14:10'),
(2, '2019-09-30 11:20:00', '2019-09-30 11:20:20'),
(1, '2019-09-30 11:20:10', '2019-09-30 11:20:20'),
(3, '2019-09-30 11:25:00', '2019-09-30 11:25:30'),
(4, '2019-10-01 23:59:30', '2019-10-02 00:00:30'), 
(4, '2019-09-30 10:00:00', '2019-09-30 10:01:00');


-- 5. Summary of the Duration per Group
select [group], sum(duration) as duration from (
--  -- 4. Calculate the duration
    select [group], datediff(second, min(dt), max(dt)) duration from (
--      -- 3. Make ranges
        select *, (row_number() over(partition by [group] order by dt)-1) / 2 rn from (
            -- 2. Cumulative summary
            select *, sum([index]) over(partition by [group] order by dt rows between unbounded preceding and current row) cumul 
            from (
                -- 1. Unpivot
                select [group], fromdate as dt, 1 as [index]  from @tbl
                union all
                select [group], todate, -1 from @tbl
            )s
        )s
        where ([index]=1 and cumul=1) OR ([index]=-1 and cumul=0)
    )s
    group by [group], rn
)s
group by [group]

Результат:

Group    Duration
-----    -----
1        80
2        20
3        30
4        120

Идея проста:

  1. Возьмите все даты (FromDate и ToDate) в один столбец, добавив второй столбец с индексом 1, если это FromDate, -1, если это ToDate.
  2. Совокупное суммирование индекса для поиска началаи конец диапазона.index = 1 и cumul = 1 - начало, index = -1, а cumul = 0 - конец
  3. Группировка дат по парам, в которых пара имеет даты начала и окончания
  4. Вычисление продолжительностидиапазон
  5. Сводка продолжительности на группу
0 голосов
/ 27 сентября 2019
SELECT [GROUP]
    ,sum(CONVERT(INTEGER, REPLACE(CONVERT(VARCHAR, TODATE, 24), ':', '')) - CONVERT(INTEGER, REPLACE(CONVERT(VARCHAR, FROMDATE, 24), ':', ''))) as DurationTime
FROM GROUPS
GROUP BY [GROUP]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...