Oracle Database Configuration Assistantを使用してデータベース・テンプレートを作成し、新規データ・ウェアハウスのデータベースを構築します。
事前に定義されているSales History(SH)スキーマを使ってスター・スキーマを調査してみましょう。ユーザーは必ずしもスター・スキーマを使用する必要はありませんが、多くのユーザーがデータ・ウェアハウスとデータ・マートに、スター・スキーマを選択しているようです。したがって、開発の取引先やパートナーと新しいデータ・ウェアハウス機能について話し合う際は、スター・スキーマから始めるとよいでしょう。
この節の例では小規模なスター・スキーマ・データ・マートに焦点を当てています。しかし、ここで取り扱うほとんどのデータ・ウェアハウス機能は、スター・スキーマでも非スター・スキーマでも利用可能です(ビットマップ・スター結合は明らかに例外ですが)。
SHスキーマは、リレーショナル・スター・スキーマの一例です。このスキーマは、1つの大きなレンジ・パーティションのファクト表(SALES)と、5つのディメンション表(TIMES、PROMOTIONS、CHANNELS、PRODUCTS、CUSTOMERS)で構成されています。さらにCUSTOMERSにリンクしたCOUNTRIES表がシンプルなスノーフレーク・スキーマを構成しています。
モデルと属性は、スター型変換、パラレル実行、クエリー・リライトなどのデータ・ウェアハウスに対する機能を特に示しています。ただしこれらは、実際の本番環境におけるこの種のデータ・ウェアハウスの最適なアプローチを表しているわけではありません。このような設計はスター・スキーマよりもビジネス要件によるところが大きいでしょう。SALES表とPROMOTIONS表の関係は、外部結合機能を示せるよう意図的に外してあります。
データ・ウェアハウス・スター・スキーマの表
先ほどインポートしたスター・スキーマのDDLを示します。
CREATE TABLE times
(
time_id DATE
, day_name VARCHAR2(9)
CONSTRAINT tim_day_name_nn NOT NULL
, day_number_in_week NUMBER(1)
CONSTRAINT tim_day_in_week_nn NOT NULL
, day_number_in_month NUMBER(2)
CONSTRAINT tim_day_in_month_nn NOT NULL
, calendar_week_number NUMBER(2)
CONSTRAINT tim_cal_week_nn NOT NULL
, fiscal_week_number NUMBER(2)
CONSTRAINT tim_fis_week_nn NOT NULL
, week_ending_day DATE
CONSTRAINT tim_week_ending_day_nn NOT NULL
, calendar_month_number NUMBER(2)
CONSTRAINT tim_cal_month_number_nn NOT NULL
, fiscal_month_number NUMBER(2)
CONSTRAINT tim_fis_month_number_nn NOT NULL
, calendar_month_desc VARCHAR2(8)
CONSTRAINT tim_cal_month_desc_nn NOT NULL
, fiscal_month_desc VARCHAR2(8)
CONSTRAINT tim_fis_month_desc_nn NOT NULL
, days_in_cal_month NUMBER
CONSTRAINT tim_days_cal_month_nn NOT NULL
, days_in_fis_month NUMBER
CONSTRAINT tim_days_fis_month_nn NOT NULL
, end_of_cal_month DATE
CONSTRAINT tim_end_of_cal_month_nn NOT NULL
, end_of_fis_month DATE
CONSTRAINT tim_end_of_fis_month_nn NOT NULL
, calendar_month_name VARCHAR2(9)
CONSTRAINT tim_cal_month_name_nn NOT NULL
, fiscal_month_name VARCHAR2(9)
CONSTRAINT tim_fis_month_name_nn NOT NULL
, calendar_quarter_desc CHAR(7)
CONSTRAINT tim_cal_quarter_desc_nn NOT NULL
, fiscal_quarter_desc CHAR(7)
CONSTRAINT tim_fis_quarter_desc_nn NOT NULL
, days_in_cal_quarter NUMBER
CONSTRAINT tim_days_cal_quarter_nn NOT NULL
, days_in_fis_quarter NUMBER
CONSTRAINT tim_days_fis_quarter_nn NOT NULL
, end_of_cal_quarter DATE
CONSTRAINT tim_end_of_cal_quarter_nn NOT NULL
, end_of_fis_quarter DATE
CONSTRAINT tim_end_of_fis_quarter_nn NOT NULL
, calendar_quarter_number NUMBER(1)
CONSTRAINT tim_cal_quarter_number_nn NOT NULL
, fiscal_quarter_number NUMBER(1)
CONSTRAINT tim_fis_quarter_number_nn NOT NULL
, calendar_year NUMBER(4)
CONSTRAINT tim_cal_year_nn NOT NULL
, fiscal_year NUMBER(4)
CONSTRAINT tim_fis_year_nn NOT NULL
, days_in_cal_year NUMBER
CONSTRAINT tim_days_cal_year_nn NOT NULL
, days_in_fis_year NUMBER
CONSTRAINT tim_days_fis_year_nn NOT NULL
, end_of_cal_year DATE
CONSTRAINT tim_end_of_cal_year_nn NOT NULL
, end_of_fis_year DATE
CONSTRAINT tim_end_of_fis_year_nn NOT NULL
);
CREATE UNIQUE INDEX time_pk
ON times (time_id) ;
ALTER TABLE times
ADD ( CONSTRAINT time_pk
PRIMARY KEY (time_id)
) ;
CREATE TABLE channels
( channel_id CHAR(1)
, channel_desc VARCHAR2(20)
CONSTRAINT chan_desc_nn NOT NULL
, channel_class VARCHAR2(20)
) ;
CREATE UNIQUE INDEX chan_pk
ON channels (channel_id) ;
ALTER TABLE channels
ADD ( CONSTRAINT chan_pk
PRIMARY KEY (channel_id)
) ;
CREATE TABLE promotions
( promo_id NUMBER(6)
, promo_name VARCHAR2(20)
CONSTRAINT promo_name_nn NOT NULL
, promo_subcategory VARCHAR2(30)
CONSTRAINT promo_subcat_nn NOT NULL
, promo_category VARCHAR2(30)
CONSTRAINT promo_cat_nn NOT NULL
, promo_cost NUMBER(10,2)
CONSTRAINT promo_cost_nn NOT NULL
, promo_begin_date DATE
CONSTRAINT promo_begin_date_nn NOT NULL
, promo_end_date DATE
CONSTRAINT promo_end_date_nn NOT NULL
) ;
CREATE UNIQUE INDEX promo_pk
ON promotions (promo_id) ;
ALTER TABLE promotions
ADD ( CONSTRAINT promo_pk
PRIMARY KEY (promo_id)
) ;
CREATE TABLE countries
( country_id CHAR(2)
, country_name VARCHAR2(40)
CONSTRAINT country_country_name_nn NOT NULL
, country_subregion VARCHAR2(30)
, country_region VARCHAR2(20)
);
ALTER TABLE countries
ADD ( CONSTRAINT country_pk
PRIMARY KEY (country_id)
) ;
CREATE TABLE customers
( cust_id NUMBER
, cust_first_name VARCHAR2(20)
CONSTRAINT customer_fname_nn NOT NULL
, cust_last_name VARCHAR2(40)
CONSTRAINT customer_lname_nn NOT NULL
, cust_gender CHAR(1)
, cust_year_of_birth NUMBER(4)
, cust_marital_status VARCHAR2(20)
, cust_street_address VARCHAR2(40)
CONSTRAINT customer_st_addr_nn NOT NULL
, cust_postal_code VARCHAR2(10)
CONSTRAINT customer_pcode_nn NOT NULL
, cust_city VARCHAR2(30)
CONSTRAINT customer_city_nn NOT NULL
, cust_state_province VARCHAR2(40)
, country_id CHAR(2)
CONSTRAINT customer_country_id_nn NOT NULL
, cust_main_phone_number VARCHAR2(25)
, cust_income_level VARCHAR2(30)
, cust_credit_limit NUMBER
, cust_email VARCHAR2(30)
) ;
CREATE UNIQUE INDEX customers_pk
ON customers (cust_id) ;
ALTER TABLE customers
ADD ( CONSTRAINT customers_pk
PRIMARY KEY (cust_id)
) ;
ALTER TABLE customers
ADD ( CONSTRAINT customers_country_fk
FOREIGN KEY (country_id) REFERENCES countries(country_id));
CREATE TABLE products
( prod_id NUMBER(6)
, prod_name VARCHAR2(50)
CONSTRAINT products_prod_name_nn NOT NULL
, prod_desc VARCHAR2(4000)
CONSTRAINT products_prod_desc_nn NOT NULL
, prod_subcategory VARCHAR2(50)
CONSTRAINT products_prod_subcat_nn NOT NULL
, prod_subcat_desc VARCHAR2(2000)
CONSTRAINT products_prod_subcatd_nn NOT NULL
,prod_category VARCHAR2(50)
CONSTRAINT products_prod_cat_nn NOT NULL
, prod_cat_desc VARCHAR2(2000)
CONSTRAINT products_prod_catd_nn NOT NULL
, prod_weight_class NUMBER(2)
, prod_unit_of_measure VARCHAR2(20)
, prod_pack_size VARCHAR2(30)
, supplier_id NUMBER(6)
, prod_status VARCHAR2(20)
CONSTRAINT products_prod_stat_nn NOT NULL
, prod_list_price NUMBER(8,2)
CONSTRAINT products_prod_list_price_nn NOT NULL
, prod_min_price NUMBER(8,2)
CONSTRAINT products_prod_min_price_nn NOT NULL
) ;
CREATE UNIQUE INDEX products_pk
ON products (prod_id) ;
ALTER TABLE products
ADD ( CONSTRAINT products_pk
PRIMARY KEY (prod_id)
) ;
CREATE TABLE sales
( prod_id NUMBER(6)
CONSTRAINT sales_product_nn NOT NULL
, cust_id NUMBER
CONSTRAINT sales_customer_nn NOT NULL
, time_id DATE
CONSTRAINT sales_time_nn NOT NULL
, channel_id CHAR(1)
CONSTRAINT sales_channel_nn NOT NULL
, promo_id NUMBER(6)
, quantity_sold NUMBER(3)
CONSTRAINT sales_quantity_nn NOT NULL
, amount NUMBER(10,2)
CONSTRAINT sales_amount_nn NOT NULL
, cost NUMBER(10,2)
CONSTRAINT sales_cost_nn NOT NULL
)
PARTITION BY RANGE (time_id)
(PARTITION Q1_1998 VALUES LESS THAN (TO_DATE('01-APR-1998','DD-MON-YYYY')),
PARTITION Q2_1998 VALUES LESS THAN (TO_DATE('01-JUL-1998','DD-MON-YYYY')),
PARTITION Q3_1998 VALUES LESS THAN (TO_DATE('01-OCT-1998','DD-MON-YYYY')),
PARTITION Q4_1998 VALUES LESS THAN (TO_DATE('01-JAN-1999','DD-MON-YYYY')),
PARTITION Q1_1999 VALUES LESS THAN (TO_DATE('01-APR-1999','DD-MON-YYYY')),
PARTITION Q2_1999 VALUES LESS THAN (TO_DATE('01-JUL-1999','DD-MON-YYYY')),
PARTITION Q3_1999 VALUES LESS THAN (TO_DATE('01-OCT-1999','DD-MON-YYYY')),
PARTITION Q4_1999 VALUES LESS THAN (TO_DATE('01-JAN-2000','DD-MON-YYYY')),
PARTITION Q1_2000 VALUES LESS THAN (TO_DATE('01-APR-2000','DD-MON-YYYY')),
PARTITION Q2_2000 VALUES LESS THAN (TO_DATE('01-JUL-2000','DD-MON-YYYY')),
PARTITION Q3_2000 VALUES LESS THAN (TO_DATE('01-OCT-2000','DD-MON-YYYY')),
PARTITION Q4_2000 VALUES LESS THAN (MAXVALUE))
;
ALTER TABLE sales
ADD ( CONSTRAINT sales_product_fk
FOREIGN KEY (prod_id)
REFERENCES products
, CONSTRAINT sales_customer_fk
FOREIGN KEY (cust_id)
REFERENCES customers
, CONSTRAINT sales_time_fk
FOREIGN KEY (time_id)
REFERENCES times
, CONSTRAINT sales_channel_fk
FOREIGN KEY (channel_id)
REFERENCES channels
) ;
初期パーティション化の理解
データ・ウェアハウスで最も一般的に使用されるパーティション化は、時間によるスター・スキーマのファクト表に対するレンジ・パーティション化です。このパーティション化により、問い合わせパフォーマンスだけでなくデータ・ロードの運用性とパフォーマンスも向上します。増分データ・ロードを実行したときや問い合わせの実行を開始したときに、その効果が実感できるでしょう。
SALES表のパーティション化基準は、月ごとであることに注意してください。大規模データ・ウェアハウスの場合、通常は各パーティションをそれ自身の表領域に配置します。表領域1つにつき1つのパーティションを配置することの最大の利点は、各パーティションを個別にバックアップまたは復元できることです。また、各パーティションにそれぞれ表領域レベルの記憶特性を与えることもできます。
この節ではわかりやすくするために、SALES表のすべてのパーティションに対して1つの表領域だけを使用しました。また、ハッシュ・パーティション化やコンポジット・パーティション化は使用しませんでした。
データ・ウェアハウス・スター・スキーマが使用する索引
このスター・スキーマには索引もロードしました。このスター・スキーマでロードされた索引のDDLを以下に示します。
CREATE BITMAP INDEX sales_prod_bix
ON sales (prod_id)
LOCAL ;
CREATE BITMAP INDEX sales_cust_bix
ON sales (cust_id)
LOCAL;
CREATE BITMAP INDEX sales_time_bix
ON sales (time_id)
LOCAL;
CREATE BITMAP INDEX sales_channel_bix
ON sales (channel_id)
LOCAL;
CREATE BITMAP INDEX sales_promo_bix
ON sales (promo_id)
LOCAL;
CREATE BITMAP INDEX products_prod_status_bix
ON products(prod_status);
CREATE INDEX products_prod_subcat_ix
ON products(prod_subcategory);
CREATE INDEX products_prod_cat_ix
ON products(prod_category);
CREATE BITMAP INDEX customers_gender_bix
ON customers(cust_gender);
CREATE BITMAP INDEX customers_marital_bix
ON customers(cust_marital_status);
CREATE BITMAP INDEX customers_yob_bix
ON customers(cust_year_of_birth);
SALES表の各ディメンション・キー(PRODUCTS表のPROD_ID、CUSTMERS表のCUST_ID、TIMES表のTIME_ID、CHANNELS表のCHANNEL_ID、PROMOTIONS表のPROMO_ID)にビットマップ索引が作成されていることに注意してください。
これらの索引では、ビットマップ・スター結合を使用できます。また、PRODUCTS表のPROD_STATUS列、CUSTOMERS表のCUST_GENDER列、CUST_MARITAL_STATUS列、CUST_YEAR_OF_BIRTH列にビットマップ索引を作成しました。
また、PRODUCTS表のPROD_SUBCATEGORY列とPROD_CATEGORY列にも索引を作成しました。
SALES表のパーティション化にともなって、ローカル・ビットマップ索引を作成しました。各ビットマップ索引が、SALES表のセットアップと同じレンジ・パーティション化を使用してパーティション化されるためです。
データ・ウェアハウスに索引を作成するときは、一般的に3つの機能を使用します。
- 並列処理を使用する ― 般的にデータ・ウェアハウス・プラットフォームでは複数のCPUが使用可能であり、多くの場合、索引の作成は夜間のバッチ・ジョブやそのほかのオフピーク時間などで行われます。
- NOLOGGINGを使用する
- COMPUTE STATISTICSオプションを使用する ― この機能は索引の作成中に索引と列の統計を収集します。これは、CREATE INDEXコマンドに対するオーバーヘッドがほとんど発生しません。
データ・ウェアハウス・スキーマで作成されるマテリアライズド・ビュー
SHスキーマにはマテリアライズド・ビューも作成されています。これは、処理の実行前に大きな負荷のかかる結合と集計操作の計算を済ませ、その結果をデータベースの表に格納しておくことで問い合わせの実行時間を軽減します。
作成されたマテリアライズド・ビューのDDLを以下に示します。
CREATE MATERIALIZED VIEW cal_month_sales_mv
BUILD IMMEDIATE
REFRESH FORCE
ENABLE QUERY REWRITE
AS
SELECT t.calendar_month_desc
, sum(s.amount) AS dollars
FROM sales s
, times t
WHERE s.time_id = t.time_id
GROUP BY t.calendar_month_desc;
CREATE MATERIALIZED VIEW fweek_pscat_sales_mv
BUILD IMMEDIATE
REFRESH COMPLETE
ENABLE QUERY REWRITE
AS
SELECT t.week_ending_day
, p.prod_subcategory
, sum(s.amount) AS dollars
, s.channel_id
, s.promo_id
FROM sales s
, times t
, products p
WHERE s.time_id = t.time_id
AND s.prod_id = p.prod_id
GROUP BY t.week_ending_day
, p.prod_subcategory
, s.channel_id
, s.promo_id;
CREATE BITMAP INDEX FW_PSC_S_MV_SUBCAT_BIX
ON fweek_pscat_sales_mv(prod_subcategory);
CREATE BITMAP INDEX FW_PSC_S_MV_CHAN_BIX
ON fweek_pscat_sales_mv(channel_id);
CREATE BITMAP INDEX FW_PSC_S_MV_PROMO_BIX
ON fweek_pscat_sales_mv(promo_id);
CREATE BITMAP INDEX FW_PSC_S_MV_WD_BIX
ON fweek_pscat_sales_mv(week_ending_day);
FWEEK_PSCAT_SALES_MVマテリアライズド・ビューのPROD_SUBCATEGORY列、CHANNEL_ID列、PROMO_ID列、WEEK_ENDING_DAY列にビットマップ索引が作成されました。
データ・ウェアハウス・スキーマで作成されるディメンションと階層
ディメンションとは、エンド・ユーザーがビジネス上のニーズに応じてデータを分類する構造体です。通常、ディメンション値は階層構造に編成されます。階層のレベルを1つ上に行くことをデータの「ロールアップ」と呼び、階層のレベルを1つ下に行くことをデータの「ドリルダウン」と呼びます。
ディメンションは、データ・ウェアハウス・スキーマの一部として設計、作成、保守します。ディメンションを作成したら、次の要件に一致するかどうかをチェックしてください。
- 親と子の間には、1:n の関係が存在する必要があります。親は1つ以上の子を持つことができますが、子は1つの親しか持てません。
- 階層レベルとその従属ディメンション属性の間には、1:1の関係が存在する必要があります。
- 親レベルと子レベルの列が異なる関係にある場合は、両者の間の接続にも1:n の結合関係が必要です。
作成されたディメンションのDDLを以下に示します。
CREATE DIMENSION times_dim
LEVEL day IS TIMES.time_id
LEVEL month IS TIMES.CALENDAR_MONTH_DESC
LEVEL quarter IS TIMES.CALENDAR_QUARTER_DESC
LEVEL year IS TIMES.CALENDAR_YEAR
LEVEL fis_week IS TIMES.week_ending_day
LEVEL fis_month IS TIMES.FISCAL_MONTH_DESC
LEVEL fis_quarter IS TIMES.FISCAL_QUARTER_DESC
LEVEL fis_year IS TIMES.FISCAL_year
HIERARCHY cal_rollup (
day CHILD OF
month CHILD OF
quarter CHILD OF
year
)
HIERARCHY fis_rollup (
day CHILD OF
fis_week CHILD OF
fis_month CHILD OF
fis_quarter CHILD OF
fis_year
)
ATTRIBUTE day DETERMINES
(day_number_in_week, day_name, day_number_in_month)
ATTRIBUTE month DETERMINES
(calendar_month_number, calendar_month_name,
days_in_cal_month, end_of_cal_month)
ATTRIBUTE quarter DETERMINES
(calendar_quarter_number,days_in_cal_quarter,
end_of_cal_quarter)
ATTRIBUTE year DETERMINES
(days_in_cal_year, end_of_cal_year)
ATTRIBUTE fis_week DETERMINES
(fiscal_week_number)
ATTRIBUTE fis_month DETERMINES
(fiscal_month_number, fiscal_month_name,
days_in_fis_month, end_of_fis_month)
ATTRIBUTE fis_quarter DETERMINES
(fiscal_quarter_number, days_in_fis_quarter,
end_of_fis_quarter)
ATTRIBUTE fis_year DETERMINES
(days_in_fis_year, end_of_fis_year) ;
CREATE DIMENSION customers_dim
LEVEL customer IS (customers.cust_id)
LEVEL city IS (customers.cust_city)
LEVEL state IS (customers.cust_state_province)
LEVEL country IS (countries.country_id)
LEVEL subregion IS (countries.country_subregion)
LEVEL region IS (countries.country_region)
HIERARCHY geog_rollup (
customer CHILD OF
city CHILD OF
state CHILD OF
country CHILD OF
subregion CHILD OF
region
JOIN KEY (customers.country_id) REFERENCES country
)
ATTRIBUTE customer DETERMINES
(cust_first_name, cust_last_name, cust_gender,
cust_marital_status, cust_year_of_birth,
cust_income_level, cust_credit_limit)
ATTRIBUTE country DETERMINES (countries.country_name) ;
CREATE DIMENSION products_dim
LEVEL product IS (products.prod_id)
LEVEL subcategory IS (products.prod_subcategory)
LEVEL category IS (products.prod_category)
HIERARCHY prod_rollup (
product CHILD OF
subcategory CHILD OF
category
)
ATTRIBUTE product DETERMINES products.prod_name
ATTRIBUTE product DETERMINES products.prod_desc
ATTRIBUTE subcategory DETERMINES products.prod_subcat_desc
ATTRIBUTE category DETERMINES products.prod_cat_desc
; CREATE DIMENSION promo_dim
LEVEL promo IS (promotions.promo_id)
LEVEL subcategory IS (promotions.promo_subcategory)
LEVEL category IS (promotions.promo_category)
HIERARCHY promo_rollup (
promo CHILD OF
subcategory CHILD OF
category
)
ATTRIBUTE promo DETERMINES (promo_name, promo_cost) ;