使用 CTE 按天和小时捕获体积计数

Use CTE to capture volume counts by day and hour

我想根据他们注册的开始日期和出院日期,按星期几和按小时查看当前患者数量。 例如:John doe 开始日期:01-01-2022 13:00:00 ;结束日期 01-01-2022 16:25:00

我希望数据显示 John doe 在设施中的每个小时。所以输出看起来像这样:

John Doe  01-01-2022     ( Hour) 13
John Doe  01-01-2022     ( Hour) 14
John Doe  01-01-2022     ( Hour) 15
John Doe  01-01-2022     ( Hour) 16

我的开始日期和出院日期都在临时 table 中,我认为我可以使用 CTE 来完成这项工作,但不确定如何 link 我的 table。 如何按小时获取流量明细,以便根据开始日期和出院日期计算每小时有多少人在设施中?

DECLARE @minDateTime AS DATETIME;
DECLARE @maxDateTime AS DATETIME;

SET @minDateTime = '2022-05-01 05:28:05.000';
SET @maxDateTime = '2022-05-02 06:50:00.000';

;
WITH Dates_CTE
     AS 
     (SELECT @minDateTime AS Dates
         UNION ALL
         SELECT Dateadd(hh, 1, Dates)
         FROM   Dates_CTE
         WHERE  Dates < Dateadd(hh, -1, @maxDateTime)
         )

SELECT --Convert(VARCHAR,Year,Dates)
                        Dates
                        ,Year(Dates) as 'Year'
                        ,Month(Dates) as 'Month'
                        ,Day(Dates) as 'day'
                        ,Datename(DW,Dates) as 'DayName'
                        ,DATEPART(HOUR,Dates) as 'hh'

FROM   Dates_CTE
OPTION (MAXRECURSION 0)

示例数据

AccountNumber   ServiceDateTime             RegistrationTypeDischargeDateTime 
G111            2021-05-07 10:44:19.000     2021-05-07 14:30:00.000 
G222            2021-05-08 09:59:00.000     2021-05-08 10:56:00.000 
G333            2021-07-02 11:35:07.000     2021-07-02 11:53:00.000 
G444            2021-07-07 07:57:16.000     2021-07-07 13:35:00.000

如果我们有另一个 table 中每个患者的进入和离开日期戳,我们可以加入您的日历 table 并按小时分组以查找在场患者的 ID 并计算它们。

create table inTreatment(
patientid int,
enter datetime,
leave datetime
);
insert into inTreatment values
(1,'2022-05-01 09:00:00','2022-05-01 18:00:00'),
(2,'2022-05-01 11:00:00','2022-05-01 14:00:00'),
(3,'2022-05-01 12:00:00','2022-05-02 15:00:00')
GO

3 行受影响

DECLARE @minDateTime AS DATETIME;
DECLARE @maxDateTime AS DATETIME;

SET @minDateTime = '2022-05-01 05:00:00.000';
SET @maxDateTime = '2022-05-02 06:00:00.000';

;
WITH Dates_CTE
     AS 
     (SELECT @minDateTime AS Dates
         UNION ALL
         SELECT Dateadd(hh, 1, Dates)
         FROM   Dates_CTE
         WHERE  Dates < Dateadd(hh, -1, @maxDateTime)
         )

SELECT --Convert(VARCHAR,Year,Dates)
                        string_agg(patientid,',') patients,
                        count(patientid) no_pats,
                         Dates
                        --,Year(Dates) as 'Year'
                        --,Month(Dates) as 'Month'
                        --,Day(Dates) as 'day'
                        ----,Datename(DW,Dates) as 'DayName'
                        --,DATEPART(HOUR,Dates) as 'hh'

FROM   Dates_CTE d
left join InTreatment i
on enter <= Dates and leave >= Dates
group by dates

OPTION (MAXRECURSION 0)
GO
patients | no_pats | Dates                  
:------- | ------: | :----------------------
null     |       0 | 2022-05-01 05:00:00.000
null     |       0 | 2022-05-01 06:00:00.000
null     |       0 | 2022-05-01 07:00:00.000
null     |       0 | 2022-05-01 08:00:00.000
1        |       1 | 2022-05-01 09:00:00.000
1        |       1 | 2022-05-01 10:00:00.000
1,2      |       2 | 2022-05-01 11:00:00.000
1,2,3    |       3 | 2022-05-01 12:00:00.000
1,2,3    |       3 | 2022-05-01 13:00:00.000
1,2,3    |       3 | 2022-05-01 14:00:00.000
1,3      |       2 | 2022-05-01 15:00:00.000
1,3      |       2 | 2022-05-01 16:00:00.000
1,3      |       2 | 2022-05-01 17:00:00.000
1,3      |       2 | 2022-05-01 18:00:00.000
3        |       1 | 2022-05-01 19:00:00.000
3        |       1 | 2022-05-01 20:00:00.000
3        |       1 | 2022-05-01 21:00:00.000
3        |       1 | 2022-05-01 22:00:00.000
3        |       1 | 2022-05-01 23:00:00.000
3        |       1 | 2022-05-02 00:00:00.000
3        |       1 | 2022-05-02 01:00:00.000
3        |       1 | 2022-05-02 02:00:00.000
3        |       1 | 2022-05-02 03:00:00.000
3        |       1 | 2022-05-02 04:00:00.000
3        |       1 | 2022-05-02 05:00:00.000

db<>fiddle here

鉴于此 table 和示例数据:

CREATE TABLE dbo.Admissions
(
  AccountNumber char(4),
  ServiceDateTime datetime,
  RegistrationTypeDischargeDateTime datetime
);

INSERT dbo.Admissions VALUES
('G111','20210507 10:44:19','20210507 14:30:00');

我会这样做:

DECLARE @min datetime = '20210507 05:28:05',
        @max datetime = '20210508 06:50:00';
        
DECLARE @d tinyint = DATEDIFF(HOUR, @min, @max),
        @floor datetime = SMALLDATETIMEFROMPARTS
        (YEAR(@min), MONTH(@min), DAY(@min), DATEPART(HOUR, @min), 0);

; -- see sqlblog.org/cte
WITH hours(h) AS
(
  SELECT @floor UNION ALL 
  SELECT DATEADD(HOUR, 1, h) 
  FROM hours WHERE h <= @max
)
SELECT a.AccountNumber, Date = CONVERT(date, hours.h), 
  Hour = DATEPART(HOUR, hours.h)
FROM hours INNER JOIN dbo.Admissions AS a
  ON a.ServiceDateTime < DATEADD(HOUR, 1, hours.h)
  AND a.RegistrationTypeDischargeDateTime >= hours.h
OPTION (MAXRECURSION 32767);

输出:

AccountNumber Date Hour
G111 2021-05-07 10
G111 2021-05-07 11
G111 2021-05-07 12
G111 2021-05-07 13
G111 2021-05-07 14

您可能需要调整<=/=/>,具体取决于您希望如何处理极端情况(例如,准时进入或退出,或进入和退出时间 < 1 小时)。

对于快速、简单的方法(与 CTE 相比),使用数字 table 或计数函数交叉应用。在这种情况下,我使用 dbo.fnTally

select a.AccountNumber, cast(a.ServiceDateTime as date) [Date],
       datepart(hour, dateadd(hour, fn.N, cast(a.ServiceDateTime as time))) hr
from #Admissions a
     cross apply dbo.fnTally(0, datediff(hour, 
                                         a.ServiceDateTime,
                                         a.RegistrationTypeDischargeDateTime)) fn;

dbo.fnTally


CREATE FUNCTION [dbo].[fnTally]
/**********************************************************************************************************************
    Jeff Moden Script on SSC: https://www.sqlservercentral.com/scripts/create-a-tally-function-fntally
**********************************************************************************************************************/
        (@ZeroOrOne BIT, @MaxN BIGINT)
RETURNS TABLE WITH SCHEMABINDING AS 
 RETURN WITH
  H2(N) AS ( SELECT 1 
               FROM (VALUES
                     (1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    ,(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
                    )V(N))            --16^2 or 256 rows
, H4(N) AS (SELECT 1 FROM H2 a, H2 b) --16^4 or 65,536 rows
, H8(N) AS (SELECT 1 FROM H4 a, H4 b) --16^8 or 4,294,967,296 rows
            SELECT N = 0 WHERE @ZeroOrOne = 0 UNION ALL
            SELECT TOP(@MaxN)
                   N = ROW_NUMBER() OVER (ORDER BY N)
              FROM H8
;