Designing Simple Schema for Disaggregation of Demand Forecast

蓝咒 提交于 2020-01-05 07:38:55

问题


Edit: [Details] I am doing a simple database design task as a training exercise where I have to come up with a basic schema design for the following case:

I have a parent-child hierarchy of products (Raw Material > Work in Progress > End Product).

Orders are placed at each level.

Number of orders shall be viewable in weekly buckets for the next 6 months.

Demand forecast can be made for each product level.

Demand forecast is done for weekly buckets, for the next 6 months.

It's usually done at the higher level in hierarchy (Raw Material or Work in Progress)

It has to be disaggregated to a lower level (End Product)

There are 2 ways in which demand forecast can be disaggregated from a higher level to lower level:

  1. User specifies percentage distribution for end product. Say, there's a forecast of 1000 for Work In Progress.. and user says I want 40% for End Product 1 and 60% for End Product 2 in bucket 10.. Then for 10th week (Sunday to Saturday) from now, forecast value for End Product 1 would be 400 and, for End Product 2 would be 600.

  2. User says, just disaggregate according to orders placed against end products in Bucket 5, and orders in bucket 5 for End Product 1 and 2 are 200 and 800 respectively, then forecast value for EP1 would be ((200/1000) * 100)% and for EP2 would be ((800/1000) * 100)% of forecast for 'Work in Progress'.

Forecast shall be viewable in weekly buckets for the next 6 months and the ideal format should be:

product name | bucket number | week start date | week end date | forecast value

What would be the a basic ideal schema for such a requirement?


Product_Hierarchy table could look like this:

id  |   name                |   parent_id
__________________________________________
1   |   raw material        |   (null)
2   |   work in progress    |   1
3   |   end product 1       |   2
4   |   end product 2       |   2

Is this a good way to store orders?

Orders

id | prod_id | order_date | delivery_date | delivered_date

where,

prod_id is foreign key that references id of product_hierarchy table,

The orders for 26 weekly buckets can be selected as

SELECT
    COUNT(*) TOTAL_ORDERS,
    WIDTH_BUCKET(
        delivery_date,
        SYSDATE,
        ADD_MONTHS(sysdate, 6), 
        TO_NUMBER( TO_CHAR(SYSDATE,'DD-MON-YYYY') - TO_CHAR(ADD_MONTHS(sysdate, 6),'DD-MON-YYYY') ) / 7
    ) BUCKET_NO
FROM
    orders_table
WHERE
    delivery_date BETWEEN SYSDATE AND ADD_MONTHS(sysdate, 6);

But this will give weekly buckets starting from today irrespective of the day. I want them to be in Sunday to Saturday weeks.

Please help designing this database structure.

(Gonna be using Oracle 11g)


回答1:


Your last comment is exactly what I meant. Cool to see you got it!

Since I had started doing it, I finished an exemple code. The difference with what you were saying is separating what will change from what will not (raw_material VS raw_material_hist) using only date for the week, which is monday, and various check constraints.

CREATE TABLE raw_material 
  ( 
     material_id     NUMBER PRIMARY KEY, 
     material_blabla VARCHAR2(20) 
  ); 

CREATE TABLE wip 
  ( 
     wip_id     NUMBER PRIMARY KEY, 
     parent_raw NUMBER REFERENCES raw_material(material_id), 
     wip_desc   VARCHAR2(20) 
  ); 

CREATE TABLE end_product 
  ( 
     end_product_id NUMBER PRIMARY KEY, 
     parent_wip     NUMBER REFERENCES wip(wip_id), 
     description    VARCHAR2(20) 
  ); 

CREATE TABLE rm_histo 
  ( 
     material_id NUMBER REFERENCES raw_material(material_id), 
     week_start  DATE CHECK (To_char(week_start, 'D')=1), 
     forecast    NUMBER(8) CHECK (forecast >0), 
     CONSTRAINT pk_rm_histo PRIMARY KEY (material_id, week_start) 
  ); 

CREATE TABLE wip_histo 
  ( 
     wip_id            NUMBER REFERENCES wip(wip_id), 
     week_start        DATE CHECK(To_char(week_start, 'D')=1), 
     wip_user_forecast NUMBER(8) CHECK (wip_user_forecast>0), 
     CONSTRAINT pk_wip_histo PRIMARY KEY (wip_id, week_start) 
  ); 

CREATE TABLE end_prod_histo 
  ( 
     end_product_id         NUMBER REFERENCES end_product(end_product_id), 
     week_start             DATE CHECK(To_char(week_start, 'D')=1), 
     end_prod_user_forecast NUMBER(8) CHECK (end_prod_user_forecast >0) 
  ); 

And at the end, indeed you use a view to see the forecasted things, or a materialized one if you have tons of data. By using a view, you do not duplicate the data, so it's safer and easier to change/update.

For your use cases 1 or 2, this does not deal with the database schema. At the end of the day it'll just be updating some value for the forecast, the logic of use cases 1 or 2 could go in a PL/SQL procedure or whatever you are using for the interface.

Edit: Also from your last comment you were mentionning having the forecasted manually set VS the computed one. So I added such a column, but credits go to you

Edit bis: As for the bucket number, just use a proper mask for the date, like IW or WW. These two changes which is the first week of the year.




回答2:


This is SQL Server syntax, you can google for the equivalent oracle functions

DATEADD(week, DATEDIFF(week, GETDATE(), 0), 0)

This will give you midnight (0:00:00) on the first day of the current week. What day this is depends on your system settings - you can use the Oracle eqivalent of DATEADD to mov it to the day you need.

I would rethink you schema, from your description it a heirachy is not the the concept that leaps to my mind - it sounds more like a sequence. I think you may be better off starting with your business objects and working back to the database rather than the other way around.




回答3:


Okay, so here's the data model I came up with.

PRODUCT -- to store product information and maintain parent-child hierarchy

id  NUMBER  "Primary Key Not Null"                  
level_code  VARCHAR2    Not Null                    
name    VARCHAR2    Not Null                    
description VARCHAR2                        
parent_id   NUMBER  Foreign Key references PRODUCT(id)                  

ORDERS -- to store orders for products

id  NUMBER  "Primary Key Not Null"                  
prod_id     NUMBER  "Foreign Key references PRODUCT(id) Not Null"                   
order_type  VARCHAR2    "Not Null Default 'Default'"
order_qty   NUMBER  Not Null
order_date  NUMBER  Foreign Key references DATE_INFO(date_key)
delivery_date   NUMBER  "Foreign Key references DATE_INFO(date_key)
Check delivery_date >= order_date"

FORECAST -- to store forecast value for products (store value for higher levels, store value for lower levels after disaggregation from a parent)

id  NUMBER  "Primary Key Not Null"
product_id  NUMBER  "Foreign Key references PRODUCT(id) Not Null"
forecast_value  NUMBER  Not Null
week    NUMBER  "Foreign Key references DATE_INFO(date_key) Not Null"                   

DISAGGREGATION_RULES -- to store which method was used for disaggregating a value from a higher level to lower level and how much percentage got distributed to lower level

id  NUMBER  "Primary Key Not Null"
parent_product_id   NUMBER  "Foreign Key id references PRODUCT(id) Not Null"
child_product_id    NUMBER  "Foreign Key id references PRODUCT(id) Not Null"
method  VARCHAR2    Not Null                    
from_week   NUMBER  "Foreign Key references DATE_INFO(date_key) Not Null"
to_week NUMBER  "Foreign Key references DATE_INFO(date_key) Not Null Check end_week >= start_week"
percent_distribution    NUMBER  Not Null                    

DATE_INFO -- date dimension, has information about start date (has to be Saturday) and end date corresponding to the week in which a particular date falls

date_key    NUMBER  "Primary Key
Not Null"                   
full_date   DATE    Not Null                    
week_begin_date DATE    Not Null                    
week_end_date   DATE    Not Null

As for the bucket number / weeks thing.. I am calculating week start date (date on Saturday, in my case) with the following function

CREATE OR REPLACE FUNCTION get_week_start_date(v_bucket_num IN NUMBER)
  RETURN DATE
IS
  week_start_date DATE;
BEGIN
  SELECT (TRUNC(SYSDATE+2, 'IW')-2) + ((v_bucket_num-1) * 7)
  INTO week_start_date FROM dual;
  RETURN week_start_date;
END;


来源:https://stackoverflow.com/questions/14796023/designing-simple-schema-for-disaggregation-of-demand-forecast

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!