SQL如何计算日期记录之间发生的人口普查点

时间:2017-11-08 11:13:40

标签: sql sql-server sql-server-2008 tsql

我正在使用MS-SQL-2008 R2尝试编写一个脚本,用于计算在任意一天,2个人口普查点:午夜和09:00占用的病床数。

我正在使用病房Ward Stays的数据集。基本上,表格中的每一行都是患者在单个病房住院的记录,记录病人入住病房的日期/时间,以及病人离开病房的日期/时间。

此表格的示例如下:

Ward_Stay_Primary_Key |   Ward_Start_Date_Time  |   Ward_End_Date_Time
          1           | 2017-09-03 15:04:00.000 | 2017-09-27 16:55:00.000
          2           | 2017-09-04 18:08:00.000 | 2017-09-06 18:00:00.000
          3           | 2017-09-04 13:00:00.000 | 2017-09-04 22:00:00.000
          4           | 2017-09-04 20:54:00.000 | 2017-09-08 14:30:00.000
          5           | 2017-09-04 20:52:00.000 | 2017-09-13 11:50:00.000
          6           | 2017-09-05 13:32:00.000 | 2017-09-11 14:49:00.000
          7           | 2017-09-05 13:17:00.000 | 2017-09-12 21:00:00.000
          8           | 2017-09-05 23:11:00.000 | 2017-09-06 17:38:00.000
          9           | 2017-09-05 11:35:00.000 | 2017-09-14 16:12:00.000
          10          | 2017-09-05 14:05:00.000 | 2017-09-11 16:30:00.000

这里要注意的关键是患者的病房住院时间可以从几小时到几天不等。

通过以下代码,我可以通过在案例陈述中指定日期来计算任何一天两个人口普查点的床位数:

SELECT 
     '05/09/2017' [Date]
    ,SUM(case when Ward_Start_Date_Time <= '05/09/2017 00:00:00.000' AND (Ward_End_Date_Time >= '05/09/2017 00:00:00.000' OR Ward_End_Date_Time IS NULL)then 1 else 0 end)[No. Beds Occupied at 00:00]
    ,SUM(case when Ward_Start_Date_Time <= '05/09/2017 09:00:00.000' AND (Ward_End_Date_Time >= '05/09/2017 09:00:00.000' OR Ward_End_Date_Time IS NULL)then 1 else 0 end)[No. Beds Occupied at 09:00]  

FROM 
    WardStaysTable

并且,基于上面的示例10记录,生成此输出:

   Date     | No. Beds Occupied at 00:00 | No. Beds Occupied at 09:00
05/09/2017  |             4              |             4

要执行此任意天数显然是繁重的,所以我要创建的是一个查询,我可以指定一个开始/结束日期参数(例如,9月1日至5日),然后查询到那时评估每条记录的Ward_Start_Date_Time和Ward_End_Date_Time变量,并按日期参数中定义的日期进行分组 - 每次00:00:00.000和09:00:00.000人口普查点落在这两个变量之间时,计算输出沿着这些方向(基于以上10条记录):

   Date    | No. Beds Occupied at 00:00 | No. Beds Occupied at 09:00     
01/09/2017 |             0              |            0
02/09/2017 |             0              |            0     
03/09/2017 |             0              |            0
04/09/2017 |             1              |            1
05/09/2017 |             4              |            4

我接近这个(也许天真)认为如果我使用cte创建一个日期表(由输入参数定义),以及相关的午夜和上午9点的人口普查日期/时间点,那么我可以使用这些用于分组和评估数据集的变量。

因此,此代码生成分组日期和人口普查日期/时间点:

DECLARE 
  @StartDate DATE = '01/09/2017'
 ,@EndDate DATE = '05/09/2017'
 ,@0900 INT = 540

SELECT  
     DATEADD(DAY, nbr - 1, @StartDate) [Date]
    ,CONVERT(DATETIME,(DATEADD(DAY, nbr - 1, @StartDate))) [MidnightDate]
    ,DATEADD(mi, @0900,(CONVERT(DATETIME,(DATEADD(DAY, nbr - 1, @StartDate))))) [0900Date]

FROM    
( 
SELECT  
ROW_NUMBER() OVER ( ORDER BY c.object_id ) AS nbr
FROM      sys.columns c
) nbrs

WHERE   nbr - 1 <= DATEDIFF(DAY, @StartDate, @EndDate)

我遇到的绊脚石是如何将cte加入到WardStays数据集中,因为没有合适的密钥...我已经尝试过使用子查询进行一些迭代来完成这项工作,但要么我采取了错误的做法或者我的语法乱七八糟。

简单来说,我试图创建以获得输出的逻辑类似于:

SELECT
[Date]
,SUM (case when  WST.Ward_Start_Date_Time <= [MidnightDate] AND (WST.Ward_End_Date_Time >= [MidnightDate] OR WST.Ward_End_Date_Time IS NULL then 1 else 0 end) [No. Beds Occupied at 00:00]
,SUM (case when  WST.Ward_Start_Date_Time <= [0900Date] AND (WST.Ward_End_Date_Time >= [0900Date] OR WST.Ward_End_Date_Time IS NULL then 1 else 0 end) [No. Beds Occupied at 09:00]

FROM WardStaysTable WST

GROUP BY [Date]

以上是否有可能,或者我是在咆哮错误的树,需要采取不同的方法吗?感谢任何建议。

2 个答案:

答案 0 :(得分:2)

我希望这样的事情:

WITH dates as (
      SELECT CAST(@StartDate as DATETIME) as dte
      UNION ALL
      SELECT DATEADD(DAY, 1, dte)
      FROM dates
      WHERE dte < @EndDate
     )
SELECT dates.dte [Date],
       SUM(CASE WHEN Ward_Start_Date_Time <= dte AND
                     Ward_END_Date_Time >= dte
                THEN 1 ELSE 0
           END) as num_beds_0000,
       SUM(CASE WHEN Ward_Start_Date_Time <= dte + CAST('09:00' as DATETIME) AND
                     Ward_END_Date_Time >= dte + CAST('09:00' as DATETIME)
                THEN 1 ELSE 0
           END) as num_beds_0900
FROM dates LEFT JOIN
     WardStaysTable wt
     ON wt.Ward_Start_Date_Time <= DATEADD(day, 1, dates.dte) AND
        wt.Ward_END_Date_Time >= dates.dte
GROUP BY dates.dte
ORDER BY dates.dte;

cte只是创建日期列表。

答案 1 :(得分:0)

多么酷的运动。以下是我提出的建议:

CREATE TABLE #tmp (ID int, StartDte datetime, EndDte datetime)
INSERT INTO #tmp values(1,'2017-09-03 15:04:00.000','2017-09-27 06:55:00.000')
INSERT INTO #tmp values(2,'2017-09-04 08:08:00.000','2017-09-06 18:00:00.000')
INSERT INTO #tmp values(3,'2017-09-04 13:00:00.000','2017-09-04 22:00:00.000')
INSERT INTO #tmp values(4,'2017-09-04 20:54:00.000','2017-09-08 14:30:00.000')
INSERT INTO #tmp values(5,'2017-09-04 20:52:00.000','2017-09-13 11:50:00.000')
INSERT INTO #tmp values(6,'2017-09-05 13:32:00.000','2017-09-11 14:49:00.000')
INSERT INTO #tmp values(7,'2017-09-05 13:17:00.000','2017-09-12 21:00:00.000')
INSERT INTO #tmp values(8,'2017-09-05 23:11:00.000','2017-09-06 07:38:00.000')
INSERT INTO #tmp values(9,'2017-09-05 11:35:00.000','2017-09-14 16:12:00.000')
INSERT INTO #tmp values(10,'2017-09-05 14:05:00.000','2017-09-11 16:30:00.000')


DECLARE 
  @StartDate DATE = '09/01/2017'
 ,@EndDate DATE = '10/01/2017'
 , @nHours INT = 9

 ;WITH d(OrderDate) AS
(
  SELECT DATEADD(DAY, n-1, @StartDate) 
  FROM (SELECT TOP (DATEDIFF(DAY, @StartDate, @EndDate) + 1)
   ROW_NUMBER() OVER (ORDER BY [object_id]) FROM sys.all_objects) AS x(n)
)
, CTE AS(
select OrderDate, t2.*
 from #tmp t2
 cross apply(select orderdate from d ) d
 where StartDte >= @StartDate and EndDte <= @EndDate)

 select OrderDate,
 SUM(CASE WHEN OrderDate >= StartDte and OrderDate <= EndDte THEN 1 ELSE 0 END)  [No. Beds Occupied at 00:00],
 SUM(CASE WHEN StartDTE <= DateAdd(hour,@nHours,CAST(OrderDate as datetime)) and DateAdd(hour,@nHours,CAST(OrderDate as datetime))  <= EndDte THEN 1 ELSE 0 END) [No. Beds Occupied at 09:00]
   from CTE
  GROUP BY OrderDate

如果您愿意,这应该允许您使用@nHours参数检查一天中的任何时间。如果您只想查看实际属于您的日期范围的记录,那么您可以在开始日期和结束日期过滤交叉申请。