问题
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:
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.
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