简体   繁体   中英

Need to add 3 months to each value within a column, based on the 1st '3 Months' calculated off the Admission Date column in T-SQL

I have 14K records table as the following (example of the data related to one particular client_id = 1002): (my date format is mm/dd/yyyy, months come first)

ClientsEpisodes:

      client_id      adm_date      disch_date    
          1002      3/11/2005        5/2/2005
          1002      8/30/2005       2/16/2007
          1002      3/16/2017            NULL

In SQL Server (T-SQL) - I need to calculate + 3 months date into the new column [3Month Date], where the 1st "+ 3 months" value will be calculated off my existing [adm_date] column. Then + 3 more months should be added to the value in [3Months Date], then the next 3 months should be added to the next value in the [3Months Date] column, and so on..., until [3MonthsDate] <= [disch_date]. When [3Months Date] is more than [disch_date] then the data shouldn't be populated. If my [disch_date] IS NULL then the condition should be [3Months Date] <= current date (whatever it is) from GETDATE() function.

Here is what I expect to see as a result: (I highlighted my dates offsets with different colors, for a better view)

在此处输入图片说明

Below, I'll clarify with more detailed explanation, about each populated (or not populated) data set:

My first [adm_date] from ClientsEpisode table was 3/11/2005. Adding 3 months: 3/11/2005 + 3 months = 6/11/2005 - falls AFTER the initial [disch_date] (5/2/2005) - not populated

   Next [adm_date] from ClientEpisode is 8/3/2005 + 3 Months = 11/30/2005; 
        then + 3 months must be added to 11/30/2005 = 2/30/2006; 
        then 2/30/2006 + 3 months = 5/30/2006; 
        then 5/30/2006 + 3 months = 8/30/2006; 
        then 8/30/2006 + 3 months = 11/30/2006;
        then 11/30/2006 + 3 months = 3/2/2007 - falls AFTER my [disch_date] 
                                                      (2/16/2007) - not populated

the same algorithm for the next [adm_date] - [disch_date] sets 11/5/2007-2/7/2009 (in dark blue).

then, where [adm_date] = 3/16/17, I have [disch_date] IS NULL, so, the algorithm applies until [3 Months Date] <= current date (10/15/2020 in this case)

You can use recursive common expression . Below is an example. Note, that you can change the DATEADD part with other (for example add 90 days if you want) - it's a matter of bussness logic.

DECLARE @DataSource TABLE
(
    [client_id] INT
   ,[adm_date] DATE
   ,[disch_date] DATE
);

INSERT INTO @DataSource ([client_id], [adm_date], [disch_date])
VALUES (1002, '3/11/2005 ', '5/2/2005')
      ,(1002, '8/30/2005 ', '2/16/2007')
      ,(1002, '3/16/2017 ', NULL);

WITH DataSource AS
(
    SELECT ROW_NUMBER() OVER(ORDER BY [client_id]) AS [row_id]
          ,[client_id]
          ,[adm_date]
          ,DATEADD(MONTH, 3, [adm_date]) AS [3Month Date]
          ,ISNULL([disch_date], GETUTCDATE()) AS [disch_date]
    FROM @DataSource
    WHERE DATEADD(MONTH, 3, [adm_date]) <= ISNULL([disch_date], GETUTCDATE()) 
),
RecursiveDataSource AS
(
    SELECT [row_id]
          ,[client_id]
          ,[adm_date]
          ,[3Month Date]
          ,[disch_date]
          ,0 AS [level]
    FROM DataSource
    UNION ALL
    SELECT DS.[row_id]
          ,DS.[client_id]
          ,DS.[adm_date]
          ,DATEADD(MONTH, 3, RDS.[3Month Date])
          ,DS.[disch_date]
          ,[level] + 1
    FROM RecursiveDataSource RDS
    INNER JOIN DataSource DS
        ON RDS.[row_id] = DS.[row_id]
        AND DATEADD(MONTH, 3, RDS.[3Month Date]) < DS.[disch_date]
)
SELECT *
FROM RecursiveDataSource
ORDER BY [row_id]
        ,[level];
    
        

This question already has an accepted answer, but you say in the comments for that, that you have performance problems. Try this instead - it's also a lot simpler.

A recursive CTE is really useful if the value of the next row depends on the value of the previous row.

Here, we don't need the answer to the previous row - we just add n x 3 months (eg, 3 months, 6 months, 9 months) and filter the rows you want to keep.

Therefore, instead of doing a recursive CTE, just do it via set logic.

Here's some data setup:

CREATE TABLE #Datasource (client_id int, adm_date date, disch_date date);
INSERT INTO #Datasource (client_id, adm_date, disch_date) VALUES
(1002, '20050311', '20050502'),
(1002, '20050830', '20070216'),
(1002, '20170316', NULL),
(1002, '20071105', '20090207');

And here's the simple SELECT

WITH DataSourceMod AS
    (SELECT client_id, adm_date, disch_date, ISNULL(disch_date, getdate()) AS disc_date_mod
        FROM #Datasource
    ),
Nums_One_to_OneHundred AS 
   (SELECT a * 10 + b AS n
     FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) A(a)
    CROSS JOIN (VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) B(b)
    )
SELECT ds.client_id, ds.adm_date, ds.disch_date, DATEADD(month, 3*Nums.n, ds.adm_date) AS ThreeMonthDate
FROM  DataSourceMod ds
    CROSS JOIN Nums_One_to_OneHundred Nums
WHERE DATEADD(month, 3* Nums.n, ds.adm_date) <= ds.disc_date_mod
ORDER BY ds.client_id, ds.adm_date;

This works by

  • Calculating the effective discharge date (the specified date, or today)
  • Calculating all possible rows for up to 300 months in the future (the table One_to_OneHundred .. um.. has all the values from 1 to 100, then multiplied by 3.)
  • Only taking those that fulfil the date condition

You can further optimise this if desired, by limiting the number of 3 months you need to add. Here's a rough version.

WITH DataSourceMod AS
    (SELECT client_id, adm_date, disch_date, ISNULL(disch_date, getdate()) AS disc_date_mod, 
            FLOOR(DATEDIFF(month, adm_date, ISNULL(disch_date, getdate())) / 3) + 1 AS nMax 
        FROM #Datasource
    ),
Nums_One_to_OneHundred AS 
   (SELECT a * 10 + b AS n
     FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) A(a)
    CROSS JOIN (VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) B(b)
    )
SELECT ds.client_id, ds.adm_date, ds.disch_date, DATEADD(month, 3*Nums.n, ds.adm_date) AS ThreeMonthDate
FROM  DataSourceMod ds
    INNER JOIN Nums_One_to_OneHundred Nums ON Nums.n <= ds.nMax
WHERE DATEADD(month, 3* Nums.n, ds.adm_date) <= ds.disc_date_mod
ORDER BY ds.client_id, ds.adm_date;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM