· HARUBOUYA · dataflow · 10 min read
Dataformでデータ変換を管理する
画面に貼り付けたSQLは、誰がいつ何のために変えたのか追えなくなります。SQLをファイルとして持ち、依存関係を自動で解決して実行する仕組みの入り方を書きます。

データ基盤を作っていると、SQLが増えます。集計用、加工用、確認用などです。これらが管理画面の「スケジュールされたクエリ」に散らばっていくと、ある時点から誰がいつ何のために変えたのかが追えなくなります。
Dataform は、そのSQLをファイルとして持ち、依存関係を自動で解決して順に実行する仕組みです。BigQuery に組み込まれており、追加の費用はかかりません(実行されるクエリの料金だけかかります)。
問題:画面に貼ったSQLは管理できない
| 起きること | 理由 |
|---|---|
| 実行順が人の記憶頼りになります | Aの結果を使うBを、Aより先に動かしてしまいます |
| 変更の履歴が残りません | 誰がいつ直したか、なぜ直したかが分かりません |
| 同じ加工が何か所にも書かれます | 直すときに全部を直せません |
| テスト用と本番用が混ざります | 消してよいものが分からなくなります |
一番重い症状は実行順です。元のテーブルができる前に集計が動き、前日の数字で集計された結果が静かに残ります。
仕組み
Dataform の構成要素は3つだけです。
| 要素 | 役割 |
|---|---|
.sqlx ファイル | 1ファイル=1テーブル(またはビュー)。SQLと設定を1つに書きます |
ref() | 他のテーブルを参照する書き方。これが依存関係になります |
| ワークフローの実行 | 依存関係の順に、まとめて実行します |
ファイルの中身
config {
type: "table",
schema: "mart",
description: "日別の売上集計"
}
SELECT
DATE(order_at, "Asia/Tokyo") AS order_date,
COUNT(DISTINCT order_id) AS order_count,
SUM(amount) AS amount
FROM
${ref("stg_orders")}
GROUP BY
order_dateconfig が設定、その下がSQLです。${ref("stg_orders")} の部分が、実際のテーブル名に置き換わります。
type の使い分け
| type | 何になるか | 使う場面 |
|---|---|---|
view | ビュー | 元データが小さいです。常に最新を見たいです |
table | テーブル(毎回作り直し) | 集計結果。件数が数百万までなら扱いやすいです |
incremental | テーブル(差分を追加) | ログのように積み上がるデータ |
declaration | 既存テーブルの宣言 | 外から取り込まれたテーブルを ref() で使えるようにします |
依存関係は書かなくてよい
ref() を使えば、実行順は自動で決まります。
stg_orders → mart_daily_sales → mart_monthly_sales上の並びを人が書く必要はありません。mart_daily_sales が ref("stg_orders") を使っている事実から、Dataform が順番を組み立てます。
これが画面に貼ったSQLとの最大の違いです。テーブルを1つ足しても、実行順の設定を直す作業が発生しません。
検査を付ける
Dataform には assertion(検査)があります。条件を満たさない行が1件でもあれば、そこで止まります。
config {
type: "table",
schema: "mart",
assertions: {
uniqueKey: ["order_date"],
nonNull: ["order_date", "amount"]
}
}| 検査 | 意味 |
|---|---|
uniqueKey | 指定した列の組み合わせが重複していないこと |
nonNull | 指定した列が空でないこと |
rowConditions | 自分で書いた条件を満たすこと(例:amount >= 0) |
uniqueKey だけは最初から付けます。集計が二重に走ったときに気づける唯一の仕掛けになります。
Gitにつなぐ
Dataform はリポジトリと接続できます。接続すると次ができます。
| できること | 効果 |
|---|---|
| 変更の履歴が残ります | 誰がいつ何を直したかを追えます |
| 枝を分けて作業できます | 本番の定義を壊さずに試せます |
| 変更を確認してから反映できます | SQLの差分を他の人が見られます |
1人で使う場合でも、過去の定義に戻せるという理由だけで接続する価値があります。
やること
手順1. 1テーブルだけ移す
全部を一度に移さないでください。いま動いているスケジュール実行のうち、依存の少ないものを1つ選びます。
- Google Cloud コンソールで「BigQuery」→「Dataform」を開きます
- 「リポジトリを作成」を押し、名前とリージョンを入れます
- 作成したリポジトリを開き、「開発ワークスペースを作成」を押します
definitionsフォルダに新しい.sqlxファイルを作ります- 既存のSQLを貼り、上に
config { type: "table", schema: "..." }を足します
手順2. 参照を ref() に置き換える
SQLの中に書かれているテーブル名を探し、${ref("テーブル名")} の形に直します。
元のテーブルが Dataform の外にある場合は、先に declaration を作ります。
config {
type: "declaration",
schema: "raw",
name: "orders"
}手順3. 実行して確かめる
- 画面右上の「実行」を押します
- 「アクション」の一覧から、作ったものを選びます
- 実行結果が「成功」になることを確認します
- BigQuery 側で、できたテーブルの件数を手元の数字と突き合わせます
手順4. スケジュールを設定する
- リポジトリの画面で「ワークフロー設定」を開きます
- 「作成」を押し、実行する対象と頻度(cron 形式)を指定します
- 実行に使うサービスアカウントを選びます
旧いスケジュール実行は、しばらく両方動かしてから止めます。結果が一致することを数日確認してから切り替えます。
失敗例
| 失敗 | 何が起きるか | 代わりにやること |
|---|---|---|
| 画面のSQLとDataformを二重に持ちます | どちらが正か分からなくなります | 移した時点で旧い側を止めます。止めるまでの期間を決めます |
ref() を使わずテーブル名を直書き | 依存関係が認識されず、実行順が崩れます | 参照はすべて ref() にします |
| assertion を付けません | 二重実行や欠損に気づけません | 最低でも uniqueKey を付けます |
incremental の絞り込み条件が甘いです | 同じ行が何度も追加されます | 取り込み条件と一意キーを必ず指定します |
| 全部を一度に移します | どこで壊れたか分かりません | 依存の少ないものから1つずつ |
| 本番のスキーマに直接書きながら開発します | 検証中のテーブルが利用者に見えます | 開発用のスキーマを分けます |
| 説明を書きません | 半年後に用途が分からなくなります | description を1行入れます |
まとめ
- 画面に貼ったSQLの一番の問題は、実行順が人の記憶頼りになること
- Dataform は
.sqlxファイルとref()だけで、依存関係と実行順を自動で解決します - テーブルを足しても、実行順の設定を直す作業が発生しません
uniqueKeyの検査は最初から付けます。二重実行に気づける唯一の仕掛け- Gitにつなぐと、過去の定義に戻せます。1人で使う場合でも価値があります
- 移行は1テーブルずつ行います。旧いスケジュール実行と数日並走させてから切り替えます
最初にやることは、いま動いているスケジュール実行を一覧にして、依存の少ないものを1つ選ぶことです。そこから始めれば、半日で仕組みの形が分かります。
参考になったら、いいねを押してください。





