Home > Software engineering >  SQL cumulative based on condition
SQL cumulative based on condition

Time:08-01

I need to take cumulative based on condition. If it is holiday I dont want to take cumulative.

If the row is the first row in date order and if that day is holiday then it should take daywiseplan value. For all subsequent rows, if IsHolday equals zero, accumulate DaywisePlan into the total. If IsHolday equals one, accumulate the value of DaywisePlan on the next row in date order where IsHolday equals zero

Date DaywisePlan IsHoliday ExpectedOutput
7/1/2022 34 1 34
7/2/2022 34 1 34
7/3/2022 34 0 34
7/4/2022 34 0 68
7/5/2022 34 0 102
7/6/2022 34 0 136
7/7/2022 34 1 136
7/8/2022 34 1 136
7/9/2022 34 0 170
7/10/2022 34 0 204
7/11/2022 34 1 204
7/12/2022 34 0 238

CodePudding user response:

in one Query id´ can't think of, but use a CTE , it is quite easy using The window Function SUM and FIRST_VALUE

if you have more month and want to have a SUM for all month sereately, you need to PARTITION both window function mothwise

WITH CTE AS
(SELECT
    [Date], [DaywisePlan], [IsHoliday],
    FIRST_VALUE([DaywisePlan]) OVER(PARTITION BY [IsHoliday] ORDER By [Date]) [First],
    SUM(CASE WHEN [IsHoliday] = 0 THEN [DaywisePlan] ELSe 0 END) OVER(ORDER By [Date]) as [Sum]
FROM tab1)
SELECT [Date], [DaywisePlan], [IsHoliday]
,CASE WHEN [Sum] = 0 AND [IsHoliday] = 1 THEN [Sum]  [first] ELSe [Sum] END as [Sum] FROM CTE
Date                    | DaywisePlan | IsHoliday | Sum
:---------------------- | ----------: | --------: | --:
2022-07-01 02:00:00.000 |          34 |         1 |  34
2022-07-02 02:00:00.000 |          34 |         1 |  34
2022-07-03 02:00:00.000 |          34 |         0 |  34
2022-07-04 02:00:00.000 |          34 |         0 |  68
2022-07-05 02:00:00.000 |          34 |         0 | 102
2022-07-06 02:00:00.000 |          34 |         0 | 136
2022-07-07 02:00:00.000 |          34 |         1 | 136
2022-07-08 02:00:00.000 |          34 |         1 | 136
2022-07-09 02:00:00.000 |          34 |         0 | 170
2022-07-10 02:00:00.000 |          34 |         0 | 204
2022-07-11 02:00:00.000 |          34 |         1 | 204
2022-07-12 02:00:00.000 |          34 |         0 | 238

db<>fiddle here

CodePudding user response:

In the comments I proposed logic based on the first version of your expected results.

The expected results in the question as currently posed do not match that logic. Instead they seem to match this logic:

Do not accumulate DaywisePlan until arriving at the first row in order of date ascending where IsHolday equals zero. For that row and all subsequent rows, if IsHolday equals zero, accumulate DaywisePlan into the total.

You have also used an ambiguous date format which I infer (given the nature of your question) to be 'month/day/year', but could also be valid 'day/month/year' values. Here it happens to be the caes that the interpretation makes no difference to the ordering, but you should make it a habit of using non-ambiguous date formats like 'yyyyMMdd'.

In any case, here is a query which will produce the original expected results, and another query which will produce the new expected results. I have used similar CTE's for both to make the logic (and the difference between them) a little easier to read.


create table #mytable
(
   [Date]         date primary key,
   DaywisePlan    int,
   IsHoliday      bit,
   ExpectedOutput int
);

set dateformat mdy;

-- original dataset

insert #mytable values
('7/1/2022',   34,   1,  34 ),
('7/2/2022',   34,   1,  34 ),
('7/3/2022',   34,   0,  68 ),
('7/4/2022',   34,   0,  102 ),
('7/5/2022',   34,   0,  136 ),
('7/6/2022',   34,   0,  170 ),
('7/7/2022',   34,   1,  170 ),
('7/8/2022',   34,   1,  170 ),
('7/9/2022',   34,   0,  204 ),
('7/10/2022',  34,   0,  238 ),
('7/11/2022',  34,   1,  238 ),
('7/12/2022',  34,   0,  272 );

-- logic producing original dataset

with working as
(
   select      [date],
               DaywisePlan,
               IsHoliday,
               ExpectedOutput,
               FullAccum      =  sum(DayWisePlan) 
                                 over (order by [date] rows unbounded preceding),
               HoldayAccum    =  sum
                                 (
                                    iif(isHoliday = 1 and [date] != t.mindate, DayWisePlan, 0)
                                 ) over (order by [date] rows unbounded preceding)
   from        #mytable
   cross join (select min([date]) from #myTable) t(mindate)
)
select   [date], 
         daywiseplan, 
         isholiday, 
         expectedoutput,  
         CalculatedOutput = FullAccum - HoldayAccum
from     working;

-- edited dataset

delete from #mytable;

insert #mytable values
('7/1/2022',   34,   1,   34  ),
('7/2/2022',   34,   1,   34  ),
('7/3/2022',   34,   0,   34  ),
('7/4/2022',   34,   0,   68  ),
('7/5/2022',   34,   0,   102 ),
('7/6/2022',   34,   0,   136 ),
('7/7/2022',   34,   1,   136 ),
('7/8/2022',   34,   1,   136 ),
('7/9/2022',   34,   0,   170 ),
('7/10/2022',  34,   0,   204 ),
('7/11/2022',  34,   1,   204 ),
('7/12/2022',  34,   0,   238 );

-- logic to produce edited dataset
with working as
(
   select      [date],
               DaywisePlan,
               IsHoliday,
               ExpectedOutput,
               firstNonHoliday   = (select min([date]) from #myTable where IsHoliday = 0),
               FullAccum         =  sum(DayWisePlan) 
                                    over (order by [date] rows unbounded preceding),
               HoldayAccum       =  sum
                                    (
                                       iif(isHoliday = 1, DayWisePlan, 0)
                                    ) over (order by [date] rows unbounded preceding)
   from        #mytable
)
select   [date], 
         daywiseplan, 
         isholiday, 
         expectedoutput,  
         CalculatedOutput = iif([date] < firstNonHoliday, daywiseplan, FullAccum - HoldayAccum)
from     working;

If you just mean to say "ignore any holidays after the first non-holiday" then the logic can be significantly simplified (keeping the CTE for comparative purposes):

with working as
(
   select      [date],
               DaywisePlan,
               IsHoliday,
               ExpectedOutput,
               firstNonHoliday   = (select min([date]) from #myTable where IsHoliday = 0),
               FullAccum         = sum(iif(isHoliday = 0, DayWisePlan, 0)) 
                                   over(order by date rows unbounded preceding)
   from        #mytable  
   
)
select   [date], 
         daywiseplan, 
         isholiday, 
         expectedoutput,  
         CalculatedOutput = iif([date] <= firstNonHoliday, dayWisePlan, fullaccum)
from     working;
  • Related