Search code examples
sqlsql-servercase

Reuse Case statement calculation in select Query


select 
ROW_NUMBER()OVER(PARTITION BY [ConsLookUpID] ORDER BY [ConsLookUpID]) as [Write-off-Count]  
,CASE
WHEN ROW_NUMBER()OVER(PARTITION BY [ConsLookUpID] ORDER BY [ConsLookUpID])=1
THEN 
        CASE
            WHEN DATEPART(DAY,GETDATE())>15
                    THEN FORMAT(DATEADD(MONTH,0,GETDATE()),'yyyy-MM') 
            ELSE
                FORMAT(DATEADD(MONTH,-1,GETDATE()),'yyyy-MM')
        END
WHEN ROW_NUMBER()OVER(PARTITION BY [ConsLookUpID] ORDER BY [ConsLookUpID])=2
THEN 
        CASE
            WHEN DATEPART(DAY,GETDATE())>15
                THEN FORMAT(DATEADD(MONTH,-1,GETDATE()),'yyyy-MM') 
            ELSE
                FORMAT(DATEADD(MONTH,-2,GETDATE()),'yyyy-MM')
        END
WHEN ROW_NUMBER()OVER(PARTITION BY [ConsLookUpID] ORDER BY [ConsLookUpID])=3
THEN 
        CASE
            WHEN DATEPART(DAY,GETDATE())>15
                THEN FORMAT(DATEADD(MONTH,-2,GETDATE()),'yyyy-MM') 
            ELSE
                FORMAT(DATEADD(MONTH,-3,GETDATE()),'yyyy-MM')
        END
ELSE ''
        END AS [Write-off-Month]
,[RevLookUpId]+'_'+[Write-off-Month] as [Write-off-Key]
FROM ABC

Result I want is use the case statement result in the Writ-off-Key column as the value of Write-off-Month. I can use the case statement again, will give me the answer, but I do not want to do that, as it will increase the processing time.

Tried performing case statement calculation in different query and then inner joining both he queries using conslookUpId, but conslookupId are repeating thus even though I had used inner join, it is performing cross join.

Seems to me may be not achievable in same query, but there will be some way, and cannot figure it out. Also I would like to use only single query if possible.


Solution

  • SQL is unfortunately weird sometimes about making you repeat yourself for best results. But I need to address this:

    I can use the case statement again, will give me the answer, but I do not want to do that, as it will increase the processing time.

    ... which is not true. While SQL is weird about making you repeat yourself, most database engines are (usually!) good about recognizing when you repeat the same expression and only doing the calculation work once.

    However, in this case, you can significantly improve things by nesting some of the code as a subquery (or pulling it out to a CTE). Note you have to do some of this anyway for the windowing function to work as you expect. Significantly, this also lets us use some math to compute the number of months to adjust in one place, rather than needing a CASE result for each possible number of write-offs.

    WITH PreCompute As (
        SELECT RevLookUpId
            , ROW_NUMBER()OVER(PARTITION BY [ConsLookUpID] ORDER BY [ConsLookUpID]) as [Write-off-Count] 
            , DATEPART(DAY, GETDATE()) / 16 As DayOffset
        FROM ABC 
    )
    SELECT 
        [Write-off-Count]
        ,CASE WHEN [Write-off-Count] BETWEEN 1 AND 3
              THEN FORMAT(DATEADD(MONTH, DayOffset - [Write-off-Count], GETDATE()), 'yyyy-MM')
              ELSE '' END As [Write-off-Month]
        ,[RevLookUpId]
            +'_'
            +CASE WHEN [Write-off-Count] BETWEEN 1 AND 3
                  THEN FORMAT(DATEADD(MONTH, DayOffset - [Write-off-Count], GETDATE()), 'yyyy-MM')
                  ELSE '' END as [Write-off-Key]
    FROM PreCompute
    

    In fact, the only reason the above code still uses CASE at all is to control for the set number of write-off counts provided in the original code. We can get around even that if we join to a table-value constructor to filter the allowed values, like this:

    WITH PreCompute As (
        SELECT RevLookUpId
            ,ROW_NUMBER()OVER(PARTITION BY [ConsLookUpID] ORDER BY [ConsLookUpID]) as [Write-off-Count] 
            ,DATEPART(DAY, GETDATE()) / 16 As DayOffset
        FROM ABC 
    )
    SELECT [Write-off-Count]
        ,COALESCE(FORMAT(DATEADD(MONTH, DayOffset - WOCount, GETDATE()), 'yyyy-MM'),'') As [Write-off-Month]
        ,[RevLookUpId] + '_'
            + COALESCE(FORMAT(DATEADD(MONTH, DayOffset - WOCount, GETDATE()), 'yyyy-MM'),'') as [Write-off-Key]
    FROM PreCompute
    LEFT JOIN ( VALUES (1), (2), (3) ) Map(WOCount)
         ON Map.WOCount = PreCompute.[Write-Off-Count]
    

    And now, since I also use an integer division operation to determine which half of the month we have, there are no CASE expressions anywhere in the code at all! There is still some repetition, but it's greatly reduced and, again, not as costly to the query as you might expect.

    Note these options are intended to exactly match behavior of the code from the question... but it seems like not having any value at all for the month portion of the Write-off-key if the count is out of spec is probably actually a mistake (or a data impossibility). And if you can, indeed, be 100% certain you'll only have counts from 1 to 3, we can simplify even further:

    WITH PreCompute As (
        SELECT RevLookUpId
            ,ROW_NUMBER()OVER(PARTITION BY [ConsLookUpID] ORDER BY [ConsLookUpID]) as [Write-off-Count] 
            ,DATEPART(DAY, GETDATE()) / 16 As DayOffset
        FROM ABC 
    )
    SELECT 
        [Write-off-Count]
        ,FORMAT(DATEADD(MONTH, DayOffset - [Write-off-Count], GETDATE()), 'yyyy-MM') As [Write-off-Month]
        ,[RevLookUpId] + '_'
            + FORMAT(DATEADD(MONTH, DayOffset - [Write-off-Count], GETDATE()), 'yyyy-MM') as [Write-off-Key]
    FROM PreCompute