【问题标题】:Duration overlap causing double counting持续时间重叠导致重复计算
【发布时间】:2015-10-30 17:09:35
【问题描述】:

我正在使用 SQL Server Management Studio 2008 创建查询。用于创建报告的 Reporting Services 2008。

几周以来,我一直在努力解决这个问题,但我遇到了障碍。我希望有人能够提出解决方案,因为现在我的大脑已经变成糊状了。

我目前正在开发一个 SQL 查询,它将向 Reporting Services 报告提供数据。该报告的目的是显示我们所在县周边地区急救提供者的可用百分比。我们的想法是,在我们的 20 个地点中的每一个,一次都应该只有一名急救人员提供掩护。

这一切都很好,除了一个位置的急救人员在每个掩护期的开始和结束时重叠了他们的掩护。

封面重叠示例:

|位置 |开始日期 |结束日期 | +----------+----------+---------------- -----+ |灯芯 | 22/06/2015 09:00:00 | 22/06/2015 19:00:00 | |灯芯 | 22/06/2015 18:30:00 | 23/06/2015 09:00:00 | |灯芯 | 23/06/2015 09:00:00 | 23/06/2015 18:30:00 | |灯芯 | 23/06/2015 18:00:00 | 24/06/2015 09:00:00 | +----------+----------+---------------- -----+

在一个完美的世界中,他们设置掩护的数据库不允许他们这样做,但它是一个外部开发的数据库,不允许我们对其进行类似的更改。我们也不允许创建函数、存储过程、计数表等......

查询本身应返回每个地点有急救覆盖的分钟数,然后分解为一天中的小时数。封面上的任何重叠都不应最终增加额外的封面,而应合并。一次可以一个人,如果他们重叠,那么它应该只算一个人很多掩护。

示例输出:

+----------+----------+---------------- -----+------------+--------------+--------+--------+- -----+----------+ |位置 |来自Dt |到Dt |时差 |可用性 |第N天 |天号 |小时 |天数 | +----------+----------+---------------- -----+------------+--------------+--------+--------+- -----+----------+ |灯芯 | 22/06/2015 18:00:00 | 22/06/2015 18:59:59 | 59 | 100 |星期一 | 1 | 18 | 0 | |灯芯 | 22/06/2015 18:30:00 | 22/06/2015 18:59:59 | 29 | 50 |星期一 | 1 | 18 | 0 | |灯芯 | 22/06/2015 19:00:00 | 22/06/2015 19:59:59 | 59 | 100 |星期一 | 1 | 19 | 0 | +----------+----------+---------------- -----+------------+--------------+--------+--------+- -----+----------+

示例代码:

    DECLARE  
      @StartTime datetime,  
      @EndTime datetime, 
      @GivenDate datetime; 


 SET @GivenDate = '2015-06-22'; 
 SET @StartTime = @GivenDate + ' 00:00:00'; 
 SET @EndTime = '2015-06-23' + ' 23:59:59'; 

Declare @Sample Table
(
Location Varchar(50),
StartDate Datetime,
EndDate Datetime
)

Insert @Sample

Select
sta.location,
act.Start,
act.END

from emp,
con,
sta,
act

where 
emp.ID = con.ID
and con.location = sta.location
and SUBSTRING(sta.ident,3,2) in ('51','22')
and convert(varchar(10),act.start,111) between @GivenDate and @EndTime
and act.ACT= 18
group by sta.location,
act.Start,
act.END
order by 2

;WITH Yak (location, fromDt, toDt, maxDt,hourdiff) 
AS ( 
SELECT location, 
StartDate, 
/*check if the period of cover rolls onto the next hour */
    convert(datetime,convert(varchar(21),
    CONVERT(varchar(10),StartDate,111)+' '
    +convert(varchar(2),datepart(hour,StartDate))+':59'+':59'))
,
EndDate
,dateadd(hour,1,dateadd(hour, datediff(hour, 0, StartDate), 0))-StartDate
FROM @Sample

UNION ALL 

SELECT location, 
dateadd(second,1,toDt), 
dateadd(hour, 1, toDt),
maxDt,
hourdiff 
FROM Yak 
WHERE toDt < maxDt 
) ,

TAB1 (location, FROMDATE,TODATE1,TODATE) AS
(SELECT
location,
@StartTime,
convert(datetime,convert(varchar(21),
        CONVERT(varchar(10),@StartTime,120)+' '
        +convert(varchar(2),datepart(hour,@StartTime))+':59'+':59.999')),
@EndTime 

from @Sample

UNION ALL
SELECT 
location,
(DATEADD(hour, 1,(convert(datetime,convert(varchar(21),
        CONVERT(varchar(10),FROMDATE,120)+' '
        +convert(varchar(2),datepart(hour,FROMDATE))+':00'+':00.000')))))ToDate,
(DATEADD(hour, 1,(convert(datetime,convert(varchar(21),
        CONVERT(varchar(10),TODATE1,120)+' '
        +convert(varchar(2),datepart(hour,TODATE1))+':59'+':59.999'))))) Todate1,
TODATE
FROM TAB1 WHERE TODATE1 < TODATE
),
/*CTE Tab2 adds zero values to all possible hours between start and end dates */
TAB2 AS
(SELECT location, FROMDATE,
CASE WHEN TODATE1 > TODATE THEN TODATE ELSE TODATE1 END AS TODATE
FROM TAB1)

SELECT location, 
fromDt, 
/* Display MaxDT as start time if cover period goes into next dat */
CASE WHEN toDt > maxDt THEN maxDt ELSE toDt END AS toDt,
/* If the end date is on the next day find out the minutes between the start date and the end of the day or find out the minutes between the next day and the end date */
Case When ToDt > Maxdt then datediff(mi,fromDt,maxDt) else datediff(mi,FromDt,ToDt) end as TimeDiff,
Case When ToDt > Maxdt then round(datediff(S,fromDt,maxDt)/3600.0*100,0) else round(datediff(S,FromDt,ToDt)/3600.0*100.0,0) end as Availability,
/*Display the name of the day of the week*/
CASE WHEN toDt > maxDt THEN datename(dw,maxDt) ELSE datename(dw,fromDt) END AS DayN,
CASE WHEN toDt > maxDt THEN case when datepart(dw,maxDt)-1 = 0 then 7 else datepart(dw,maxDt)-1 end  ELSE case when datepart(dw,fromDt)-1 = 0 then 7 else  datepart(dw,fromDt)-1 END  end AS DayNo
,DATEPART(hour, fromDt) as Hour,
'0' as DayCount
FROM Yak 
where Case When ToDt > Maxdt then datediff(mi,fromDt,maxDt) else datediff(mi,FromDt,ToDt) end <> 0

group by location,fromDt,maxDt,toDt

Union all

SELECT
tab2.location, 
convert(varchar(19),Tab2.FROMDATE,120),
convert(varchar(19),Tab2.TODATE,120),
'0',
'0',
datename(dw,FromDate) DayN,
case when datepart(dw,FromDate)-1 = 0 then 7 else datepart(dw,FromDate)-1 end AS DayNo,
DATEPART(hour, fromDate) as Hour,
COUNT(distinct datename(dw,fromDate))
FROM TAB2

Where datediff(MINUTE,convert(varchar(19),Tab2.FROMDATE,120),convert(varchar(19),Tab2.TODATE,120)) > 0

group by location, TODATE, FROMDATE 

Order by 2

option (maxrecursion 0)

我尝试了以下论坛条目,但它们在我的情况下不起作用: http://forums.teradata.com/forum/general/need-help-merging-consecutive-and-overlapping-date-spans

Checking for time range overlap, the watchman problem [SQL]

Calculate Actual Downtime ignoring overlap in dates/times

很抱歉这么长,但我想我会尽力为您提供尽可能详细的信息。任何帮助将不胜感激。谢谢。

【问题讨论】:

  • 您的示例输出没有多大意义,它充满了重叠。我的想法是消除重叠。
  • 如果结束时间与开始时间重叠,是否可以忽略结束时间?
  • Bulat - 这是当前数据的输出。目前我还没有找到消除重叠的方法。
  • MolleyC - 目前我不知道如何运行测试以找到重叠。理想情况下,如果发现时间重叠,则采用最早的开始日期和最晚的结束日期。

标签: sql sql-server reporting-services overlap availability


【解决方案1】:

所以我想出的解决方案使用临时表,您可以轻松地将其更改为 CTE,这样您就可以避免使用存储过程。

我尝试使用窗口函数来查找重叠记录并获取最小和最大时间,问题是您在哪里有重叠链接,例如09:00 - 09:10, 09:05 - 09:15, 09:11 - 09:20,因此涵盖了从 09:00 到 09:20 的所有分钟,但几乎不可能说出 09:00 - 09 :10 与 09:11 - 09:20 相关,而不是递归遍历结果,直到您到达链的底部。 (希望这是有道理的)。

所以我将所有日期范围分解为 StartDate 和 EndDate 之间的每一分钟,然后您可以使用 ROW_NUMBER() 窗口函数来捕获任何重复项,然后您可以使用它来查看有多少不同的人覆盖了同一分钟。

CREATE TABLE dbo.dates
(
Location VARCHAR(64),
StartDate DATETIME,
EndDate DATETIME
);

INSERT INTO dbo.dates VALUES
('Wick','20150622 09:00:00','20150622 19:00:00'),
('Wick','20150622 18:30:00','20150624 09:00:00'),
('Wick','20150623 09:00:00','20150623 18:30:00'),
('Wick','20150623 18:00:00','20150624 09:00:00'),
('Wick','20150630 09:00:00','20150630 09:30:00'),
('Wick','20150630 09:00:00','20150630 09:45:00'),
('Wick','20150630 09:10:00','20150630 09:25:00'),
('Wick','20150630 09:35:00','20150630 09:55:00'),
('Wick','20150630 09:57:00','20150630 10:10:00');

SELECT ROW_NUMBER() OVER (PARTITION BY Location ORDER BY StartDate) [Id],
Location,
StartDate,
EndDate
INTO dbo.overlaps
FROM dbo.dates;

SELECT TOP 10000 N=IDENTITY(INT, 1, 1)
INTO dbo.Num
FROM master.dbo.syscolumns a CROSS JOIN master.dbo.syscolumns  b;

SELECT 0 [N] INTO dbo.Numbers;

INSERT INTO dbo.Numbers SELECT * FROM dbo.Num;

SELECT  [Location]      = raw.Location,
        [WorkedDate]    = CAST([MinuteWorked] AS DATE),
        [DayN]          = DATENAME(WEEKDAY, [MinuteWorked]),
        [DayNo]         = DATEPART(WEEKDAY, [MinuteWorked]) -1,
        [Hour]          = DATEPART(HOUR, [MinuteWorked]),
        [MinutesWorked] = SUM(IIF(raw.[Minutes] = 1, 1, 0)),
        [MaxWorkers]    = MAX(raw.[Minutes])
FROM
(
SELECT
  o.Location,
  DATEADD(MINUTE, n.N, StartDate) [MinuteWorked],
  ROW_NUMBER() OVER (PARTITION BY o.Location, DATEADD(MINUTE, n.N, StartDate) ORDER BY DATEADD(MINUTE, n.N, StartDate)) [Minutes]
FROM dbo.overlaps o
INNER JOIN dbo.Numbers n ON n.N < DATEDIFF(MINUTE, StartDate, EndDate)
) raw
GROUP BY
    raw.Location,
    CAST([MinuteWorked] AS DATE),
    DATENAME(WEEKDAY, [MinuteWorked]),
    DATEPART(WEEKDAY, [MinuteWorked]) - 1,
    DATEPART(HOUR, [MinuteWorked])

这是结果的一个子集:

Location    WorkedDate  DayN        DayNo   Hour    MinutesWorked   MaxWorkers
Wick        2015-06-24  Wednesday   3       8       60              2
Wick        2015-06-30  Tuesday     2       9       58              3
Wick        2015-06-30  Tuesday     2       10      10              1

Here's小提琴

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多