Open9
dbt-dimensional-modelling を使ってディメンショナルモデリングに入門
とは
- dbt-dimensional-modelling とは dbt を使ったディメンショナルモデリングを実現するためのチュートリアル
- GitHub リポジトリに 1 章ごとに説明書きがあるのでやりながら学んでいく
- コンテンツ自体は英語だが自分なりに訳して日本語にしていく(訳間違ってたらすみません 🙏)
0 章: ディメンショナルモデリングの概念
- ディメンショナルモデリングはデータプラクティショナー(データエンジニア?)が分析のためにデータを整理して提供するための手法の一つ
- その他には Data Vault や第三正規化(3NF)、One Big Table などがある
- ディメンションモデリングは、1996 年に Ralph Kimball が著書『 The Data Warehouse Toolkit』で紹介した手法
- ディメンションモデリングの目的は、生データを取得し、それをビジネスを表すファクトテーブルとディメンションテーブルに変換すること
- ディメンショナルモデリングの利点
- 分析用にシンプルなデータモデリングが実現可能
- ディメンションモデルをを使用するとき複雑な結合が不要
- ファクトテーブルとディメンションテーブルの結合のみで分析が可能
- 分析クエリが DRY になる
- ディメンションは他のファクトで再利用可能できるためさぎょうとコードロジックの重複を回避できる
- 高速にデータ取得ができる
- すでに結合、集計済みのテーブルを扱うため計算が早い
- 実際のビジネスプロセスとの緊密な連携
- ビジネスプロセスとメトリックスはディメンショナルモデリングにおいてモデル化及び計算される
- 分析用にシンプルなデータモデリングが実現可能
1 章: dbt プロジェクトとデータベースのセットアップ
-
以下を参考に dbt プロジェクトをセットアップする
-
DuckDB or PostgreSQL をインストールする必要があったが DuckDB を選択
-
dbt docs 立ち上げたらインポートできてるっぽい
-
DuckDB の中に入ったレコードを確認する方法
> python
>>> import duckdb
>>> con = duckdb.connect('target/adventureworks.duckdb')
>>> print(con.execute("select * from sales.salesorderheader limit 10;").fetchall())
DuckDB
- (公式サイトより)
- 高速なインプロセスの分析用データベース
- 特徴としては以下が挙げられる
- インストールやデプロイ方法がシンプル
- Linux や macOS, Windows で動く
- OSS, MIT ライセンスで無料
2 章: ビジネスプロセスを特定する
- ビジネスプロセスの特定はビジネスユーザーとの共同作業で行われる
- ビジネスユーザーは目標とプロセスに関するコンテキストを把握しておりその情報を提供できる
- 例となるビジネスである AdventureWorks の CEO と話をすると、次の情報が得られたとする
AdventureWorks は自転車を製造し、消費者 (B2C) と企業 (B2B) に販売しています。自転車は世界中の顧客に出荷されます。私は会社の CEO として、2011 年度末までに当社が生み出した収益を次の項目別に知りたいと思います。
製品カテゴリとサブカテゴリ
お客様
注文の状況
配送先の国、州、都市
- 問題となるビジネスプロセスは販売プロセスだとわかったので販売プロセスのディメンションモデルを設計する
3 章: ファクトテーブルとディメンションテーブルを識別する
- 前提となる情報
AdventureWorks は自転車を製造し、消費者 (B2C) と企業 (B2B) に販売しています。自転車は世界中の顧客に出荷されます。私は会社の CEO として、2011 年度末までに当社が生み出した収益を次の項目別に知りたいと思います。
製品カテゴリとサブカテゴリ
お客様
注文の状況
配送先の国、州、都市
- ビジネスの販売プロセスを表すディメンションモデルを作成し次の方法でデータを細分化する
- 製品カテゴリとサブカテゴリ
- お客様
- 注文の状況
- 配送先の国、州、都市
- 日付(年、月、日)
ファクトテーブル
- 現実世界のビジネスプロセスを表すデータベーステーブル
- ファクトテーブルの各レコードは次のビジネスイベントを表す
- アイテム販売
- ウェブサイトのクリック
- 生産作業指示書
-
sales.salesorderheader
テーブルには注文に使用されたクレジットカード、配送先住所など顧客に関する情報が含まれている -
sales.salesorderdetail
には注文された製品に関わる情報、注文数量、単価が含まれており収益計算に使用できる - この 2 つを結合して
fct_sales
を作成する
with stg_salesorderheader as (
select
salesorderid,
customerid,
creditcardid,
shiptoaddressid,
status as order_status,
cast(orderdate as date) as orderdate
from {{ ref('salesorderheader') }}
),
stg_salesorderdetail as (
select
salesorderid,
salesorderdetailid,
productid,
orderqty,
unitprice,
unitprice * orderqty as revenue
from {{ ref('salesorderdetail') }}
)
select
{{ dbt_utils.generate_surrogate_key(['stg_salesorderdetail.salesorderid', 'salesorderdetailid']) }} as sales_key,
{{ dbt_utils.generate_surrogate_key(['productid']) }} as product_key,
{{ dbt_utils.generate_surrogate_key(['customerid']) }} as customer_key,
{{ dbt_utils.generate_surrogate_key(['creditcardid']) }} as creditcard_key,
{{ dbt_utils.generate_surrogate_key(['shiptoaddressid']) }} as ship_address_key,
{{ dbt_utils.generate_surrogate_key(['order_status']) }} as order_status_key,
{{ dbt_utils.generate_surrogate_key(['orderdate']) }} as order_date_key,
stg_salesorderdetail.salesorderid,
stg_salesorderdetail.salesorderdetailid,
stg_salesorderdetail.unitprice,
stg_salesorderdetail.orderqty,
stg_salesorderdetail.revenue
from stg_salesorderdetail
inner join stg_salesorderheader on stg_salesorderdetail.salesorderid = stg_salesorderheader.salesorderid
サロゲートキー
- ※ ここでサロゲートキーをそれぞれのテーブルの ID をシードにして生成している
-
dbt_utils.generate_surrogate_key()
の実装を見てみると与えられたキーを string 型に変換し hash 値に変更している
ディメンションテーブル
- ビジネスプロセス中のイベントのコンテキスト情報または説明情報を表すために使用
- ディメンションの例には次のものがある
- 顧客の詳細
- ウェブサイトのクリック場所の詳細
- 商品の詳細
スノーフレークスキーマ
- ファクトテーブルが中心にありスノーフレークの中心から多数のディメンションテーブルが分岐している
- このときディメンションテーブルは正規化されてているため多数の結合が発生する
スタースキーマ
-
ファクトテーブルが中心にあるのは変わりないがディメンションテーブルを非正規化して保持する形
-
スタースキーマを採用すると以下のような 6 つのディメンションテーブルができる
4 章: ディメンションテーブルを作成する
-
dim_product
を作成する - 前の章で設計したとおり
dim_product
はproduct
とproductcategory
,productsubcategory
を 結合して作る - サロゲートキーにはハッシュ値を採用(
dbt_utils.generate_surrogate_key()
)
select
{{ dbt_utils.generate_surrogate_key(['stg_product.productid']) }} as product_key,
stg_product.productid,
stg_product.name as product_name,
stg_product.productnumber,
stg_product.color,
stg_product.class,
stg_product_subcategory.name as product_subcategory_name,
stg_product_category.name as product_category_name
from stg_product
left join stg_product_subcategory on stg_product.productsubcategoryid = stg_product_subcategory.productsubcategoryid
left join stg_product_category on stg_product_subcategory.productcategoryid = stg_product_category.productcategoryid
5 章: ファクトテーブルを作成する
-
fct_sales
テーブルを作成 - ディメンションテーブルと同じくサロゲートキーにはハッシュ値を採用
- 外部キーもディメンションテーブルでサロゲートキーを採用したようにハッシュ値に変換
select
{{ dbt_utils.generate_surrogate_key(['stg_salesorderdetail.salesorderid', 'salesorderdetailid']) }} as sales_key,
{{ dbt_utils.generate_surrogate_key(['productid']) }} as product_key,
{{ dbt_utils.generate_surrogate_key(['customerid']) }} as customer_key,
{{ dbt_utils.generate_surrogate_key(['creditcardid']) }} as creditcard_key,
{{ dbt_utils.generate_surrogate_key(['shiptoaddressid']) }} as ship_address_key,
{{ dbt_utils.generate_surrogate_key(['order_status']) }} as order_status_key,
{{ dbt_utils.generate_surrogate_key(['orderdate']) }} as order_date_key,
- ファクトテーブルはデータボリューム的に大きくなることも予想されるので
incremental
を採用することも考慮する
6 章:ディメンションモデルの関係を文章化する
- ER 図を作成してディメンションモデルの関係を理解しやすくする
- こちらを参考にdbt で ER 図を生成してみる
- ディメンションモデルでテーブル間のリレーションは簡素になっているはず
- だが ER 図を簡略的に作る仕組みが無いと運用的にはつらそう…
7 章: ディメンションモデルの使用
- 最後にデータウェアハウスを Tableau や PowerBI、Looker などの BI に接続することで使用
- 最新の BI ではテーブル間のリレーションをサポートするセマンティックレイヤーが組み込まれている
- 例えば Looker では LookML を使用して関係を定義できる
explore: fct_order {
join: dim_user {
sql_on: ${fct_order.user_key} = ${dim_user.user_key} ;;
relationship: many_to_one
}
}
- BI ツールにリレーションシップをサポートするセマンティクスレイヤーがない場合はファクトテーブルをそのすべてのディメンションテーブルに結合する大きな 1 つのテーブル One Big Table(OBT)を作成してリレーションを反映
-
dbt_utils.star()
は代理キー列を除くすべての列が選択される