📝
dbt Style Guideを読む
こちらの記事の続きです。
dbt style guideを元にプロジェクトを構築するといいと書かれていたので、dbt style guideを読んで行きます。
公式のリファレンス読むの大事ですよね。
例にもよってモチベーションは、同僚にも読んでもらってデファクトスタンダードを作るためです。
まとめ
- dbtに限らず、いい感じのことが書いてあった
- SQL触る人なら一度目を通してもいいかもと思った
- ただ、スタイルガイドは一人で「これに従うんだー」っていうんじゃなくて、チームで合意形成することが大切だろうなと思った(余計な論争に発展しかねない)
Model Naming
- モデルは
staging
、marts
、base/intermediate
の3つのカテゴリーに分類される - なぜこの設計を採用するかの詳細はこちらのポストを参照してね(僕なりにまとめたものはこちら)
- ファイル、ディレクトリ構造は次のようになるよ
├── dbt_project.yml
└── models
├── marts
| └── core
| ├── intermediate
| | ├── intermediate.yml
| | ├── customers__unioned.sql
| | ├── customers__grouped.sql
| └── core.yml
| └── core.docs
| └── dim_customers.sql
| └── fct_orders.sql
└── staging
└── stripe
├── base
| ├── base__stripe_invoices.sql
├── src_stripe.yml
├── src_stripe.docs
├── stg_stripe.yml
├── stg_stripe__customers.sql
└── stg_stripe__invoices.sql
-
stg_stripe__invoices
のように、全てのオブジェクト名は複数形にしましょう -
base__<source>_<object>
のようにベーステーブルはbase__
のプレフィックスをつけよう -
customers__unioned
のように、中間テーブルはオブジェクトに対して行われたアクションを示す過去形の動詞で終わるようにしましよう -
fct_orders
、dim_customers
のように、マートはfactとdimensionに分類しよう
Model configuration
- モデル特有のアトリビュート(例えばsortやdistなど)はモデルの中で指定すべきだよ
- ディレクトリないのモデルに特定の設定を適用する場合は
dbt_project.yml
ファイルで指定すべきだよ -
In-model configurations
は次のように指定すべきだよ{{ config( materialized = 'table', sort = 'id', dist = 'id' ) }}
- マート層では常に(viewではなく)テーブルとして構成した方がいいよ
dbt conventions
-
stg_
モデル(もし必要だったらbase_
モデル)のみがsource
からselectすべき - 他の全てのモデルはsourceからではなく、モデルを参照すべきだよ
Testing
- 全てのサブディレクトリに
.yml
ファイルを置くべき、そしてそれぞれのモデルはそのファイルによってテストされるべき- stagingフォルダでの命名規則は
src_sourcename.yml
であるべきで、他のフォルダでの命名規則はfoldername.yml
であるべき(例:core.yml
)
- stagingフォルダでの命名規則は
- 最低限、それぞれのモデルのprimary keyがユニークかどうかと非nullかどうかはテストされるべき
Naming and field conventions
- スキーマ、テーブル、カラムは
snake_case
であるべき - 命名はプロダクト由来の用語より、ビジネス由来の用語で行うべき
- どのモデルもprimary keyを持つべき
- モデルのprimary keyは
account_id
のように<object>_id
とすべき、これはなんのid
を指し示しているかが下流の処理でわかりやすくするためである - base/stagingモデルではフィールドはカテゴリーから並べるべき
- 識別子が最初でタイムスタンプは最後のようにするべき
- タイムスタンプは
created_at
のように<event>_at
とすべき、そしてUTCにしておくべき- 特定のタイムゾーンを指定するときは
created_at_pt
のようにサフィックスで表すべき
- 特定のタイムゾーンを指定するときは
- (海外の考えっぽい)価格、収益のフィールドは小数点表示すべき($19.99をセントの整数表示ではなく
19.99
のように表すべき)- もし小数点表示でなく整数表示にするときは
price_in_cents
のようにサフィックスで表すべき
- もし小数点表示でなく整数表示にするときは
- カラムに予約語は避けるべき
- 一貫性はとても重要!
customers
テーブルのidを表すならuser_id
ではなくcustomer_id
にすべき
CTEs
なぜ多くのCTEを使うと良いかの詳細はこのポストを読んでね
- ファイルの最上位階層のCTEでは全て
{{ ref('...') }}
ステートメントが使われるべきである - パフォーマンスが許す限り、CTEは単一の論理的な作業単位を実行する必要がある
- CTEの名前は、処理の内容を伝えるためならば冗長になっても良い(短くしてカッコつけるより、処理の内容を正確に伝える方が大切)
- CTE内の複雑なロジックや、注意すべきロジックにはコメントするべき
- モデル間で重複しているCTEは、単一のモデルに出して参照するべき
-
final
という名前のCTEか、もしくはそれに相当するCTEを最後にselectするべき、そうするとデバッグ容易性が上がる - CTEはこのような書式にするべき
with events as ( ... ), -- CTE comments go here filtered_events as ( ... ) select * from filtered_events
SQL style guide
- カンマは末尾につける
- インデントはスペース4つにする
- 1行の文字数は80文字以下にする
- フィールド名や予約語は小文字にする
- フィールドやテーブル名に別名をつけるときは
as
句を使う - フィールドは集計やwindowの前に記載すべき
-
order by
やgroup by
においては列の名前を列挙するよりも数字で指定する方が好ましい(例: group by 1, 2)- もしいくつかのかのカラムで
group by
をするのであれば、モデルの設計を見直す価値があるかもしれない
- もしいくつかのかのカラムで
-
union *
よりもunion all
の方が好ましい - 結合するときにテーブルに別名をつけるのは避ける(特に頭文字による別名はよくない)
-
customers
と比較してc
というテーブルが何であるか理解するのが難しくなる
-
- 2つ以上ののテーブルをジョインする時は、常にカラム名の前にテーブルの別名をつけること
- 1つのテーブルからselectするだけならいらないよ
- ジョインは明示的に記述する方が親切(
join
の代わりにinnner join
と記述するようにする)-
left join
を常用するべきである、right join
を書いたら、from
とjoin
する対象を反対にするべき(right join
を使うな)
-
- 適切に改行しろ、改行によるパフォーマンス悪化なんてたかが知れてるが、読みづらいことによる脳のリソース消費はえぐい
SQLの例
with
my_data as (
select * from {{ ref('my_data') }}
),
some_cte as (
select * from {{ ref('some_cte') }}
),
some_cte_agg as (
select
id,
sum(field_4) as total_field_4,
max(field_5) as max_field_5
from some_cte
group by 1
),
final as (
select [distinct]
my_data.field_1,
my_data.field_2,
my_data.field_3,
-- use line breaks to visually separate calculations into blocks
case
when my_data.cancellation_date is null
and my_data.expiration_date is not null
then expiration_date
when my_data.cancellation_date is null
then my_data.start_date + 7
else my_data.cancellation_date
end as cancellation_date,
some_cte_agg.total_field_4,
some_cte_agg.max_field_5
from my_data
left join some_cte_agg
on my_data.id = some_cte_agg.id
where my_data.field_1 = 'abc'
and (
my_data.field_2 = 'def' or
my_data.field_2 = 'ghi'
)
having count(*) > 1
)
select * from final
- joinしたものは左からselectするべき
select trips.*, drivers.rating as driver_rating, riders.rating as rider_rating from trips left join users as drivers on trips.driver_id = drivers.user_id left join users as riders on trips.rider_id = riders.user_id
YAML style guide
- インデントは2つのスペースにするべき
- リストアイテムはインデントする
- 辞書であるリスト項目を区切るには、必要に応じて改行を使用すべき
- 1行が80文字以下になるようにすべき
YAMLの例
version: 2
models:
- name: events
columns:
- name: event_id
description: This is a unique identifier for the event
tests:
- unique
- not_null
- name: event_time
description: "When the event occurred in UTC (eg. 2018-01-01 12:00:00)"
tests:
- not_null
- name: user_id
description: The ID of the user who recorded the event
tests:
- not_null
- relationships:
to: ref('users')
field: id
Jinja style guide
- Jinjaデリミタを使うときはデリミタの両端にスペースを入れること(
{{this}}
ではなく、{{ this }}
のようにする) - Jinjaの論理ブロックを視覚的に示すために改行を使うこと
Discussion