Case statement in SQL Server with rolling weeks - easier way?

djblois

I am creating a case statement (inside a view) that tests 2 variables and outputs a value for another field. The 2 variables it tests are ForecastMultiplier and a function that gets the current week number. Forecast Multiplier means how many months to take in to effect. I then have a table with 12 months of forecast which I use to figure out how much on hand inventory we should have.

Here are some examples:

  • If Forecast Multiplier = 1 and we are in the first week of the month then I will take all of Month1 Forecast (which is the current month).
  • If Forecast Multiplier = 1 and we are in the second week of the month then I will take 75% of Month1 Forecast plus 25% of Month2 Forecast.
  • If Forecast Multiplier = 5 and we are in the first week of the month then I will take Month1 Forecast plus Month2 Forecast plus Month3 Forecast plus Month4 Forecast plus Month5 Forecast
  • If Forecast Multiplier = 5 and we are in the third week of the month then I will take 50% of Month1 Forecast plus Month2 Forecast plus Month3 Forecast plus Month4 Forecast plus Month5 Forecast plus 25% of Month6 Forecast

and this needs to be done for forecast multipliers of .5, 1.5, etc as well. So you can see how, using a simple select case will get very cumbersome.

Here is some of the case statement, so you can see:

CASE 
    when mpi.UseForecast = 0 then mpi.MinimumOnHandQuantity
    when mpi.ForecastMultiplier = 1 and dbo.GetWeekNumber() = 1 Then fp.month1
    when mpi.ForecastMultiplier = 1 and dbo.GetWeekNumber() = 2 Then (fp.month1 * .75) + (fp.MONTH2 * .25)
    when mpi.ForecastMultiplier = 1 and dbo.GetWeekNumber() = 3 Then (fp.month1 * .50) + (fp.MONTH2 * .50)
    when mpi.ForecastMultiplier = 1 and dbo.GetWeekNumber() = 4 Then (fp.month1 * .25) + (fp.MONTH2 * .75)
    when mpi.ForecastMultiplier = 1.5 and dbo.GetWeekNumber() = 1 Then fp.month1 + (fp.month2 * .5)
    when mpi.ForecastMultiplier = 1.5 and dbo.GetWeekNumber() = 2 Then (fp.month1 * .75) + (fp.month2 * .5)
    when mpi.ForecastMultiplier = 1.5 and dbo.GetWeekNumber() = 3 Then (fp.month1 * .50) + (fp.month2 * .5)
    when mpi.ForecastMultiplier = 1.5 and dbo.GetWeekNumber() = 4 Then (fp.month1 * .25) + (fp.month2 * .5)
    when mpi.ForecastMultiplier = 2 and dbo.GetWeekNumber() = 1 Then fp.month1 + fp.month2
    when mpi.ForecastMultiplier = 2 and dbo.GetWeekNumber() = 2 Then (fp.month1 * .75) + fp.month2 + (fp.MONTH3 * .25)
    when mpi.ForecastMultiplier = 2 and dbo.GetWeekNumber() = 3 Then (fp.month1 * .50) + fp.month2 + (fp.MONTH3 * .50)
    when mpi.ForecastMultiplier = 2 and dbo.GetWeekNumber() = 4 Then (fp.month1 * .25) + fp.month2 + (fp.MONTH3 * .75)

The SQL statement works but there are a few issues with it. It is cumbersome. It is getting slower the more Months I add to it (it will never go more than 12 months). Also, it doesn't take the months with 5 weeks in to account. Finally, if possible we would prefer rolling days, but by this method it is nearly impossible. Is there a way of doing this through stored procedures and/ or functions? btw, I originally was doing the select statement in a Scaler function but it was very slow, so now I am trying the view.

What I am looking for is a better way to achieve this same result or as I mentioned in the last paragraph, a better result.

Here is the Schema and some data:

MinimumProductInfoes (MPI):

Code Region UseForecast ForecastMultipler MinimumOnHand
---- ------ ----------- ----------------- -------------
1    R1     0           0                 50
1    R2     1           2                 0
2    R1     1           4.5               0
2    R3     1           3                 0
3    R1     1           12                0

ForecastPivot (FP):

Code  Region  Month1 Month2 Month3 Month4 Month5 Month6 Month7 ... Month12
----  ------  ------ ------ ------ ------ ------ ------ ------     -------
1     R1      200    200    50     75     200    50     50         80
1     R2      500    500    500    500    500    500    500        500
2     R1      1000   0      0      0      0      0      0          0
2     R3      25     1000   1000   1000   1000   1000   1000       1000
Shawn

SQL Fiddle

Schema Setup: (SQLFiddle uses MS SQL 2014, but this will still work in 2008.)

CREATE TABLE MPI ( Code int, Region varchar(5), UseForecast bit
    , ForecastMultiplier decimal(5,1), MinimumOnHand int ) ;
INSERT INTO MPI ( Code, Region, UseForecast, ForecastMultiplier, MinimumOnHand )
VALUES 
      ( 1,'R1',0,0,50 )
    , ( 1,'R2',1,2,0)
    , ( 2,'R1',1,4.5,0)
    , ( 2,'R3',1,3,0)
    , ( 3,'R1',1,12,0)
;

CREATE TABLE FP ( Code int,  Region varchar(5), Month1 int, Month2 int
    , Month3 int, Month4 int, Month5 int, Month6 int, Month7 int
    , Month8 int, Month9 int, Month10 int, Month11 int, Month12 int ) ;
INSERT INTO FP (Code, Region, Month1, Month2, Month3, Month4, Month5
    , Month6, Month7, Month8, Month9, Month10, Month11, Month12 )
VALUES 
      ( 1,'R1',200,200,50,75,200,50,50,50,50,50,50,80 )
    , ( 1,'R2',500,500,500,500,500,500,500,50,50,50,50,500 )
    , ( 2,'R1',1000,0,0,0,0,0,0,50,50,50,50,0 )
    , ( 2,'R3',25,1000,1000,1000,1000,1000,1000,50,50,50,50,1000 )
;

Build Simple Calendar Table:

NOTE: The calendar table is for general use in your database, so add whatever calculations you need. I often use a modified version of Aaron Bertrand's example: https://github.com/shawnoden/SQL_Stuff/blob/master/sql_CreateDateDimension.sql

/* #dim is just a temp holding table for intermediate calculations. */
CREATE TABLE #dim (
      theDate           date        PRIMARY KEY
    , theDay            AS DATEPART(day, theDate)           --int
    , theWeek           AS DATEPART(week, theDate)          --int 
    , theMonth          AS DATEPART(month, theDate)         --int
    , theYear           AS DATEPART(year, theDate)          --int
    , yyyymmdd          AS CONVERT(char(8), theDate, 112)   /* yyyymmdd */
    , mm_dd_yy          AS CONVERT(char(10), theDate, 101)  /* mm/dd/yyyy */
);

/**************************************************************************/
/* Use the catalog views to generate as many rows as we need. */

INSERT INTO #dim ( theDate ) 
SELECT d
FROM (
    SELECT d = DATEADD(day, rn - 1, '20170101')
    FROM 
    (
        SELECT TOP (DATEDIFF(day, '20160101', '20190101')) 
            rn = ROW_NUMBER() OVER (ORDER BY s1.object_id)
        FROM sys.all_objects AS s1
        CROSS JOIN sys.all_objects AS s2
        ORDER BY s1.object_id
    ) AS x
) AS y;

/* Now create the final ref table for the dates. */
CREATE TABLE refDateDimension
(
      DateKey             int         NOT NULL PRIMARY KEY
    , theDate             date        NOT NULL
    , theDay              tinyint     NOT NULL  
    , WeekOfMonth         tinyint     NOT NULL
    , theMonth            tinyint     NOT NULL
    , theYear             int         NOT NULL
    , mm_dd_yy            char(10)    NOT NULL   /* mm/dd/yyyy */
);

/* Insert data in the dimension table. */
INSERT refDateDimension WITH (TABLOCKX)
SELECT
      DateKey              = CONVERT(int, yyyymmdd)
    , theDate              = theDate
    , theDay               = CONVERT(tinyint, theDay)
    , WeekOfMonth          = CONVERT(tinyint
                              , DENSE_RANK() OVER 
                                (PARTITION BY theYear, theMonth 
                                 ORDER BY theWeek)
                           )
    , theMonth             = CONVERT(tinyint, theMonth)
    , theYear              = theYear
    , mm_dd_yy             = mm_dd_yy
FROM #dim
OPTION (MAXDOP 1);

/* CLEANUP */
DROP TABLE #dim ;

Now we use the calendar table to quickly find the week number.:

SELECT s1.Code
  , s1.Region
  , CEILING(s1.ForecastMinOnHand) AS ForecastMinOnHand /* Round up to even number. */
FROM (
  SELECT MPI.Code, MPI.Region
    --, MPI.UseForecast, MPI.ForecastMultiplier, d.WeekOfMonth
    , CASE 
          WHEN MPI.UseForecast = 0          THEN MPI.MinimumOnHand
          WHEN MPI.ForecastMultiplier = 1    THEN CASE d.WeekOfMonth 
                                                      WHEN 1 THEN FP.Month1 
                                                      WHEN 2 THEN (FP.Month1*.75) + (FP.Month2*.25)
                                                      WHEN 3 THEN (FP.Month1*.5) + (FP.Month2*.5)
                                                      ELSE (FP.Month1*.25) + (FP.Month2*.75)
                                                 END
          WHEN MPI.ForecastMultiplier = 2    THEN CASE d.WeekOfMonth 
                                                      WHEN 1 THEN FP.Month1 + FP.Month2
                                                      WHEN 2 THEN (FP.Month1*.75) + FP.Month2 + (FP.Month3*.25)
                                                      WHEN 3 THEN (FP.Month1*.5) + FP.Month2 + (FP.Month3*.5)
                                                      ELSE (FP.Month1*.25) + FP.Month2 + (FP.Month3*.75)
                                                 END     
          WHEN MPI.ForecastMultiplier = 3    THEN CASE d.WeekOfMonth 
                                                      WHEN 1 THEN FP.Month1 + FP.Month2 + FP.Month3
                                                      WHEN 2 THEN (FP.Month1*.75) + FP.Month2 + FP.Month3 + (FP.Month4*.25)
                                                      WHEN 3 THEN (FP.Month1*.5) + FP.Month2 + FP.Month3 + (FP.Month4*.5)
                                                      ELSE (FP.Month1*.25) + FP.Month2 + FP.Month3 + (FP.Month4*.75)
                                                 END   
          WHEN MPI.ForecastMultiplier = 4.5  THEN CASE d.WeekOfMonth 
                                                      WHEN 1 THEN FP.month1 + fp.month2 + fp.month3 + fp.month4 + (FP.month5*.5)
                                                      WHEN 2 THEN (FP.Month1*.75) + fp.month2 + fp.month3 + fp.month4 + (FP.Month5*.75)
                                                      WHEN 3 THEN (FP.Month1*.5) + fp.month2 + fp.month3 + fp.month4 + FP.Month5
                                                      ELSE (FP.Month1*.25) + fp.month2 + fp.month3 + fp.month4 + FP.Month5 + (FP.Month6*.25)
                                                 END     

     END AS ForecastMinOnHand
     --, MPI.*, FP.*
  FROM MPI 
  INNER JOIN refDateDimension d ON CAST(getDate() AS date) = d.theDate 
  INNER JOIN FP ON MPI.Code = FP.Code
    AND MPI.Region = FP.Region
) s1

Results:

| Code | Region | ForecastMinOnHand |
|------|--------|-------------------|
|    1 |     R1 |                50 |
|    1 |     R2 |              1000 |
|    2 |     R1 |               750 |
|    2 |     R3 |              2269 |

My main purpose of using the calendar table here is that it takes away the GetWeekNumber() function and saves a bunch of processing cycles. It will also let your SQL optimizer run a bit more efficiently. In general, you would add a lot more columns to it to account for the time calculations you need elsewhere. It's a utility table. And extremely handy.

I still think the long CASE statement can be distilled down a bit. If you have the ability to edit the ForecastPivot view, you should be able to get at some of this data a little easier without a pivot.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

SQL Server - Case Statement

From Dev

Case Statement in SQL Server

From Dev

Case Statement in SQL Server

From Dev

Rolling 13 weeks in Microsoft sql

From Java

OR is not supported with CASE Statement in SQL Server

From Dev

SQL Server Convert in Case Statement

From Dev

CASE when statement SQL Server

From Dev

Sql Server Weird CASE Statement

From Dev

Nested If Statement Using Case Statement in SQL Server

From Dev

Easier way to write conditional statement

From Dev

Is there an easier way than nested iifs to count mismatched conditions in SQL server

From Dev

SQL Server : set a variable in CASE statement

From Dev

SQL Server: use Case statement for variable declaration

From Dev

SQL Server CASE statement with mupltiple conditionals syntax

From Dev

CASE statement inside a WHERE clause in SQL Server

From Dev

Nested CASE Statement With ELSe (SQL Server)

From Dev

SQL Server Case Statement and Aggregation functions

From Dev

SQL Server pivot using case statement

From Dev

SQL Server: Alternative of long CASE statement

From Dev

Condition inside case statement - SQL server 2008

From Dev

SQL Server CASE statement in Where Clause of Select

From Dev

Inner Join with CASE statement is not working in SQL SERVER

From Dev

SQL SERVER 2008 select statement with case

From Dev

Trying to use CASE statement in SQL Server

From Dev

Use CASE statement with SUM function in SQL Server

From Dev

CASE statement inside a WHERE clause in SQL Server

From Dev

Using a case statement to SELECT AS in SQL Server

From Dev

Case Statement With Between Clause In Sql Server

From Dev

SQL Server CASE statement with mupltiple conditionals syntax

Related Related

HotTag

Archive