SQL рассчитывает общее время простоя минут - PullRequest
1 голос
/ 04 апреля 2019

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

-ID
-DateOpen
-DateClosed
-Total

Я хочу получить сумму минутв день, принимая во внимание, что билеты могут быть одновременными, например:

ID    |     DateOpen                    |        DateClosed       | Total 
1          2019-04-01 08:00:00 AM            2019-04-01 08:45:00    45
2          2019-04-01 08:10:00 AM            2019-04-01 08:20:00    10
3          2019-04-01 09:06:00 AM            2019-04-01 09:07:00    1
4          2019-04-01 09:06:00 AM            2019-04-01 09:41:00    33

Кто-то может помочь мне с этим, пожалуйста!: c

Если я использую запрос "SUM", он вернет 89, но если вы увидите даты, вы поймете, что фактический результат должен быть 78, потому что билеты 2 и 3 были запущены, в то время как другой билетработал ...

DECLARE @DateOpen date = '2019-04-01'

SELECT AlarmID, DateOpen, DateClosed, TDT FROM AlarmHistory 
WHERE CONVERT(date,DateOpen) = @DateOpen

Ответы [ 4 ]

1 голос
/ 04 апреля 2019

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

Вот пример, который будет работать с MySQL:

 SET @row_num = 0;

 SELECT COUNT(DISTINCT time_stamp)
         -- this simulates your dateopen and dateclosed table
   FROM (SELECT '2019-04-01 08:00:00' open_time, '2019-04-01 08:45:00' close_time
         UNION SELECT '2019-04-01 08:10:00', '2019-04-01 08:20:00'
         UNION SELECT '2019-04-01 09:06:00', '2019-04-01 09:07:00'
         UNION SELECT '2019-04-01 09:06:00', '2019-04-01 09:41:00') times_used
   JOIN (
         -- generate sequence of minutes in day
         SELECT TIME(sequence*100) time_stamp
           FROM (
             -- create sequence 1 - 10000
                 SELECT (@row_num:=@row_num + 1) AS sequence
                   FROM {table_with_10k+_records}
                  LIMIT 10000
                ) minutes
         HAVING time_stamp IS NOT NULL
          LIMIT 1440
       ) times ON (time_stamp >= TIME(open_time) AND time_stamp < TIME(close_time));         

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

ПРИМЕЧАНИЕ. В зависимости от вашей базы данных может быть лучший способ создания последовательности.MySQL не имеет функции генерации последовательности. Я сделал это таким образом, чтобы показать основную идею, которую можно легко преобразовать для работы с любой базой данных, которую вы используете.

0 голосов
/ 10 апреля 2019

Другой подход, использует разрывы и островки . Ответ основан на SQL Time Packing of Islands

Тест в реальном времени: http://sqlfiddle.com/#!18/462ac/11

with gap_detector as
(
    select 
        DateOpen, DateClosed,                 
        case when 
            lag(DateClosed) over (order by DateOpen) is null 
            or lag(DateClosed) over (order by DateOpen) < DateOpen 
        then 
            1
        else 
            0 
        end as gap
    from dt              
)
, downtime_grouper as
(
    select 
        DateOpen, DateClosed, 
        sum(gap) over (order by DateOpen) as downtime_group
    from gap_detector
)
 -- group's open and closed detector. then computes the group's downtime
select 
    downtime_group,
    min(DateOpen) as group_date_open, 
    max(DateClosed) as group_date_closed,
    datediff(minute, min(DateOpen), max(DateClosed)) as group_downtime,

    sum(datediff(minute, min(DateOpen), max(DateClosed))) 
        over(order by downtime_group) as downtime_running_total
from downtime_grouper
group by downtime_group

Выход:

enter image description here

Как это работает

DateOpen - это начало серии простоя, если у него нет предыдущего простоя (обозначено нулем lag(DateClosed)). DateOpen также является началом ряда простоев, если он имеет разрыв с датой предыдущего простоя DateClosed.

with gap_detector as
(
    select 
        lag(DateClosed) over (order by DateOpen) as previous_downtime_date_closed,
        DateOpen, DateClosed,                 
        case when 
            lag(DateClosed) over (order by DateOpen) is null 
            or lag(DateClosed) over (order by DateOpen) < DateOpen 
        then 
            1
        else 
            0 
        end as gap
    from dt              
)
select *
from gap_detector
order by DateOpen;

Выход:

enter image description here

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

with gap_detector as
(
    select 
        DateOpen, DateClosed,                 
        case when 
            lag(DateClosed) over (order by DateOpen) is null 
            or lag(DateClosed) over (order by DateOpen) < DateOpen 
        then 
            1
        else 
            0 
        end as gap
    from dt              
)
select 
   DateOpen, DateClosed, gap, 
   sum(gap) over (order by DateOpen) as downtime_group
from gap_detector
order by DateOpen;

enter image description here

Как видно из вышеприведенного вывода, теперь мы можем легко обнаружить самые ранние DateOpen и самые поздние DateClosed группы простоя, применяя MIN(DateOpen) и MAX(DateClosed) путем группировки по downtime_group. Для downtime_group 1 у нас есть самый ранний DateOpen 08:00 и последний DateClosed из 08:45. Для downtime_group 2 у нас есть самый ранний DateOpen 09:06 и последний DateClosed из 9:41. И из этого мы можем пересчитать правильное время простоя, даже если есть одновременные простои.


Мы можем сделать код короче, исключив обнаружение нулевого предыдущего простоя (текущая строка, которую мы оцениваем, является первой строкой в ​​таблице), изменив логику. Вместо того, чтобы обнаруживать промежутки, мы обнаруживаем острова (непрерывные простои). Что-то является смежным, если DateClosed предыдущего простоя перекрывает DateOpen текущего простоя, обозначенного 0. Если он не перекрывается, то это разрыв, обозначенный 1.

Вот запрос:

Тест в реальном времени: http://sqlfiddle.com/#!18/462ac/12

with gap_detector as
(
    select 
        DateOpen, DateClosed,                 
        case when lag(DateClosed) over (order by DateOpen) >= DateOpen 
        then 
            0
        else 
            1 
        end as gap
    from dt              
)
, downtime_grouper as
(
    select 
        DateOpen, DateClosed, 
        sum(gap) over (order by DateOpen) as downtime_group
    from gap_detector
)
 -- group's open and closed detector. then computes the group's downtime
select 
    downtime_group,
    min(DateOpen) as group_date_open, 
    max(DateClosed) as group_date_closed,
    datediff(minute, min(DateOpen), max(DateClosed)) as group_downtime,

    sum(datediff(minute, min(DateOpen), max(DateClosed))) 
        over(order by downtime_group) as downtime_running_total
from downtime_grouper
group by downtime_group

Если вы используете SQL Server 2012 или выше:

iif(lag(DateClosed) over (order by DateOpen) >= DateOpen, 0, 1) as gap
0 голосов
/ 04 апреля 2019

Кстати, как сказал MarcinJ, 41 - 6 - это 35, а не 33. Таким образом, ответ - 80, а не 78.

Следующее решение будет работать, даже если параметр даты не одинтолько день (1440 минут).Скажем, если параметром даты является месяц или даже год, это решение все равно будет работать.

Демонстрация в реальном времени: http://sqlfiddle.com/#!18/462ac/5

-- arranged the opening and closing downtime
with a as 
(
    select 
        DateOpen d, 1 status
    from dt
    union all
    select
        DateClosed, 2
    from dt
)
-- don't compute the downtime from previous date
-- if the current date's status is opened
-- yet the previous status is closed
, downtime_minutes AS
(
    select 
        *, 
        lag(status) over(order by d, status desc) as prev_status,
        case when status = 1 and lag(status) over(order by d, status desc) = 2 then
            null
        else
            datediff(minute, lag(d) over(order by d, status desc), d)
        end as downtime
    from a
)
select sum(downtime) as all_downtime from downtime_minutes;

Вывод:

| all_downtime |
|--------------|
|           80 |

Посмотрите, как это работает:

enter image description here

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

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

Может сделатькороче код, изменив условие:

-- arranged the opening and closing downtime
with a as 
(
    select 
        DateOpen d, 1 status
    from dt
    union all
    select
        DateClosed, 2
    from dt
    -- order by d. postgres can do this?
)
-- don't compute the downtime from previous date
-- if the current date's status is opened
-- yet the previous status is closed
, downtime_minutes AS
(
    select 
        *, 
        lag(status) over(order by d, status desc) as prev_status,
        case when not ( status = 1 and lag(status) over(order by d, status desc) = 2 ) then
            datediff(minute, lag(d) over(order by d, status desc), d)
        end as downtime
    from a
)
select sum(downtime) from downtime_minutes;

Не особенно горжусь моим оригинальным решением: http://sqlfiddle.com/#!18/462ac/1

Что касается status desc в order by d, status desc, если DateClosed аналогичен DateOpen других простоев, status desc сначала отсортирует DateClosed.

Для этих данных, где 8:00 присутствует в обоихDateOpened и DateClosed:

INSERT INTO dt
    ([ID], [DateOpen], [DateClosed], [Total])
VALUES
    (1, '2019-04-01 07:00:00', '2019-04-01 07:50:00', 50),
    (2, '2019-04-01 07:45:00', '2019-04-01 08:00:00', 15),   
    (3, '2019-04-01 08:00:00', '2019-04-01 08:45:00', 45);
;

Для аналогичного времени (например, 8:00), если мы не будем сначала сортировать закрытие перед открытием, то 7:00 будут вычисляться только до 7:50,вместо 8:00, так как время простоя 8: 00 - изначально нулевое.Вот как устроены и рассчитываются время простоя и закрытие, если на аналогичную дату нет status desc, например, 8:00.Общее время простоя составляет всего 95 минут, что неправильно.Это должно быть 105 минут.

enter image description here

Вот как это будет организовано и вычислено, если мы сначала отсортируем DateClosed перед DateOpen (используя status desc) когда у них аналогичная дата, например, 8:00.Общее время простоя составляет 105 минут, что является правильным.

enter image description here

0 голосов
/ 04 апреля 2019

ответ @ drakin8564 адаптирован для SQL Server, который, я полагаю, вы используете:

;WITH Gen AS
(
    SELECT TOP 1440 
           CONVERT(TIME, DATEADD(minute, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)), '00:00:00')) AS t
      FROM sys.all_objects a1
     CROSS
      JOIN sys.all_objects a2
)
SELECT COUNT(DISTINCT t)
  FROM incidents inci
  JOIN Gen 
    ON Gen.t >= CONVERT(TIME, inci.DateOpen) 
   AND Gen.t < CONVERT(TIME, inci.DateClosed)

Ваша итоговая сумма за последнюю запись неверна, говорит 33, а это 35, так что запрос приводит к 80, а не 78.

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