One of the tasks of an architect who is implementing a data warehouse is to create the date dimensions. Because these dimensions have no source system, they are often created in something like Excel and then inserted directly into the dimension tables themselves. Searching the internet will probably reveal some code that will automatically create these dimensions. I've created one such PL/SQL block here that will create three date dimensions, a daily, monthly, and yearly grain. Because this will probably only be run once, there is no error handling nor is there a way to add to the date dimension (it will only create from scratch). Feel free and add those features if you so desire. Here is an explanation of the parameters:
1.) p_begin_year - This is the first year of the range of dates that you wish to create
2.) p_end_year - This is the last year of the range of dates that you wish to create
3.) p_create_tables - This is intended to have a value of YES or NO. Set it to YES if you want the tables to be created and populated. Set it to no if you only want the tables to be populated (e.g., they have already been created).
4.) p_include_fiscal_year - If you would like some data related to your fiscal year in the dim_day and dim_month tables, then set this value to YES. Otherwise, set it to NO.
5.) p_fiscal_year_begin_month_no - If the answer to #4 is YES, then set this to the calendar month number that is the first month in your fiscal year. In other words, if your fiscal year begins in July, set this to 7. If it begins in October, set it to 10, etc.
If nothing else, this will be a starting point for you with regards to creating a date dimension. Examine the code, tweak it, etc.
Note: This has been written in PL/SQL, meaning that it will only run in an Oracle database. If you would like to do this with a system from another database vendor, similar scripts can be written in that vendor's native language.
declare
p_begin_year int := 2001;
p_end_year int := 2014;
p_create_tables varchar2(3) := 'YES';
p_include_fiscal_year varchar2(3) := 'YES';
p_fiscal_year_begin_month_no int := 7;
d_first_day date;
d_last_day date;
d_loop_day date;
i_counter int;
begin
if upper(p_create_tables) = 'YES' then
EXECUTE IMMEDIATE 'CREATE TABLE DIM_DAY '
|| '( '
|| 'KEY_DAY INT, '
|| 'CALENDAR_DAY DATE, '
|| 'CALENDAR_MONTH_NUMBER INT, '
|| 'MONTH_NAME VARCHAR2(50), '
|| 'CALENDAR_QUARTER INT, '
|| 'CALENDAR_YEAR INT '
|| ')';
end if;
select to_date('01-JAN-' || p_begin_year, 'DD-MON-YYYY') into d_first_day from dual;
select to_date('31-DEC-' || p_end_year, 'DD-MON-YYYY') into d_last_day from dual;
d_loop_day := d_first_day;
i_counter := 1;
while d_loop_day <= d_last_day
loop
EXECUTE IMMEDIATE 'insert into dim_day (key_day, calendar_day, calendar_month_number, month_name, calendar_quarter, calendar_year) '
|| 'select ' || i_counter || ', '
|| '''' || to_char(d_loop_day, 'DD-MON-YYYY') || ''', '
|| 'extract(month from to_date(''' || d_loop_day || ''', ''DD-MON-YYYY'')),'
|| 'to_char(to_date(''' || d_loop_day || ''', ''DD-MON-YY''), ''MONTH''), '
|| 'case when extract(month from to_date(''' || d_loop_day || ''', ''DD-MON-YY'')) between 1 and 3 then 1 '
|| 'when extract(month from to_date(''' || d_loop_day || ''', ''DD-MON-YY'')) between 4 and 6 then 2 '
|| 'when extract(month from to_date(''' || d_loop_day || ''', ''DD-MON-YY'')) between 7 and 9 then 3 '
|| 'when extract(month from to_date(''' || d_loop_day || ''', ''DD-MON-YY'')) between 10 and 12 then 4 '
|| 'else -1 '
|| 'end, '
|| 'extract(year from to_date(''' || d_loop_day || ''', ''DD-MON-YY'')) '
|| 'from dual ';
i_counter := i_counter + 1;
d_loop_day := trunc(d_loop_day) + 1;
end loop;
commit;
if p_include_fiscal_year = 'YES' then
if p_create_tables = 'YES' then
EXECUTE IMMEDIATE 'alter table dim_day '
|| 'add '
|| '( '
|| 'FISCAL_MONTH_NUMBER INT, '
|| 'FISCAL_QUARTER INT, '
|| 'FISCAL_YEAR INT '
|| ') ';
end if;
EXECUTE IMMEDIATE 'update dim_day o '
|| 'set (o.fiscal_month_number, o.fiscal_year) = '
|| '( '
|| 'select case when i.calendar_month_number - ' || p_fiscal_year_begin_month_no || ' >= 0 then '
|| '(i.calendar_month_number - ' || p_fiscal_year_begin_month_no || ') + 1 '
|| 'else '
|| '12 + (i.calendar_month_number - ' || p_fiscal_year_begin_month_no || ') + 1 '
|| 'end, '
|| 'case when i.calendar_month_number - ' || p_fiscal_year_begin_month_no || ' >= 0 then '
|| 'i.calendar_year '
|| 'else '
|| 'i.calendar_year + 1 '
|| 'end '
|| 'from dim_day i '
|| 'where i.calendar_day = o.calendar_day '
|| ') ';
EXECUTE IMMEDIATE 'update dim_day o '
|| 'set o.fiscal_quarter = '
|| '( '
|| 'select case when i.fiscal_month_number in (1,2,3) then 1 '
|| 'when i.fiscal_month_number in (4,5,6) then 2 '
|| 'when i.fiscal_month_number in (7,8,9) then 3 '
|| 'when i.fiscal_month_number in (10,11,12) then 4 '
|| 'else -1 '
|| 'end '
|| 'from dim_day i '
|| 'where i.calendar_day = o.calendar_day '
|| ') ';
commit;
end if;
if upper(p_create_tables) = 'YES' then
if p_include_fiscal_year = 'YES' then
EXECUTE IMMEDIATE 'CREATE TABLE DIM_MONTH '
|| '( '
|| 'KEY_MONTH INT, '
|| 'CALENDAR_MONTH_NUMBER INT, '
|| 'CALENDAR_MONTH_NAME VARCHAR2(20), '
|| 'CALENDAR_QUARTER_NUMBER INT, '
|| 'CALENDAR_YEAR INT, '
|| 'FISCAL_MONTH_NUMBER INT, '
|| 'FISCAL_QUARTER_NUMBER INT, '
|| 'FISCAL_YEAR INT, '
|| 'FIRST_DAY DATE, '
|| 'LAST_DAY DATE '
|| ') ';
else
EXECUTE IMMEDIATE 'CREATE TABLE DIM_MONTH '
|| '( '
|| 'KEY_MONTH INT, '
|| 'CALENDAR_MONTH_NUMBER INT, '
|| 'CALENDAR_MONTH_NAME VARCHAR2(20), '
|| 'CALENDAR_QUARTER_NUMBER INT, '
|| 'CALENDAR_YEAR INT, '
|| 'FIRST_DAY DATE, '
|| 'LAST_DAY DATE '
|| ') ';
end if;
end if;
if p_include_fiscal_year = 'YES' then
EXECUTE IMMEDIATE 'insert into dim_month '
|| 'select rownum, a.* '
|| 'from '
|| '( '
|| 'select distinct calendar_month_number, '
|| 'month_name, '
|| 'calendar_quarter, '
|| 'calendar_year, '
|| 'fiscal_month_number, '
|| 'fiscal_quarter, '
|| 'fiscal_year, '
|| 'trunc(calendar_day,''month''), '
|| 'last_day(calendar_day) '
|| 'from dim_day '
|| 'order by calendar_year, '
|| 'calendar_month_number '
|| ') a ';
commit;
else
EXECUTE IMMEDIATE 'insert into dim_month '
|| 'select rownum, a.* '
|| 'from '
|| '( '
|| 'select distinct calendar_month_number, '
|| 'month_name, '
|| 'calendar_quarter, '
|| 'calendar_year, '
|| 'first_day(calendar_day), '
|| 'last_day(calendar_day) '
|| 'from dim_day '
|| ') '
|| 'order by a.calendar_year, '
|| 'a.calendar_month_number ';
commit;
end if;
if upper(p_create_tables) = 'YES' then
EXECUTE IMMEDIATE 'CREATE TABLE DIM_CALENDAR_YEAR '
|| '( '
|| 'KEY_CALENDAR_YEAR NUMBER, '
|| 'CALENDAR_YEAR NUMBER, '
|| 'FIRST_DAY DATE, '
|| 'LAST_DAY DATE '
|| ') ';
end if;
EXECUTE IMMEDIATE 'insert into dim_calendar_year '
|| 'select rownum, a.* '
|| 'from '
|| '( '
|| 'select distinct calendar_year, '
|| '''01-JAN-'' || calendar_year || '''', '
|| '''31-DEC-'' || calendar_year || '''' '
|| 'from dim_month '
|| 'order by calendar_year '
|| ') a ';
commit;
end;
Image courtesy of Anusorn P nachol / FreeDigitalPhotos.net
Showing posts with label Dimension Tables. Show all posts
Showing posts with label Dimension Tables. Show all posts
Friday, April 18, 2014
Friday, October 12, 2012
Kimball Conference Lessons Learned
I recently had the distinct privilege of attending Ralph Kimball's Dimensional Modeling In Depth class, as described in this post. Learning directly from icons such as Ralph Kimball and Margy Ross has been a huge blessing and a very enjoyable experience. One of the most eye-opening (and valuable) experiences has involved tweaking my understanding regarding concepts that I thought I understood...but found that I didn't. Some of these misunderstandings have even come out in this blog, so I'll use this post to correct some of those...
1.) Junk Dimensions - The examples of junk dimensions that I have provided included the word "junk" in the name. Margy Ross suggests not naming it as such, which makes a lot of sense. Encountering a table with junk in the name may cause some confusion (perhaps even concern) for an analyst who is not well-versed in dimensional modeling.
2.) Snowflake Schema - The terms portion of my website provides the following definition for a snowflake schema
1.) Junk Dimensions - The examples of junk dimensions that I have provided included the word "junk" in the name. Margy Ross suggests not naming it as such, which makes a lot of sense. Encountering a table with junk in the name may cause some confusion (perhaps even concern) for an analyst who is not well-versed in dimensional modeling.
2.) Snowflake Schema - The terms portion of my website provides the following definition for a snowflake schema
Occasionally there are reasons to join one dimension table to another dimension table. A schema in which this occurs is referred to as a snowflake schema. The ERD, in this case, will show this “second layer” of dimension tables as being similar in appearance to a snowflake.
This dimensional modeling class proved to me that this definition is a bit misleading. Joining one dimension table to another, such as the one on the terms portion of my website, is referred to as an outrigger. A snowflake schema involves an attempt to completely denormalize a dimension.
3.) Type 3 Slowly Changing Dimension - In this post, I described Type 3 slowly changing dimensions as being a hybrid between type 1 and type 2. In reality, this hybrid is actually referred to as a type 6 (I need to update the other post). So, what is a type 3 slowly changing dimension? I'll save that explanation for a future post; however, the type 3 is not the hybrid that I thought it was.
One of the advantages of attending a course like this is that you get to bounce your knowledge against some of the most brilliant minds in the industry. In some cases they help to affirm what you already know. In other cases they correct what you already "know"...which turns you into a stronger asset for your organization and for the industry.
For more information on data warehousing concepts visit www.brianciampa.com. For data that can be used to practice modeling and/or ETL, click on Career in Data Warehousing and then click Grow. Also, if you need a fresh approach to marketing your data warehousing skillset, consider The Data Warehouse Portfolio.
One of the advantages of attending a course like this is that you get to bounce your knowledge against some of the most brilliant minds in the industry. In some cases they help to affirm what you already know. In other cases they correct what you already "know"...which turns you into a stronger asset for your organization and for the industry.
For more information on data warehousing concepts visit www.brianciampa.com. For data that can be used to practice modeling and/or ETL, click on Career in Data Warehousing and then click Grow. Also, if you need a fresh approach to marketing your data warehousing skillset, consider The Data Warehouse Portfolio.
Friday, September 21, 2012
To Constrain or Not Constrain
One of the advantages of using a relational database is that you can mandate that certain relationships MUST exist within your data. If somebody tries to enter data into the database that does not relate to other data correctly, the database can be configured to reject that "bad data" until it is corrected. Since many data warehouses reside in relational databases, using this feature is an option for the data warehousing team as well.
Consider this example...suppose that a star contains a DIM_PRODUCT table that contains the products that are used by that business process. If the Men's Raincoat product has a KEY_PRODUCT value of 27 (surrogate key) then the records in the corresponding fact tables that pertain to this product will have a KEY_PRODUCT value of 27 (foreign key). That's a lesson from Database 101. If somebody removes the Men's Raincoat product from the DIM_PRODUCT table while records that point to it exist in the fact table, then those fact table records will point to nothing...and become meaningless.
The advantage of enforcing this constraint within the database itself is that if somebody tries to remove the Men's Raincoat product from the DIM_PRODUCT table the database will not allow it until the "child records" from the fact table have been deleted or repointed. Using this feature sounds like a no-brainer (and it may be) but a data warehouse provides an additional twist to this kind of decision since so much data is being inserted.
Option 1: Enforce Constraints - This will ensure that the relationship between the fact and dimension tables are always valid from a technical perspective. However, each time that the data is loaded via the ETL job, the fact table must look to make sure that a parent record exists in the dimension. If (and only if) it exists, it will load that record into the fact. Doing that for each and every record will ensure good data intregity but it can also slow a job down.
Option 2: Do Not Enforce Constraints - This will probably result in a faster and more efficient ETL job. However, the possiblity of the fact table containing some orphan records exists.
It is up to each data warehousing team to decide which is best for their particular situation. In some cases it is appropriate to physically enforce constraints (option 1). In other cases, it may be appropriate to logically enforce constraints (option 2), meaning that the data warehousing team will need to periodically run SQL statements that specifically look for orphaned records.
If you are looking for more information on data warehousing and/or data that can be used to practice ETL and architecture skills, visit www.brianciampa.com and click on Career in Data Warehousing. Also, if you are looking for a fresh way to market your data warehousing skills, consider The Data Warehouse Portfolio.
Image: FreeDigitalPhotos.net
Consider this example...suppose that a star contains a DIM_PRODUCT table that contains the products that are used by that business process. If the Men's Raincoat product has a KEY_PRODUCT value of 27 (surrogate key) then the records in the corresponding fact tables that pertain to this product will have a KEY_PRODUCT value of 27 (foreign key). That's a lesson from Database 101. If somebody removes the Men's Raincoat product from the DIM_PRODUCT table while records that point to it exist in the fact table, then those fact table records will point to nothing...and become meaningless.
The advantage of enforcing this constraint within the database itself is that if somebody tries to remove the Men's Raincoat product from the DIM_PRODUCT table the database will not allow it until the "child records" from the fact table have been deleted or repointed. Using this feature sounds like a no-brainer (and it may be) but a data warehouse provides an additional twist to this kind of decision since so much data is being inserted.
Option 1: Enforce Constraints - This will ensure that the relationship between the fact and dimension tables are always valid from a technical perspective. However, each time that the data is loaded via the ETL job, the fact table must look to make sure that a parent record exists in the dimension. If (and only if) it exists, it will load that record into the fact. Doing that for each and every record will ensure good data intregity but it can also slow a job down.
Option 2: Do Not Enforce Constraints - This will probably result in a faster and more efficient ETL job. However, the possiblity of the fact table containing some orphan records exists.
It is up to each data warehousing team to decide which is best for their particular situation. In some cases it is appropriate to physically enforce constraints (option 1). In other cases, it may be appropriate to logically enforce constraints (option 2), meaning that the data warehousing team will need to periodically run SQL statements that specifically look for orphaned records.
If you are looking for more information on data warehousing and/or data that can be used to practice ETL and architecture skills, visit www.brianciampa.com and click on Career in Data Warehousing. Also, if you are looking for a fresh way to market your data warehousing skills, consider The Data Warehouse Portfolio.
Image: FreeDigitalPhotos.net
Friday, September 14, 2012
Bridge Tables
Each of the examples that we've used thus far in this blog have involved situations in which each fact row was associated with only on dimension row, per dimension table. In other words there has always been a many-to-one relationship between the fact table and its associated dimensions. In the real world, this relationship will not always be the case.
Suppose that a fact table contains employee salary data and each employee can be associated with multiple departments at once. Conceptually, the design would be something like this...
One possible way of resolving this is to use what some people call a bridge table. This is essentially a crosswalk between the fact and dimension designed to resolve the many-to-many problem. The design for such a table is below.
The EMPLOYEE_DEPT_BRIDGE table will not contain any data that will be seen in query results. It will only be used as a link between the FACT_SALARY and the DIM_DEPARTMENT tables. However, there is a rule that must be well understood by any and all analysts that select from this table: You must always group by the department in your query results if your select statement joins to the DIM_DEPARTMENT table (via the bridge, of course) in any way. For example, suppose that John Smith (KEY_EMPLOYEE value is 123) has $1,000 in wages and is associated with both accounting and human resources (part time in each, perhaps). Consider this SQL statement...
select a.key_employee_name,
sum(a.wage_amount)
from fact_salary a,
employee_dept_bridge b,
dim_department c
where a.key_employee = b.key_employee
and b.key_department = c.key_department
and a.key_employee = 123
group by a.key_employee_name
Because the EMPLOYEE_DEPT_BRIDGE table will contain two rows for John Smith (one for accounting and one for human resources), these results will show a salary of $2,000 which will appear incorrect to the user.
Now, consider this SQL statement...
select a.key_employee_name,
department_name,
sum(a.wage_amount)
from fact_salary a,
employee_dept_bridge b,
dim_department c
where a.key_employee = b.key_employee
and b.key_department = c.key_department
and a.key_employee = 123
group by a.key_employee_name,
department_name
The results will show his $1,000 salary associated with each department. This will appear to be more correct to the user, assuming they understand that he is associated with both departments.
In this case, educating your analysts and report writers to use this table correctly is a key component to the success of this design.
Suppose that a fact table contains employee salary data and each employee can be associated with multiple departments at once. Conceptually, the design would be something like this...
One possible way of resolving this is to use what some people call a bridge table. This is essentially a crosswalk between the fact and dimension designed to resolve the many-to-many problem. The design for such a table is below.
The EMPLOYEE_DEPT_BRIDGE table will not contain any data that will be seen in query results. It will only be used as a link between the FACT_SALARY and the DIM_DEPARTMENT tables. However, there is a rule that must be well understood by any and all analysts that select from this table: You must always group by the department in your query results if your select statement joins to the DIM_DEPARTMENT table (via the bridge, of course) in any way. For example, suppose that John Smith (KEY_EMPLOYEE value is 123) has $1,000 in wages and is associated with both accounting and human resources (part time in each, perhaps). Consider this SQL statement...
select a.key_employee_name,
sum(a.wage_amount)
from fact_salary a,
employee_dept_bridge b,
dim_department c
where a.key_employee = b.key_employee
and b.key_department = c.key_department
and a.key_employee = 123
group by a.key_employee_name
Because the EMPLOYEE_DEPT_BRIDGE table will contain two rows for John Smith (one for accounting and one for human resources), these results will show a salary of $2,000 which will appear incorrect to the user.
| KEY_EMPLOYEE | SUM(A.WAGE_AMT) |
| 123 | 2,000 |
Now, consider this SQL statement...
select a.key_employee_name,
department_name,
sum(a.wage_amount)
from fact_salary a,
employee_dept_bridge b,
dim_department c
where a.key_employee = b.key_employee
and b.key_department = c.key_department
and a.key_employee = 123
group by a.key_employee_name,
department_name
The results will show his $1,000 salary associated with each department. This will appear to be more correct to the user, assuming they understand that he is associated with both departments.
| KEY_EMPLOYEE | DEPARTMENT_NAME | SUM(A.WAGE_AMT) |
| 123 | ACCOUNTING | 1,000 |
| 123 | HUMAN RESOURCES | 1,000 |
In this case, educating your analysts and report writers to use this table correctly is a key component to the success of this design.
Saturday, August 25, 2012
Dimensional Modeling Video
Dimensional modeling is a very powerful technique in helping to enable excellent decision making. This is a video that explains the business value that can be derived from using this technique. For more information on dimensional modeling, including free data with which you can practice, look at the Learn, Grow, and Succeed links at http://www.brianciampa.com/careerindatawarehousing.html.
Also, for those looking for a fresh way to market your skillset consider The Data Warehouse Portfolio at http://www.brianciampa.com.
Friday, August 10, 2012
ETL - Practice Loading a Dimension - Solution
In the previous post we looked at the process used to write a basic ETL job to populate a dimension table. As discussed previously, while no two developers will write one exactly the same way, an ETL job that populates a dimension table will need to accomplish the following...
1.) Extract all of the dimension attributes from the source.
2.) Transform the data according to the requirements (i.e., concatenate first name and last name to create a full name column, etc.)
3.) Identify those records that are new to the table as opposed to those records that already exist in the table and may need to be updated due to updates in the source system
4.) Generate new surrogate key values and add them to the new records
5.) Load the records into the dimension table
Consider this example of an Oracle PL/SQL procedure that will populate the DIM_PRODUCT table. Before running the procedure keep these things in mind...
1.) I made an error in the SQL file from the previous post, so you may want to re-download that and run it again. The DIM_PRODUCT.PRODUCT_EFFECTIVE_FLAG should be named DIM_PRODUCT.PRODUCT_ACTIVE_FLAG.
2.) This job depends on an Oracle sequence to create the surrogate keys. Before running the job, run this statement in your Oracle environment...
CREATE SEQUENCE
SEQ_DIM_PRODUCT
MINVALUE 0
INCREMENT BY 1
START WITH 1;
3.) I'll encourage you not to get too lost in the Oracle syntax. The point is to examine the logical flow of an ETL job. If you have a better way of structuring the Oracle code (or want to use something other than Oracle), then by all means do that.
Run the procedure (after you have created the sample data provided in the previous post, of course) to populate the DIM_PRODUCT dimension. Notice what is happening...
1.) Extract all of the dimension attributes from the source - Everything is pulled from the source system and placed into the STAGE_PRODUCT_EXTRACT table.
2.) Transform the data according to the requirements (i.e., concatenate first name and last name to create a full name column, etc.) - The PRODUCT_ACTIVE_FLAG is derived and that data is placed into the STAGE_PRODUCT_TRANSFORM table.
3.) Identify those records that are new to the table as opposed to those records that already exist in the table and may need to be updated due to updates in the source system - New records are identified (via an outer join) and placed into the STAGE_PRODUCT_LOAD table.
4.) Generate new surrogate key values and add them to the new records - Surrogate keys are created in the STAGE_PRODUCT_LOAD table using the Oracle sequence mentioned earlier.
5.) Load the records into the dimension table - Existing records are updated in the DIM_PRODUCT table and the new records from the STAGE_PRODUCT_LOAD table are loaded.
This job is designed to be run as many times as necessary. Running one time or multiple times should still result in 25 records being placed into the DIM_PRODUCT table. This is a simple example for a few reasons, one of which is that we are working with a very small amount of data. A more complex ETL job may examine the source system's data and somehow determine which records are new and/or have been updated before pulling them into the staging area.
Also, more complex ETL jobs may not have five simple steps, as this one does, to accomplish the five things listed above. It may take several steps to accomplish those five things due to the complexity of the data.
If you wish, change some of the source data to see the updates occur or tweak the ETL job to your liking. By running this ETL job, you have just turned ordinary data into something that is truly valuable.
Image: FreeDigitalPhotos.net
1.) Extract all of the dimension attributes from the source.
2.) Transform the data according to the requirements (i.e., concatenate first name and last name to create a full name column, etc.)
3.) Identify those records that are new to the table as opposed to those records that already exist in the table and may need to be updated due to updates in the source system
4.) Generate new surrogate key values and add them to the new records
5.) Load the records into the dimension table
Consider this example of an Oracle PL/SQL procedure that will populate the DIM_PRODUCT table. Before running the procedure keep these things in mind...
1.) I made an error in the SQL file from the previous post, so you may want to re-download that and run it again. The DIM_PRODUCT.PRODUCT_EFFECTIVE_FLAG should be named DIM_PRODUCT.PRODUCT_ACTIVE_FLAG.
2.) This job depends on an Oracle sequence to create the surrogate keys. Before running the job, run this statement in your Oracle environment...
CREATE SEQUENCE
SEQ_DIM_PRODUCT
MINVALUE 0
INCREMENT BY 1
START WITH 1;
3.) I'll encourage you not to get too lost in the Oracle syntax. The point is to examine the logical flow of an ETL job. If you have a better way of structuring the Oracle code (or want to use something other than Oracle), then by all means do that.
Run the procedure (after you have created the sample data provided in the previous post, of course) to populate the DIM_PRODUCT dimension. Notice what is happening...
1.) Extract all of the dimension attributes from the source - Everything is pulled from the source system and placed into the STAGE_PRODUCT_EXTRACT table.
2.) Transform the data according to the requirements (i.e., concatenate first name and last name to create a full name column, etc.) - The PRODUCT_ACTIVE_FLAG is derived and that data is placed into the STAGE_PRODUCT_TRANSFORM table.
3.) Identify those records that are new to the table as opposed to those records that already exist in the table and may need to be updated due to updates in the source system - New records are identified (via an outer join) and placed into the STAGE_PRODUCT_LOAD table.
4.) Generate new surrogate key values and add them to the new records - Surrogate keys are created in the STAGE_PRODUCT_LOAD table using the Oracle sequence mentioned earlier.
5.) Load the records into the dimension table - Existing records are updated in the DIM_PRODUCT table and the new records from the STAGE_PRODUCT_LOAD table are loaded.
This job is designed to be run as many times as necessary. Running one time or multiple times should still result in 25 records being placed into the DIM_PRODUCT table. This is a simple example for a few reasons, one of which is that we are working with a very small amount of data. A more complex ETL job may examine the source system's data and somehow determine which records are new and/or have been updated before pulling them into the staging area.
Also, more complex ETL jobs may not have five simple steps, as this one does, to accomplish the five things listed above. It may take several steps to accomplish those five things due to the complexity of the data.
If you wish, change some of the source data to see the updates occur or tweak the ETL job to your liking. By running this ETL job, you have just turned ordinary data into something that is truly valuable.
Image: FreeDigitalPhotos.net
Friday, August 3, 2012
ETL - Practice Loading a Dimension
We've been looking recently at the concept of extract, transform, and load (ETL) jobs. This post will begin to examine some of the mechanics. When loading a dimension table the ETL job should accomplish the following things...
1.) Extract all of the dimension attributes from the source.
2.) Transform the data according to the requirements (i.e., concatenate first name and last name to create a full name column, etc.)
3.) Identify those records that are new to the table as opposed to those records that already exist in the table and may need to be updated due to updates in the source system
4.) Generate new surrogate key values and add them to the new records
5.) Load the records into the dimension table
Each ETL job that populates a dimension table will need to accomplish these five things, although no two developers will develop a job exactly the same way. To get a feel for this type of job, consider this very simple example. A source system contains these two tables among others (click the picture to enlarge)...
...and this dimension needs to be populated...
If you have an Oracle environment available to you, run this sql file to create the two source system tables, populate them with data, and create the DIM_PRODUCT table. Each product, as you can see, is grouped under a single product group. The PRODUCT_EFFECTIVE_FLAG indicates whether or not a product is currently effective (current at the time of the ETL job). If you wish, try to write an ETL job that will populate the DIM_PRODUCT table in PL/SQL or another language of your choosing. We will examine a possible solution in a future post.
1.) Extract all of the dimension attributes from the source.
2.) Transform the data according to the requirements (i.e., concatenate first name and last name to create a full name column, etc.)
3.) Identify those records that are new to the table as opposed to those records that already exist in the table and may need to be updated due to updates in the source system
4.) Generate new surrogate key values and add them to the new records
5.) Load the records into the dimension table
Each ETL job that populates a dimension table will need to accomplish these five things, although no two developers will develop a job exactly the same way. To get a feel for this type of job, consider this very simple example. A source system contains these two tables among others (click the picture to enlarge)...
...and this dimension needs to be populated...
If you have an Oracle environment available to you, run this sql file to create the two source system tables, populate them with data, and create the DIM_PRODUCT table. Each product, as you can see, is grouped under a single product group. The PRODUCT_EFFECTIVE_FLAG indicates whether or not a product is currently effective (current at the time of the ETL job). If you wish, try to write an ETL job that will populate the DIM_PRODUCT table in PL/SQL or another language of your choosing. We will examine a possible solution in a future post.
Friday, June 22, 2012
Snowflake Schema
Going back to the piggy bank example from this blog's first post, we know that the value of a data warehouse lies with allowing a user to analyze data easily. This is mainly achieved through denormalization. This often differs from the value of a source system which lies with getting data into the system quickly. This is mainly achieved through normalization.
So, if we were to run a select statement against a source system that is designed to return (among other things) an employee name as well as his department, those two things may be stored in two tables. The employee table may simply store a foreign key to the department table, which stores the names of the departments. The query may find 'John Smith' in the employee table and then the number 6 in the department field. The database will have to go to the department table, look up the row with the number 6 to see that the value is 'Human Resources' and then return those two values, along with any other values that were specified in the select statement, in the query results. Doing this for several rows and for several different kinds of values will take some time.
In a typical data warehouse design, however, all of these values may exist in the same table and be repeated. This way, the query can go to one table and find 'John Smith' and 'Human Resources' without having to translate a foreign key. This keeps things quick.
However, there are some instances in which some normalization is appropriate in a star schema. This normalization is not to the extent that it exists in the source system, which is often third normal form, but it is normalization none the less. Consider the ERD below (click on it to expand)...
This is yet another addition to the star schema that we have been using for the past several weeks. Notice the DIM_EXECUTIVE table at the bottom. It is a dimension table that is joined directly to the DIM_EMPLOYEE dimension table. It is not joined to the fact table. Why would we not add the four non-key values directly to the DIM_EMPLOYEE table? We could and that would not be incorrect, but what if there is not much executive information compared to the number of employees?
So, if the DIM_EXECUTIVE table contains 10 rows and the DIM_EMPLOYEE table contains 4,000,000 rows, then placing this executive information into the DIM_EMPLOYEE table will result in 3,000,990 rows of empty space. In this case, it might make sense.
When a dimension table joins to another dimension table the star schema is now referred to as a snowflake schema. The "second layer" of dimension tables causes the tables, when they are depicted in an ERD, to resemble a snowflake.
I will add that when in doubt, it is probably best not to snowflake. Snowflaking causes some additional complexity on the part of the reporting tools when it comes to interpreting the data.
To experiment with analyzing this kind of data, you can run the script found here in an Oracle environment. Rather than continue adding to the script little by little, this script will create ALL of the tables and data depicted in the ERD. As with last week's post on junk dimensions, you have some options with regards to snowflaking or not. Remember that your number one goal is to turn your organization's data into something that is truly valuable, thus enabling your leaders to make excellent decisions. Make your decision with that in mind.
More on snowflaking can be found in the books listed in the additional reading section to the right as well as by visiting http://www.brianciampa.com/ and, under the Concepts menu, clicking Terms or Advanced.
Update: The Kimball training that I received taught me that I was incorrect in classifying this type of design as a snowflake schema. A snowflake schema contains a completely normalized version of the dimension tables. This post describes an outrigger table. The example above should still be considered a star schema. Sorry for my mistake.
Image(s): FreeDigitalPhotos.net
So, if we were to run a select statement against a source system that is designed to return (among other things) an employee name as well as his department, those two things may be stored in two tables. The employee table may simply store a foreign key to the department table, which stores the names of the departments. The query may find 'John Smith' in the employee table and then the number 6 in the department field. The database will have to go to the department table, look up the row with the number 6 to see that the value is 'Human Resources' and then return those two values, along with any other values that were specified in the select statement, in the query results. Doing this for several rows and for several different kinds of values will take some time.
In a typical data warehouse design, however, all of these values may exist in the same table and be repeated. This way, the query can go to one table and find 'John Smith' and 'Human Resources' without having to translate a foreign key. This keeps things quick.
However, there are some instances in which some normalization is appropriate in a star schema. This normalization is not to the extent that it exists in the source system, which is often third normal form, but it is normalization none the less. Consider the ERD below (click on it to expand)...
This is yet another addition to the star schema that we have been using for the past several weeks. Notice the DIM_EXECUTIVE table at the bottom. It is a dimension table that is joined directly to the DIM_EMPLOYEE dimension table. It is not joined to the fact table. Why would we not add the four non-key values directly to the DIM_EMPLOYEE table? We could and that would not be incorrect, but what if there is not much executive information compared to the number of employees?
So, if the DIM_EXECUTIVE table contains 10 rows and the DIM_EMPLOYEE table contains 4,000,000 rows, then placing this executive information into the DIM_EMPLOYEE table will result in 3,000,990 rows of empty space. In this case, it might make sense.
When a dimension table joins to another dimension table the star schema is now referred to as a snowflake schema. The "second layer" of dimension tables causes the tables, when they are depicted in an ERD, to resemble a snowflake.
I will add that when in doubt, it is probably best not to snowflake. Snowflaking causes some additional complexity on the part of the reporting tools when it comes to interpreting the data.
To experiment with analyzing this kind of data, you can run the script found here in an Oracle environment. Rather than continue adding to the script little by little, this script will create ALL of the tables and data depicted in the ERD. As with last week's post on junk dimensions, you have some options with regards to snowflaking or not. Remember that your number one goal is to turn your organization's data into something that is truly valuable, thus enabling your leaders to make excellent decisions. Make your decision with that in mind.
More on snowflaking can be found in the books listed in the additional reading section to the right as well as by visiting http://www.brianciampa.com/ and, under the Concepts menu, clicking Terms or Advanced.
Update: The Kimball training that I received taught me that I was incorrect in classifying this type of design as a snowflake schema. A snowflake schema contains a completely normalized version of the dimension tables. This post describes an outrigger table. The example above should still be considered a star schema. Sorry for my mistake.
Image(s): FreeDigitalPhotos.net
Friday, June 15, 2012
Junk Dimensions
When the time comes to move out of a house or apartment, one of the more tedious tasks involves packing your belongings into boxes. While these boxes are typically organized based on their destination in the new house or their contents, there always seems to be a few items that just don't fit into one of these categories. So, that last box contains these miscellaneous items with no good description.
This challenge is sometimes encountered in the area of dimensional modeling as well. Remember from this post that there are instances in which a dimension value may not fit into any of the other dimension tables. In this case, it is appropriate to add the value to the fact table as a degenerate dimension.
Now, consider the scenario in which there may be a number of low cardinality dimension values that do not fit into an existing dimension table. While adding each of these values to the fact table is not incorrect, another option exists. Suppose that the source system that feeds the transaction star that we've typically used contains the following contextual elements (dimensions) for each transaction...
1.) Was this item paid using cash, debit, or credit?
2.) Was this sale originated via a phone call, an online order, or a walk-in?
3.) Was this a promotional item (Y/N)?
Because these are all low cardinality values, consider this approach. Each of the possible combinations are placed into a dimension table, assigned a surrogate key, and then joined to the fact table for analysis. This is referred to as a junk dimension. The contents of this junk dimension will be something like...
Creating a junk dimension with items that are not low cardinality is probably not a good design technique. If you were to place the transaction line number, something that is unique to each fact row, into this dimension, the need to create all possible combinations of the junk dimension values will cause this table to contain more rows than the fact table. In this case, the high cardinality items would best be placed directly into the fact table as degenerate dimensions, leaving the junk dimension to contain the low cardinality items.
If you wish, you may use one of the following methods to take a look at this data in Oracle...
1.) If you have already created the transaction star from the post on conformed dimensions, run this script to add a junk dimension.
2.) If you have not created the above mentioned transaction star, then run this script to create it first, and then run this one to add a junk dimension.
This ERD displays this star schema with the new junk dimension in the lower left-hand corner (click the image to enlarge).
As I mentioned earlier, placing these low cardinality items into the fact table as degenerate dimensions is not a bad approach. However, in that case, each fact's value will have to be stored on the fact row which will require more space than the junk dimension. Comparing that cost with the cost of having the ETL lookup the surrogate key in the junk dimension for placement into fact table will have to be considered. You have some options in this case.
Remember that your job is to enable excellent decision making for the leaders of your organization. Consider the options and select the one that will best lead you down that path.
Update: In this post I mentioned that I made a slight error in my explanation of junk dimensions. The examples of junk dimensions that I have provided included the word "junk" in the name. Margy Ross suggests not naming it as such, which makes a lot of sense. Encountering a table with junk in the name may cause some confusion (perhaps even concern) for an analyst who is not well-versed in dimensional modeling.
Image courtesy of FreeDigitalPhotos.net
This challenge is sometimes encountered in the area of dimensional modeling as well. Remember from this post that there are instances in which a dimension value may not fit into any of the other dimension tables. In this case, it is appropriate to add the value to the fact table as a degenerate dimension.
Now, consider the scenario in which there may be a number of low cardinality dimension values that do not fit into an existing dimension table. While adding each of these values to the fact table is not incorrect, another option exists. Suppose that the source system that feeds the transaction star that we've typically used contains the following contextual elements (dimensions) for each transaction...
1.) Was this item paid using cash, debit, or credit?
2.) Was this sale originated via a phone call, an online order, or a walk-in?
3.) Was this a promotional item (Y/N)?
Because these are all low cardinality values, consider this approach. Each of the possible combinations are placed into a dimension table, assigned a surrogate key, and then joined to the fact table for analysis. This is referred to as a junk dimension. The contents of this junk dimension will be something like...
| KEY_TRANSACTION_ JUNK |
PAYMENT_ METHOD |
ORIGINATION | PROMOTIONAL_ ITEM |
| 1 | Cash | Phone Call | Yes |
| 2 | Cash | Online | Yes |
| 3 | Cash | Walk-In | Yes |
| 4 | Cash | Phone Call | No |
| 5 | Cash | Online | No |
| 6 | Cash | Walk-In | No |
| 7 | Credit | Phone Call | Yes |
| 8 | Credit | Online | Yes |
| 9 | Credit | Walk-In | Yes |
| 10 | Credit | Phone Call | No |
| 11 | Credit | Online | No |
| 12 | Credit | Walk-In | No |
| 13 | Debit | Phone Call | Yes |
| 14 | Debit | Online | Yes |
| 15 | Debit | Walk-In | Yes |
| 16 | Debit | Phone Call | No |
| 17 | Debit | Online | No |
| 18 | Debit | Walk-In | No |
Creating a junk dimension with items that are not low cardinality is probably not a good design technique. If you were to place the transaction line number, something that is unique to each fact row, into this dimension, the need to create all possible combinations of the junk dimension values will cause this table to contain more rows than the fact table. In this case, the high cardinality items would best be placed directly into the fact table as degenerate dimensions, leaving the junk dimension to contain the low cardinality items.
If you wish, you may use one of the following methods to take a look at this data in Oracle...
1.) If you have already created the transaction star from the post on conformed dimensions, run this script to add a junk dimension.
2.) If you have not created the above mentioned transaction star, then run this script to create it first, and then run this one to add a junk dimension.
This ERD displays this star schema with the new junk dimension in the lower left-hand corner (click the image to enlarge).
As I mentioned earlier, placing these low cardinality items into the fact table as degenerate dimensions is not a bad approach. However, in that case, each fact's value will have to be stored on the fact row which will require more space than the junk dimension. Comparing that cost with the cost of having the ETL lookup the surrogate key in the junk dimension for placement into fact table will have to be considered. You have some options in this case.
Remember that your job is to enable excellent decision making for the leaders of your organization. Consider the options and select the one that will best lead you down that path.
Update: In this post I mentioned that I made a slight error in my explanation of junk dimensions. The examples of junk dimensions that I have provided included the word "junk" in the name. Margy Ross suggests not naming it as such, which makes a lot of sense. Encountering a table with junk in the name may cause some confusion (perhaps even concern) for an analyst who is not well-versed in dimensional modeling.
Image courtesy of FreeDigitalPhotos.net
Friday, June 8, 2012
Slowly Changing Dimensions Explained...Using Twitter
I recently changed my twitter profile picture and after seeing the results I realized that this would be a great way to explain slowly changing dimensions. When I uploaded the new picture and then viewed my timeline of tweets (my tweets) there was no trace of the old picture. I could not tell that it had ever been used. Even the tweets that had been associated with the old picture at one point now displayed the new one. This is the behavior of a type 1 slowly changing dimension. History is not kept. Only the dimension values that are current are displayed for ALL facts.
Now, imagine that the old picture remained for those tweets that were sent when it was active and that the tweets sent AFTER the new picture was uploaded contained the new picture. Viewing my timeline of my tweets will show me when I uploaded new pictures. Twitter does not work this way, but if it did this would mimic the behavior of a type 2 slowly changing dimension. As history changes, new rows are added to the dimension table. Any fact rows that occur AFTER that change will point to the new row. The fact rows that occurred before that change will continue to point to the older row.
Now, imagine that the timeline on twitter showed two pictures for each tweet. One picture represented the picture that was current at the time of the tweet. The other picture represented the picture that is the most current. The latest tweets would probably show the same picture (most current one) twice. If this were the case, we could easily see that when I tweeted about something two months ago, my profile used one picture but now it uses another. This would mimic the behavior of a type 3 slowly changing dimension, which is a hybrid between type 1 and type 2. A type 3 slowly changing dimension will add a new row as dimension objects change (as in a type 2). Each dimension row, however, in that history will contain a column (or columns) that represent the current value. So, if a new row is added, the current value columns for the historical rows will be updated with the new current information. An analyst can easily see that at the time of a certain fact, the dimension values were one thing but now, they are something else.
Of course, writing ETL so that it populates a type 2 or type 3 slowly changing dimension is more complex than a type 1. Creating a dimension to include history when the source system itself does not include history may cause difficulty if a dimension needs to be reloaded (although there are ways around this as well). Consider all of the available options and make a decision that will turn your user's data into something that is truly valuable.
For some hands-on examples of slowly changing dimensions take a look at this post. Also, remember that you can read this blog and access other data warehousing information by going to http://www.brianciampa.com/.
Now, imagine that the old picture remained for those tweets that were sent when it was active and that the tweets sent AFTER the new picture was uploaded contained the new picture. Viewing my timeline of my tweets will show me when I uploaded new pictures. Twitter does not work this way, but if it did this would mimic the behavior of a type 2 slowly changing dimension. As history changes, new rows are added to the dimension table. Any fact rows that occur AFTER that change will point to the new row. The fact rows that occurred before that change will continue to point to the older row.
Now, imagine that the timeline on twitter showed two pictures for each tweet. One picture represented the picture that was current at the time of the tweet. The other picture represented the picture that is the most current. The latest tweets would probably show the same picture (most current one) twice. If this were the case, we could easily see that when I tweeted about something two months ago, my profile used one picture but now it uses another. This would mimic the behavior of a type 3 slowly changing dimension, which is a hybrid between type 1 and type 2. A type 3 slowly changing dimension will add a new row as dimension objects change (as in a type 2). Each dimension row, however, in that history will contain a column (or columns) that represent the current value. So, if a new row is added, the current value columns for the historical rows will be updated with the new current information. An analyst can easily see that at the time of a certain fact, the dimension values were one thing but now, they are something else.
Of course, writing ETL so that it populates a type 2 or type 3 slowly changing dimension is more complex than a type 1. Creating a dimension to include history when the source system itself does not include history may cause difficulty if a dimension needs to be reloaded (although there are ways around this as well). Consider all of the available options and make a decision that will turn your user's data into something that is truly valuable.
For some hands-on examples of slowly changing dimensions take a look at this post. Also, remember that you can read this blog and access other data warehousing information by going to http://www.brianciampa.com/.
Saturday, May 19, 2012
Practice Using Degenerate Dimensions
In the previous post, we looked at degenerate dimensions. Rather than create a new SQL script that you can use to practice we can revisit the transaction data star that we used in the February 25th post. The script is here and the ERD is below.
Notice that the TRANSACTION_NUMBER and TRANSACTION_LINE columns are in the FACT_TRANSACTION table. If we created a new dimension table, called DIM_TRANSACTION, with this structure (in Oracle)...
CREATE TABLE DIM_TRANSACTION
(
KEY_TRANSACTION NUMBER,
TRANSACTION_NUMBER NUMBER,
TRANSACTION_LINE NUMBER
)
...it would contain as many rows as the FACT_TRANSACTION table. This is not wrong, but the effort involved in creating the surrogate keys which will only be used by one fact row may not be necessary. If you wish, run the create table statement that I provided above, add some data, and create some surrogate keys so that it can be joined to the fact table. You will see that the reporting capabilities from one design to the other are the same, with a bit more effort being required to maintain the DIM_TRANSACTION table. This is why I opted to place these columns directly in the fact table as degenerate dimensions. When faced with this design question, consider which is best for your organization and move forward with turning data into valuable data.
Notice that the TRANSACTION_NUMBER and TRANSACTION_LINE columns are in the FACT_TRANSACTION table. If we created a new dimension table, called DIM_TRANSACTION, with this structure (in Oracle)...
CREATE TABLE DIM_TRANSACTION
(
KEY_TRANSACTION NUMBER,
TRANSACTION_NUMBER NUMBER,
TRANSACTION_LINE NUMBER
)
...it would contain as many rows as the FACT_TRANSACTION table. This is not wrong, but the effort involved in creating the surrogate keys which will only be used by one fact row may not be necessary. If you wish, run the create table statement that I provided above, add some data, and create some surrogate keys so that it can be joined to the fact table. You will see that the reporting capabilities from one design to the other are the same, with a bit more effort being required to maintain the DIM_TRANSACTION table. This is why I opted to place these columns directly in the fact table as degenerate dimensions. When faced with this design question, consider which is best for your organization and move forward with turning data into valuable data.
Saturday, May 12, 2012
Degenerate Dimensions
One of the purposes of dimensional modeling is to identify what needs to be measured (facts) and identify the context needed to make those measures meaningful (dimensions). So, for example, if an organization wishes to measure revenue from direct sales, the fact in this model is the revenue amount. The context (or dimensions) may include things like sales person, date of sale, item sold, quantity, and/or a host of other things.
As discussed previously, a typical design is to place the facts of a business process that are measured at the same grain into one table (fact table) and to group the contextual pieces into other tables (dimension tables). Occasionally, a piece of context will exist at the same grain as the fact table. For example, using the same scenario as we used above, suppose that the sales revenue needs to be stored at the sales transaction level. If a transaction number exists for each individual transaction, then a dimension table that contains this value will contain as many rows as the fact table. While there is nothing wrong with this approach, it may seem like extra work to create an additional table with a surrogate key that will only be referenced by one row of the associated fact table.
As a result, some designers may opt to make this value a degenerate dimension (mentioned briefly at the end of a previous post). A degenerate dimension is a dimension value that exists directly in the fact table, as opposed to the fact table containing a foreign key that points to that record in a dimension table. The database will not need to allocate any additional space for the surrogate keys in this case, since they add little value anyway. Designing a dimension object like this to be a degenerate dimension is not a must in this situation. Using the standard approach of creating a separate table is fine too.
As with everything that we do as Business Intelligence experts, when faced with this design question it is best to consider the value that each option brings to the table. Asking some of the following questions may be a good start...
1.) Do I have too many dimension tables included in this star already?
2.) How will the reporting tools react to this design?
3.) Will the report writers become confused if I design it this way?
If needed, spend some time absorbing the concept of degenerate dimensions. We will look at some examples later.
Image: photostock / FreeDigitalPhotos.net
As discussed previously, a typical design is to place the facts of a business process that are measured at the same grain into one table (fact table) and to group the contextual pieces into other tables (dimension tables). Occasionally, a piece of context will exist at the same grain as the fact table. For example, using the same scenario as we used above, suppose that the sales revenue needs to be stored at the sales transaction level. If a transaction number exists for each individual transaction, then a dimension table that contains this value will contain as many rows as the fact table. While there is nothing wrong with this approach, it may seem like extra work to create an additional table with a surrogate key that will only be referenced by one row of the associated fact table.
As a result, some designers may opt to make this value a degenerate dimension (mentioned briefly at the end of a previous post). A degenerate dimension is a dimension value that exists directly in the fact table, as opposed to the fact table containing a foreign key that points to that record in a dimension table. The database will not need to allocate any additional space for the surrogate keys in this case, since they add little value anyway. Designing a dimension object like this to be a degenerate dimension is not a must in this situation. Using the standard approach of creating a separate table is fine too.
As with everything that we do as Business Intelligence experts, when faced with this design question it is best to consider the value that each option brings to the table. Asking some of the following questions may be a good start...
1.) Do I have too many dimension tables included in this star already?
2.) How will the reporting tools react to this design?
3.) Will the report writers become confused if I design it this way?
If needed, spend some time absorbing the concept of degenerate dimensions. We will look at some examples later.
Image: photostock / FreeDigitalPhotos.net
Friday, April 20, 2012
Slowly Changing Dimensions Solutions To Practice
In the last post I provided a script that allowed you to practice interacting with slowly changing dimensions. The answers to the three questions that I proposed are...
1.) John Farmer earned $62,000 in salary (wages, benefits, and bonus) from Information Systems and $12,200 from Human Resources. A SQL statement like this (although variations of it will answer the question as well) will answer this question...
select a.last_name,
a.first_name,
a.employee_number,
a.department,
a.hire_date,
a.title,
a.appointment_begin_date,
a.appointment_end_date,
sum(b.actual_wages_paid),
sum(b.actual_benefits_paid),
sum(b.actual_bonus_paid)
from dim_employee_scd a,
fact_salary_scd b
where a.key_employee_scd = b.key_employee_scd
and a.employee_number = 2546
group by a.last_name, a.first_name, a.employee_number, a.department, a.hire_date, a.title, a.appointment_begin_date, a.appointment_end_date
order by 3,7
2.) James Couch's salary dropped 36.6% from the first to the second position. It dropped 36.4% from the second to the third position. From a business perspective this seems odd, but the point is to see there there was a change of some sort. A SQL statement like this will answer this question...
select a.last_name,
a.first_name,
a.employee_number,
a.department,
a.hire_date,
a.title,
a.appointment_begin_date,
a.appointment_end_date,
sum(b.actual_wages_paid) + sum(b.actual_benefits_paid) + sum(b.actual_bonus_paid)
from dim_employee_scd a,
fact_salary_scd b
where a.key_employee_scd = b.key_employee_scd
and a.employee_number = 2547
group by a.last_name, a.first_name, a.employee_number, a.department, a.hire_date, a.title, a.appointment_begin_date, a.appointment_end_date
order by 3,7
3.) Assuming that a current appointment is defined as a record with a null value for the appointment_end_date (in reality, a developer would need to verify this with a subject matter expert) the following SQL statement will display salary dollars for active positions only...
select a.last_name,
a.first_name,
a.employee_number,
a.department,
a.hire_date,
a.title,
a.appointment_begin_date,
a.appointment_end_date,
sum(b.actual_wages_paid),
sum(b.actual_benefits_paid),
sum(b.actual_bonus_paid)
from dim_employee_scd a,
fact_salary_scd b
where a.key_employee_scd = b.key_employee_scd
and a.appointment_end_date is null
group by a.last_name, a.first_name, a.employee_number, a.department, a.hire_date, a.title, a.appointment_begin_date, a.appointment_end_date
order by 3,7
As the dimensions (descriptors of measures) move through time, a type 2 slowly changing dimension allows the measures to be grouped by each of those changes. For example, the salary dollars can not only be displayed per person. As the person progresses through the organization by moving to new positions, the salary dollars can be grouped by each stop along the way.
From a technical perspective this amounts to the dimension's natural key expanding from the employee number to the employee number, position title, department, and appointment begin date (or whatever defines an appointment in your organization). From a business perspective this example allows an analyst to view an individual's effect on the organization as she progresses through that organization.
Image: digitalart / FreeDigitalPhotos.net
1.) John Farmer earned $62,000 in salary (wages, benefits, and bonus) from Information Systems and $12,200 from Human Resources. A SQL statement like this (although variations of it will answer the question as well) will answer this question...
select a.last_name,
a.first_name,
a.employee_number,
a.department,
a.hire_date,
a.title,
a.appointment_begin_date,
a.appointment_end_date,
sum(b.actual_wages_paid),
sum(b.actual_benefits_paid),
sum(b.actual_bonus_paid)
from dim_employee_scd a,
fact_salary_scd b
where a.key_employee_scd = b.key_employee_scd
and a.employee_number = 2546
group by a.last_name, a.first_name, a.employee_number, a.department, a.hire_date, a.title, a.appointment_begin_date, a.appointment_end_date
order by 3,7
2.) James Couch's salary dropped 36.6% from the first to the second position. It dropped 36.4% from the second to the third position. From a business perspective this seems odd, but the point is to see there there was a change of some sort. A SQL statement like this will answer this question...
select a.last_name,
a.first_name,
a.employee_number,
a.department,
a.hire_date,
a.title,
a.appointment_begin_date,
a.appointment_end_date,
sum(b.actual_wages_paid) + sum(b.actual_benefits_paid) + sum(b.actual_bonus_paid)
from dim_employee_scd a,
fact_salary_scd b
where a.key_employee_scd = b.key_employee_scd
and a.employee_number = 2547
group by a.last_name, a.first_name, a.employee_number, a.department, a.hire_date, a.title, a.appointment_begin_date, a.appointment_end_date
order by 3,7
3.) Assuming that a current appointment is defined as a record with a null value for the appointment_end_date (in reality, a developer would need to verify this with a subject matter expert) the following SQL statement will display salary dollars for active positions only...
select a.last_name,
a.first_name,
a.employee_number,
a.department,
a.hire_date,
a.title,
a.appointment_begin_date,
a.appointment_end_date,
sum(b.actual_wages_paid),
sum(b.actual_benefits_paid),
sum(b.actual_bonus_paid)
from dim_employee_scd a,
fact_salary_scd b
where a.key_employee_scd = b.key_employee_scd
and a.appointment_end_date is null
group by a.last_name, a.first_name, a.employee_number, a.department, a.hire_date, a.title, a.appointment_begin_date, a.appointment_end_date
order by 3,7
As the dimensions (descriptors of measures) move through time, a type 2 slowly changing dimension allows the measures to be grouped by each of those changes. For example, the salary dollars can not only be displayed per person. As the person progresses through the organization by moving to new positions, the salary dollars can be grouped by each stop along the way.
From a technical perspective this amounts to the dimension's natural key expanding from the employee number to the employee number, position title, department, and appointment begin date (or whatever defines an appointment in your organization). From a business perspective this example allows an analyst to view an individual's effect on the organization as she progresses through that organization.
Image: digitalart / FreeDigitalPhotos.net
Saturday, April 14, 2012
Practice Using Slowly Changing Dimensions
A few weeks ago we looked at slowly changing dimensions. I've provided a script that will create a table called dim_employee_scd and fact_salary_scd. These are two slightly altered versions of the same tables in the star that we have been using (if you are just joining us, don't worry, they can stand alone too).
In this case, the dim_employee_scd acts as a slowly changing dimension. As always, don't read too much into the dates and/or salary amounts. This data is entirely fictitious. Run the script (here are instructions if needed), play with the data and see if you can answer these questions...
1.) How much did John Farmer receive in salary from Information Systems as opposed to Human Resources?
2.) As a percentage, how many more (or fewer) dollars did James Couch receive in salary when he moved from the first to the second and then from the second to the third position? Remember, even if the numbers decrease although it appears that he was promoted, that's ok. This is fictitious data and the point is to see there there was a change of some sort.
3.) Try to display salary dollars for active only positions, which may require that you make an assumption about the business rule.
We'll look at some solutions in the future.
In this case, the dim_employee_scd acts as a slowly changing dimension. As always, don't read too much into the dates and/or salary amounts. This data is entirely fictitious. Run the script (here are instructions if needed), play with the data and see if you can answer these questions...
1.) How much did John Farmer receive in salary from Information Systems as opposed to Human Resources?
2.) As a percentage, how many more (or fewer) dollars did James Couch receive in salary when he moved from the first to the second and then from the second to the third position? Remember, even if the numbers decrease although it appears that he was promoted, that's ok. This is fictitious data and the point is to see there there was a change of some sort.
3.) Try to display salary dollars for active only positions, which may require that you make an assumption about the business rule.
We'll look at some solutions in the future.
Saturday, March 31, 2012
Practice Using Conformed Dimensions
In the last post we looked at the benefits of having conformed dimensions. If you wish, feel free to play with this yourself. I have two scripts available that will essentially add a fact_salary table to the star that we used for practice a few weeks ago.
- If you created that star in your Oracle instance already and want to simply add the fact_salary table, use this script.
- If you want to create the entire star in your Oracle instance, use this script.
The updated ERD is below (if the image is too small to read, click on it to have it enlarged).
See if you can write some SQL that will allow you to compare the measures between the two fact tables. If you are really into this, try adding a third or fourth fact table of your own (and more dimensions if needed) and doing some additional comparisons. A powerful use of a data warehouse involves comparing measures between multiple business processes to answer questions like…
- How does his productivity growth compare to his salary growth?
- How does this year’s increase in revenue (either in dollars or as a percentage) compare to last year’s increase in dollars allocated to new marketing initiatives?
- How are the number of new work from home opportunities affecting employee turnover?
Using conformed dimensions can help to easily answer questions like these. What was once just a pile of data is now providing valuable information to decision makers.
Saturday, March 24, 2012
Conformed Dimensions
In a prior post we looked at the structure associated with a star schema, which includes both fact and dimension tables. The beauty of a star schema is that dimension tables can be (and are intended to be) shared by different stars.
Let’s look further. We’ve already used the following star as an example in an earlier post. The Fact_Transaction.Key_Sales_Person field can be joined to the Dim_Employee.Key_Employee field to report on certain pieces of employee data. If a data warehouse had 20 stars, it would be very confusing if each star had its own definition of an employee. It would also be impossible to compare data between stars.
If an employee dimension such as the one below exists in one star then the fact table in a different star can also use the key_employee values as its foreign key. In such a scenario, the ERD can be expanded to…
Notice that the organization decided to warehouse its salary data and create a new star which uses the fact_salary table. Also notice how this table references the dim_date dimension as well as the dim_employee dimension, also used by the fact_transaction table. From my perspective, at least two advantages exist…
1.) As already discussed, the time and effort involved in creating and maintaining multiple copies of the same dimension table is avoided.
2.) Facts that share dimensions can easily be compared side by side. Using the ERD above, let’s say that a report was requested to compare the dollars sold to the wages paid to each employee per month. The dollars sold (fact_transaction.actual_sales_price) and the wages paid (fact_salary.actual_wages_paid) are not in the same fact table. However, since we have conformed dimensions, this can be accomplished using a SQL statement similar to the following (using Oracle syntax)…
SELECT decode(paid.year, null, sales.year, paid.year),
decode (paid.month_number, null, sales.month_number, paid.month_number),
decode(paid.employee_last_name, null, sales. employee_last_name, paid.employee_last_name),
decode(paid.employee_first_name, null, sales. employee_first_name, paid.employee_first_name),
paid.amtpaid,
sales.salesamt
FROM
(SELECT d.year,
d.month_number,
c.employee_last_name,
c.employee_first_name
sum(b.actual_wages_paid) amtpaid
FROM fact_salary b,
dim_employee c,
dim_date d
WHERE b.key_date_paid = d.key_date
AND b.key_employee = c.key_employee
GROUP BY d.year,
d.month_number,
c.employee_last_name,
c.employee_first_name) paid
FULL OUTER JOIN
(SELECT d.year,
d.month_number,
c.employee_last_name,
c.employee_first_name
sum(a.actual_sales_price) salesamt
FROM fact_transaction a,
dim_employee c,
dim_date d
WHERE a.key_date = d.key_date
AND a.key_sales_person = c.key_employee
GROUP BY d.year,
d.month_number,
c.employee_last_name,
c.employee_first_name) sales
ON paid.year = sales.year
AND paid.month_number = sales.month_number
AND paid.employee_last_name = sales.employee_last_name
AND paid.employee_first_name = sales.employee_first_name
As long as the dimensions included in the query from each fact table are at the same grain, this analysis can be done. For example, trying to compare monthly pay to daily sales (which doesn’t even make sense) will not be possible. Also note that this kind of thing will probably be done behind the scenes with a piece of reporting software, as opposed to report writers having to write this type of query manually.
From a business perspective, because the definition of an individual employee is the same definition in multiple business processes (sales person in transactions and recipient of pay in salary) and the dates mean the same thing in multiple business processes (date of sale and date paid) then these are conformed dimensions. From a technical perspective, even if the dimension tables are copied and placed into different stars they would still technically be considered conformed as long as they are copies of each other or subsets of each other. For example, if we broke up our dim_employee table into dim_employee_usa, dim_employee_europe, dim_employee_canada, etc. and used the one that was the most appropriate for the country being analyzed, that might make for a faster response from the database. In Oracle-speak, this can be accomplished using materialized views as well (another way to essentially accomplish the same thing). It is important to note that if this approach is taken, the key_employee value must be the same for each instance of an employee that exists in multiple tables. So, if Brian Ciampa exists in the overall dim_employee table and also in the dim_employee_usa table (because he is located in the US), then he should have the same key_employee value in each one. That way, either table can be joined to any fact table that contains that foreign key. The commonality with regards to structure and business rule definition is key, even if different tables are used.
Subscribe to:
Posts (Atom)


















