我正在使用一个停机时间管理系统,该系统能够在数据库中保存有关问题的支持单,我的数据库包含以下列:
-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
答案 0 :(得分:1)
您需要做的是生成一个整数序列,并使用该整数序列生成一天中的时间。在开始和结束日期之间加入该时间序列,然后计算不同时间的数量。
以下是可与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没有生成序列函数,我这样做是为了显示基本思想,可以很容易地将其转换为与正在使用的任何数据库一起工作。
答案 1 :(得分:0)
@drakin8564's answer适用于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。
答案 2 :(得分:0)
顺便说一句,就像MarcinJ告诉你的一样,41-6是35,而不是33。所以答案是80,而不是78。
即使date参数不是仅一天(1,440分钟),以下解决方案也将起作用。假设date参数是一个月,甚至是一年,此解决方案仍然可以使用。
实时演示: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 |
查看其工作原理:
它通过计算先前的停机时间来停机。如果当前日期的状态为打开并且前一个日期的状态为关闭,则不要计算停机时间,这意味着当前停机时间是不重叠的。非重叠停机时间用null表示。
对于新打开的停机时间,其停机时间最初为零,停机时间将在后续日期计算,直到关闭为止。
可以通过反转条件来缩短代码:
-- 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进行排序。
对于DateOpened和DateClosed均为8:00的数据:
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-open的停机时间最初为零。如果没有类似日期(例如8:00)的status desc
,则这是打开和关闭停机时间的安排和计算方式。总停机时间仅为95分钟,这是错误的。应该是105分钟。
如果我们在日期相似的日期(例如8:00)之前首先对DateClosed排序(通过使用status desc
,然后在DateOpen之前对其进行排序,则将如何安排和计算日期)。总停机时间为105分钟,这是正确的。
答案 3 :(得分:0)
另一种方法,使用gaps and islands方法。答案基于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
输出:
工作方式
如果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;
输出:
在检测到间隙启动器之后,我们对间隙进行了总计运行,因此我们可以将彼此相邻的停机时间进行分组。
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;
从上面的输出中可以看到,我们现在可以通过对downtime_group进行分组来应用MIN(DateOpen)
和MAX(DateClosed)
来轻松检测停机组的最早的DateOpen和最新的DateClose。在downtime_group 1上,我们最早的DateOpen是08:00
,而最新的DateClosed是08:45
。在downtime_group 2上,我们最早的09:06
的DateOpen和最新的9:41
的DateClosed。这样,即使同时发生停机,我们也可以重新计算正确的停机时间。
我们可以通过反转逻辑来消除对先前空停机时间(检测到的当前行是表中的第一行)的检测,从而缩短代码长度。我们没有检测间隙,而是检测孤岛(连续的停机时间)。如果先前的停机时间的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