移行では「同じ結果が出ているか」を定量的に示すことが重要です。dbtパッケージのdbt-audit-helperは、2つのRelationを比較し、件数・欠損・不一致行の要約を返してくれるため、移行の受け入れ基準を自動化しやすくなります。
本稿は、Analytics Engineer向けの資格対策で問われやすい概念(テストと診断の違い、再現性、CI連携)を踏まえ、実務で安全に使える差分検証パターンをまとめています。詳細仕様はdbt公式ドキュメントの安定部分を基に、バージョン差異が影響し得る箇所には注記を添えます。
移行では、従来系テーブル(旧)と新実装のテーブル(新)の間で、主キーごとの行の有無やカラム値の差異、重複や型の差異などを素早く可視化したい場面が多くあります。dbt-audit-helperは、2つのRelationを比較して、件数・一致/不一致・重複などのサマリを返すマクロを提供します。これにより、手書きのフルアウタージョインSQLよりも短時間で再現性の高い検証を組み込めます。
試験観点では「プロダクション品質の品質保証を、宣言的に・CIで再現できるか」が頻出テーマです。dbt-audit-helperをモデル(または診断専用モデル)として組み込めば、dbtのランと共に差分が自動集計され、結果を合否基準として用いることができます。
| 観点 | dbt-audit-helper | dbt_utils.equality | 手書きSQL |
|---|---|---|---|
| 目的 | 差分の要約・診断(可観測性重視) | 集合の同値性テスト(合否重視) | 自由度は高いが実装・保守コスト大 |
| 主キーの扱い | 主キー指定でフルアウタージョインに基づく比較がしやすい | 主キー不要(集合一致)だが重複が多い場合の解釈が難しい | 自由に設計可能だがバグ混入リスク |
| 出力 | 件数・不一致行のサマリテーブル(可視化/調査に最適) | テストの合否+失敗行の抽出 | 設計次第 |
| CI適性 | 高(サマリを基準にゲート可能) | 高(失敗時に即ブロック) | 中(追加スクリプトが必要) |
| 学習コスト | 低〜中(標準マクロの呼び出し) | 低(汎用テストの適用) | 高(クエリと運用の両面) |
移行時の差分検証フロー(抽象図)
packages.yml(最小)
packages:
- package: dbt-labs/dbt-audit-helper
version: ">=0.9.0,<1.0.0"基本手順は、packages.ymlにdbt-audit-helperを追加し、dbt depsで取得します。比較対象のRelation(旧/新)に対してSELECT権限があるプロファイルで実行してください。比較は大きなフルアウタージョインになる場合があるため、ウェアハウスのサイズや一時領域(中間結果)のクォータにも注意します。
dbtのベストプラクティスとして、差分のサマリを出す専用モデル(例: audit_orders)を作成し、CIでそのモデルをビルドしてゼロ件であること(もしくは許容閾値以下であること)を合否判定に使うと運用が安定します。マクロの引数名や詳細出力はパッケージのバージョンにより差異があり得るため、利用しているバージョンのドキュメントを確認してください(安定概念は「2 Relationの比較と要約」です)。
差分サマリモデルの雛形(audit_orders.sql)
{{ config(materialized='table') }}
-- 旧/新Relationを参照
{% set old_rel = ref('orders_legacy') %}
{% set new_rel = ref('orders') %}
-- 代表的な呼び出し例。利用可能な引数はバージョンに依存します。
-- primary_key: 差分照合に使う主キー(1列または複数列)
-- exclude_columns: 比較から除外する列(例: ロード時刻、追跡列)
{{ audit_helper.compare_relations(
a_relation=old_rel,
b_relation=new_rel,
primary_key=['order_id'],
exclude_columns=['_ingested_at', '_updated_at']
) }}
移行では完全一致をいきなり狙うより、揺れを制御しながら収束させるのが現実的です。以下のパターンを組み合わせると、安定して差分が減っていきます。
浮動小数の丸め、タイムゾーンの正規化、トリム/大文字小文字の正規化、監査列(ロード時刻・メタデータ)の除外は、どのDWHでも安定的に使える基本手筋です。
丸めと正規化を挟んだ比較(前処理モデルを経由)
-- 前処理(例): 旧/新それぞれで正規化したビューを用意しておき、比較はそのビューを参照
-- models/stg_orders_legacy_norm.sql
select
order_id,
upper(trim(customer_name)) as customer_name,
round(amount, 2) as amount,
to_timestamp_ntz(order_ts) as order_ts
from {{ ref('orders_legacy') }}
-- models/stg_orders_norm.sql
select
order_id,
upper(trim(customer_name)) as customer_name,
round(amount, 2) as amount,
to_timestamp_ntz(order_ts) as order_ts
from {{ ref('orders') }}
-- models/audit_orders.sql(比較)
{{ config(materialized='table') }}
{{ audit_helper.compare_relations(
a_relation=ref('stg_orders_legacy_norm'),
b_relation=ref('stg_orders_norm'),
primary_key=['order_id'],
exclude_columns=[]
) }}
差分サマリモデルをCIでビルドし、その結果に基づいてパイプラインをゲートします。ゼロ件一致を強制できない場合も、許容閾値(例: 欠損行0、値不一致<=10行)を決め、合否条件を明確化しておくと実務で揉めません。
dbtでは「単一クエリで0行を期待する」シンギュラーテストを作るだけで、任意の合否ロジックを表現できます。差分サマリモデルの結果を参照し、NG条件を返すSELECTを記述します。
シンギュラーテスト例(不一致・欠損が0件であること)
-- tests/no_diff_orders.sql
-- audit_orders(サマリ出力)に、差分があると行が出る想定
select *
from {{ ref('audit_orders') }}
where (mismatch_count > 0 or only_in_a > 0 or only_in_b > 0)
-- 上記のSELECTが1行以上返るとテスト失敗(CIでブロック)
# 参考: GitHub Actions(抜粋)
# - run: dbt deps
# - run: dbt run -s models/audit
# - run: dbt test -s tests/no_diff_orders.sql
大規模テーブルの比較は、全面比較ではなく、パーティション単位やサンプリングで段階的に進めるのが現実的です。まず直近N日だけ比較してパイプラインの健全性を担保し、その後バックフィル期間を広げていきます。これによりコストと時間を抑えつつ品質を確保できます。
また、主キーにクラスタリングやソートキーがあるとフルアウタージョインのコストが下がりやすいです。比較前に統計更新やテーブルの最適化(Snowflakeのクラスタリング、DatabricksのOPTIMIZEなど)を検討してください。
日付ウィンドウを絞った比較(varsで可変)
{{ config(materialized='table') }}
{% set days = var('compare_days', 7) %}
with legacy as (
select * from {{ ref('orders_legacy') }}
where order_date >= dateadd(day, -{{ days }}, current_date)
),
newer as (
select * from {{ ref('orders') }}
where order_date >= dateadd(day, -{{ days }}, current_date)
)
select * from (
{{ audit_helper.compare_relations(
a_relation=adapter.get_relation(database=target.database, schema=this.schema, identifier='legacy'),
b_relation=adapter.get_relation(database=target.database, schema=this.schema, identifier='newer'),
primary_key=['order_id']
) }}
)
浮動小数の丸め誤差、文字列コレーション差、タイムゾーンやサブ秒精度の差は、差分が減らない典型要因です。比較前に正規化し、どうしても揺れる列は明示的に除外します。
主キーが一意でない場合は、重複検知が先です。surrogate key(複数列のハッシュキー)を作ってから比較するのが有効です。dbt-utilsのsurrogate_keyマクロは安定して利用されています。
サロゲートキーと丸めの前処理例
-- サロゲートキー作成(dbt-utils)と丸めの統一
with base as (
select
{{ dbt_utils.surrogate_key(['order_id', 'warehouse_id']) }} as sk,
round(amount, 2) as amount,
to_timestamp_ntz(order_ts) as order_ts,
*
from {{ ref('orders') }}
)
select * from base
Analytics Engineer
問題 1
レガシーのorders_legacyと新実装のordersテーブルを比較し、主キーorder_idで行の欠損・重複・値不一致をサマリとして把握したい。監査列(_ingested_at, _updated_at)は比較から除外する。CIでこのサマリ結果を合否ゲートに使いたい。最も適切なアプローチはどれか。
正解: A
差分のサマリを得て合否ゲートに利用する要件には、dbt-audit-helperのcompare_relationsが適切です。主キーを指定して行レベルの欠損・不一致を収集し、監査列を除外して不要な差分を避け、シンギュラーテストでNG条件(例: mismatch_count>0など)を0件にするのが再現性と保守性の面で最適です。equalityテストは集合同値の合否には有効ですが、監査列の揺れやサマリ指標の出力には適しません。
dbt-audit-helperとdbt_utils.equalityの使い分けは?
equalityは「2つのRelationの集合が完全一致するか」をテストする合否用途に適しています。dbt-audit-helperは、欠損や不一致の件数・内訳をサマリとして把握する診断用途に向き、移行の進捗管理やレポーティングに向いています。移行初期はaudit-helperで差分の内訳を把握し、収束後にequalityで完全一致をゲートする運用が実務で安定します。
主キーが無い場合でも使えますか?
主キーがない場合は、そのままだと不一致の一致条件が曖昧になります。まずはユニークキーの整備、またはdbt-utilsのsurrogate_keyで複数列のハッシュキーを作り、比較キーとして指定することを推奨します。重複が多い場合は、先に重複検知・除去の前処理モデルを用意してください。
大規模テーブルの比較で時間やコストを抑えるには?
日付パーティションで直近N日から比較を開始し、段階的に窓を拡大してください。監査列を除外し、丸めやタイムゾーン正規化による不要差分を減らすことも有効です。ウェアハウス側の最適化(クラスタリングや統計更新)も効果があります。
NicheeLab編集部
データエンジニアリング・クラウド資格の専門家。Databricks・Snowflake等の認定資格を保有し、実務経験に基づいた問題作成・解説を行っています。NicheeLab運営。
dbt Model の基礎: SQL で定義する変換の最小単位
Analytics Engineer 向けに、dbt Model の定義、マテリアライゼーション、依存関係、インクリメン...
dbt Analytics Engineer 試験ガイド: 出題範囲・配点・申込の実務視点
dbt Analytics Engineer 認定の出題範囲、配点の考え方、申込から受験までの流れを、公式ドキュメントの...
dbt Cloud と dbt Core の違いと選び方:Analytics Engineer 試験に効く要点
dbt Cloud と dbt Core の機能差を、実務と資格対策の両面から整理。スケジューリング、IDE、RBAC、...
dbt プロジェクト構造ガイド: models / seeds / macros の実務レイアウト
Analytics Engineer 向けに、dbt プロジェクトのディレクトリ構造と命名規約、dbt_project....
dbt_project.yml の読み方:主要設定と命名を最短で掴む
dbt_project.yml の必須キー、命名解決(database.schema.identifier)、設定優先度...