- データベース
dbtとは?使い方をDuckDBで実行|test・incremental・v2の違い
結論
dbt(data build tool)は、SQL の SELECT 文を書くだけで、テーブルやビューの作成・依存順の実行・データのテスト・ドキュメント化までを引き受けるデータ変換ツールです。ELT の「T(変換)」だけを担い、データの取り込みはしません。
この記事でわかること
- dbt init→run→test→docs を DuckDB で実行した出力と、ref・source・Jinja の書き方
- incremental・seeds・snapshots の動きと、600万行での実測
- dbt Core(v1)・dbt v2・dbt platform の違いと、エラー原文13種の直し方
dbt は、データウェアハウス(DWH)にすでに入っている生データを、分析に使える形へ作り替える道具です。SQL を書く点は今までと同じですが、「どの順番で作るか」「結果が正しいか」「何がどこから来たか」を dbt が管理します。
flowchart TD
S[業務DB・SaaS・ログ] -->|E・L:取り込みツール| R[(DWH の raw)]
R -->|T:dbt が SELECT を<br>CREATE TABLE/VIEW に包む| ST[staging<br>名前・型をそろえる]
ST --> M[marts<br>集計・結合]
M --> B[BI・分析・AI]
T[test・docs] -.確かめる.-> M
よく調べられることの答えを先にまとめます。
| 知りたいこと | 答え | 詳しい節 |
|---|---|---|
| dbtとは | SELECT 文からテーブル・ビューを作り、依存順の実行・テスト・文書化をするツール | dbtとは |
| dbt でできないこと | データの取り込み(Extract・Load)。取り込みは別のツールが担う | dbtとは |
| dbt Core と dbt Cloud の違い | Core は手元で動かす無料の CLI、Cloud(今の名前は dbt platform)は実行・IDE・スケジュールを持つサービス | dbt Core・dbt v2・dbt platform の違い |
| dbt の使い方(最初の手順) | pip で入れる→dbt init→profiles.yml→models を書く→dbt build | dbtのインストール/dbt init |
| ref と source の違い | ref は dbt が作るモデル、source は取り込み済みの元テーブルを指す | models の書き方 |
| dbt test でできること | not_null・unique・relationships・accepted_values と、自作の SQL テスト | dbt test |
| dbt は無料か | CLI は無料。dbt platform は Developer プランが無料(月3,000モデルビルドまで) | dbt Core・dbt v2・dbt platform の違い |
| SQL だけで書くのと何が違う | 実行順・テスト・リネージ・差分更新を dbt が持つ。小さな集計なら SQL だけで済む | dbtとSQLだけの比較 |
確認環境:Ubuntu 24.04.5 LTS の上で、作業用のフォルダーに Python 3.12.3 の venv を作り、pip で dbt-core 1.12.6 と dbt-duckdb 1.11.1(DuckDB の Python パッケージ 2.0.0・Jinja2 3.1.6)を入れました。DuckDB のファイルに自作の顧客・注文・支払いデータ(日本語の氏名入り)と、イベントのデータ 100万行(incremental の計測では 600万行)を入れ、dbt init から run・test・snapshot・docs generate・docs serve(127.0.0.1 だけで待ち受け)までを実行しています。匿名の利用統計は、最初の実行より前に DBT_SEND_ANONYMOUS_USAGE_STATS=False・DO_NOT_TRACK=1 と dbt_project.yml の flags で止めました。dbt v2 はオープンソース版 dbt-oss 2.0.8 を GitHub の配布物から取り出して dbt parse・dbt ls を実行し、DuckDB への接続は、v2 が実行時に取りに行く DuckDB ドライバーのダウンロードがこの環境のプロキシで拒否されたため、公式ドキュメントの範囲で書いています。dbt platform(旧 dbt Cloud)の画面・料金は公式サイトと公式ドキュメントで確かめ、アカウントは作っていません。時間はいずれもメモリ 7GB・2 CPU を他の作業と共有するマシンでの値です。図は mermaid 11.16.0 で描画を確かめています(確認日 2026年10月29日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「dbtとは?使い方をDuckDBで実行|test・incremental・v2の違い」
dbtとは(ELTの「T」を担うデータ変換ツール)
この節では、dbt が何をする道具で、何をしない道具なのかが分かります。
dbt は「SELECT 文を書けば、それを CREATE TABLE や CREATE VIEW に包んで DWH の中で実行する」ツールです。 公式ドキュメントは dbt を「ウェアハウスの生データを信頼できるデータプロダクトに変換する。シンプルな SQL の select 文を書くと、dbt がモジュール化され保守しやすいデータモデルを作る」と説明しています。名前の dbt は data build tool の略です。
データ基盤の仕事は、大きく E(Extract:取り出す)・L(Load:入れる)・T(Transform:変換する)に分かれます。以前は DWH の外で変換してから入れる ETL が主流でしたが、DWH の計算力が上がったことで、先に入れてから DWH の中で変換する ELT が増えました。dbt はこの T だけを担います。取り込みは Fivetran・Airbyte・trocco などの取り込みツールや、自作のバッチが受け持ちます。
dbt がやること
SELECT 文からテーブル・ビューを作る(DDL は書かない)
ref() から依存関係を読み取り、正しい順番で実行する
not_null・unique などのテストを YAML で宣言して実行する
モデルの説明とリネージ(どこから来たか)を文書にする
差分だけ処理する incremental、履歴を残す snapshot
dbt がやらないこと
業務 DB や SaaS からのデータの取り込み
自前の計算エンジン(計算は DWH が行う)
グラフやダッシュボードの表示(BI ツールの役目)
定期実行(dbt Core 単体には無い。cron・Airflow・dbt platform で行う)
リアルタイムの処理(バッチで動く前提)
dbt を使うとき書くのは、主に次の3種類のファイルです。
- SQL(.sql):モデル。1ファイルが1つのテーブルかビューになる。中身は SELECT 文だけ。
- YAML(.yml):設定・テスト・説明。どの列が一意か、どの列に説明を付けるかを書く。
- Jinja:SQL の中に
{{ ref('...') }}や{% for %}を書けるテンプレート言語。dbt はこれを展開してから DWH に送る。
対応する DWH は Snowflake・BigQuery・Databricks・Redshift・PostgreSQL・DuckDB など多く、DWH ごとに「アダプター」と呼ばれる部品を入れて接続します。この記事では、手元で無料で動かせる DuckDB(1ファイルで動く分析用のデータベース)を使いました。
dbt Core・dbt v2・dbt platform(旧dbt Cloud)の違い
この節では、名前の似た dbt の提供形態の違いと、2026年10月29日時点での選び方が分かります。
2026年10月29日時点の公式ドキュメントでは、dbt は「Python で書かれた v1(dbt Core 1.x)」「Rust で書き直された v2」「サービスとしての dbt platform」の3つに整理されています。 以前「dbt Cloud」と呼ばれていたサービスは、今は dbt platform という名前です。また、公式のインストール手順では python -m pip install dbt で入るのが v2 になり、v1 の dbt --version も次のように v2 を案内しました。
Core:
- installed: 1.12.6
- latest: 1.12.6 - Up to date!
Plugins:
- duckdb: 1.11.1 - Up to date!
14:42:30 dbt v2 is available, and is the default for new installations. This is dbt Core v1.
New project - install v2: https://docs.getdbt.com/docs/local/install-dbt?utm_source=dbt-cli
| 形 | 中身 | 入れ方 | ライセンス・料金 |
|---|---|---|---|
| dbt Core(v1) | Python 製の CLI。アダプターは Python のパッケージ(dbt-duckdb 等) | pip install dbt-core dbt-duckdb |
Apache 2.0・無料。引き続きメンテナンスされる |
| dbt v2 | Rust 製のエンジン。解析が速く、YAML の誤りを厳しく検出する | pip install dbt・Homebrew・winget など |
無料。dbt Labs の v2 のライセンス契約。一部機能はサインインで使える |
| dbt-oss | v2 の基になったオープンソースのランタイム | pip install dbt-oss |
Apache 2.0 |
| dbt platform(旧 dbt Cloud) | ブラウザの IDE・ジョブのスケジュール・CI・文書の公開・権限管理 | Web でアカウントを作る | Developer 無料、Starter は1ユーザー月100ドル、Enterprise は見積もり |
dbt platform の料金(公式の料金ページ、2026年10月29日時点)は次のとおりです。課金の単位は「シート(開発者の人数)」と「成功したモデルのビルド回数」です。
| プラン | 価格 | 開発者シート | 月間モデルビルド | プロジェクト |
|---|---|---|---|---|
| Developer | 無料 | 1 | 3,000 | 1 |
| Starter | 1ユーザー月100ドル | 5 | 15,000 | 1 |
| Enterprise | 見積もり | 見積もり | 100,000 | 30 |
| Enterprise+ | 見積もり | 見積もり | 100,000 | 無制限 |
モデルビルドの数え方の目安として、この記事のプロジェクト(モデル7本)を1日1回 dbt build すると、月に約210ビルドです。モデル50本を毎日1回なら約1,500で、開発中の試行を足しても Developer の枠に収まりやすい規模です。1時間ごとに回すと24倍になる点は、見積もりのときに忘れやすいところです。
dbt v2 を同じプロジェクトで動かした結果
v2(dbt-oss 2.0.8)を取り出して、同じプロジェクトで dbt parse(プロジェクトの読み込みと解析)を実行しました。v1 の3.3秒に対して v2 は0.44秒でした(コマンドの開始から終了までの時間。3回の中央値)。
v2 は YAML の書き間違いにも厳しくなっています。description を descriptoin と打ち間違えたとき、v1 は警告も出さずに無視しましたが、v2 はエラーで止まりました。
[error] [UnusedConfigKey (dbt1060)]: While parsing config: Ignored unexpected key `"descriptoin"`. YAML path: `descriptoin`.
--> models/marts/_marts.yml:5:5
suggestion: Run 'dbt deps' to see the latest fusion compatible packages. For compatibility errors, try the autofix script: https://github.com/dbt-labs/dbt-autofix
一方で、v2 で DuckDB に接続すると、v2 は DuckDB の ADBC ドライバーを実行時にダウンロードしようとし、この環境ではプロキシに拒否されて次のエラーになりました。社内ネットワークで v2 を使うときは、公式の「dbt v2 networking requirements」に載っている接続先を許可する必要があります。
[error] [DbDriverFailed (dbt1308)]: Failed to create schema 'main' in database 'shop2' in remote for seed.shop.payment_methods: Failed to load `duckdb` driver from name, then failed to load it from the CDN.
First error:
NotFound: Driver not found: duckdb (sqlstate: [0, 0, 0, 0, 0], vendor_code: 0)
Second error:
IO: HTTP error: CONNECT proxy failed: proxy server responded 403/403 (sqlstate: [0, 0, 0, 0, 0], vendor_code: 0)
公式の移行ガイドによると、v2 が対応するアダプターは BigQuery・Databricks・Redshift・Snowflake・DuckDB(CLI のみ)などで、PostgreSQL のように v2 の一覧に無い DWH は v1 を使います。v1 のプロジェクトをすぐに移す必要は無く、v1 は引き続きサポートされると書かれています。この記事のコマンドとファイルの書き方は、v1 と v2 で共通です。
dbtのインストール(dbt-coreとdbt-duckdb)
この節では、dbt を手元に入れて、接続を確かめるまでの手順が分かります。
v1 は「dbt-core」と、使う DWH のアダプター(dbt-duckdb・dbt-postgres・dbt-snowflake など)を pip で一緒に入れます。 プロジェクトごとに venv を作るのが基本です。
venv を作って dbt を入れる
python3 -m venv .venv → . .venv/bin/activate → pip install dbt-core dbt-duckdb。v2 を使うなら pip install dbt。
利用統計の送信を止める(必要なら)
v1 は dbt_project.yml の flags: send_anonymous_usage_stats: false か環境変数 DBT_SEND_ANONYMOUS_USAGE_STATS=False。v2 は DBT_ENGINE_SEND_ANONYMOUS_USAGE_STATS=false か DO_NOT_TRACK=1。
dbt init でプロジェクトを作る
dbt init shop。対話でアダプターと接続先を聞かれる。--skip-profile-setup を付けると接続設定は後で書く。
profiles.yml に接続先を書く
既定の場所は ~/.dbt/profiles.yml。環境変数 DBT_PROFILES_DIR で別のフォルダーにできる。
dbt debug で接続を確かめる
「All checks passed!」が出れば準備完了。
pip で入れた venv の大きさを測ると、364MB ありました。内訳の大きいものは、dbt-core 1.12 が依存として入れる dbt-core-experimental-parser(v2 のパーサーの実行ファイル、174MB)、DuckDB 本体(65MB)、babel(33MB)です。この実行ファイルは v1.12 で --use-v2-parser を付けたときに使われるもので、付けない普段の実行では呼ばれません(dbt-core のソースコードで確かめました)。容量が限られた CI のイメージでは、この大きさを見込んでおくと慌てずに済みます。
dbt debug が通ったときの出力の最後は次のとおりです。
14:44:08 Registered adapter: duckdb=1.11.1
14:44:09 Connection test: [OK connection ok]
14:44:09 All checks passed!
dbt initとプロジェクトの中身(profiles.yml・dbt_project.yml)
この節では、dbt init で何ができ、どのファイルに何を書くのかが分かります。
dbt のプロジェクトは「dbt_project.yml があるフォルダー」です。 dbt init を実行すると、次の雛形ができました(example のモデルは消して使います)。
shop/.gitignore
shop/README.md
shop/analyses/.gitkeep
shop/dbt_project.yml
shop/macros/.gitkeep
shop/models/example/my_first_dbt_model.sql
shop/models/example/my_second_dbt_model.sql
shop/models/example/schema.yml
shop/seeds/.gitkeep
shop/snapshots/.gitkeep
shop/tests/.gitkeep
| フォルダー・ファイル | 入れるもの |
|---|---|
| dbt_project.yml | プロジェクト名・使う profile 名・フォルダーごとの既定の設定 |
| models/ | モデル(SELECT 文の .sql、Python の .py)と、説明・テストの .yml |
| seeds/ | 小さな CSV(コード表など)。dbt seed でテーブルになる |
| snapshots/ | 変化の履歴を残す設定 |
| tests/ | 自作の SQL テスト(返った行が「失敗」) |
| macros/ | Jinja のマクロ(使い回す SQL の部品) |
| target/ | 実行で生まれる成果物(展開後の SQL・manifest.json など)。Git には入れない |
接続先は、プロジェクトの外にある profiles.yml に書きます。パスワードなどをリポジトリに入れないためです。DuckDB なら、ファイルの場所を書くだけです。
shop: # dbt_project.yml の profile: と同じ名前
target: dev # 既定で使う接続先
outputs:
dev:
type: duckdb
path: /srv/work/data/shop.duckdb
threads: 2 # 同時に実行するモデルの数
Snowflake や PostgreSQL では、ユーザー名・パスワードを "{{ env_var('DBT_PASSWORD') }}" のように環境変数から読む書き方が公式ドキュメントで案内されています。開発用(dev)と本番用(prod)を outputs に並べ、dbt run --target prod で切り替えます。
dbt_project.yml では、フォルダーごとの既定の作り方(マテリアライズ)を決めました。staging はビュー、marts はテーブルです。
name: 'shop'
profile: 'shop'
flags:
send_anonymous_usage_stats: false
models:
shop:
staging:
+materialized: view
marts:
+materialized: table
dbtのmodelsの書き方:staging・marts、refとsource
この節では、モデルの分け方と、ref・source の使い方が、動かしたコードで分かります。
dbt のモデルは「1ファイル=1つの SELECT 文=1つのテーブルかビュー」です。 元のテーブルは {{ source() }}、dbt が作る別のモデルは {{ ref() }} で指します。直接テーブル名を書かないことで、dbt が依存関係を知り、実行順を決められます。
まず、取り込み済みの元テーブルを YAML で「source」として登録します。
version: 2
sources:
- name: raw
schema: raw
tables:
- name: customers
- name: orders
- name: payments
- name: events
staging のモデルでは、元のテーブルを1対1で受けて、列名と型だけをそろえます。
-- models/staging/stg_orders.sql
select
id as order_id,
user_id as customer_id,
cast(order_date as date) as ordered_on,
status
from {{ source('raw', 'orders') }}
marts のモデルでは、staging を ref で受けて結合・集計します。顧客ごとの集計は次のとおりです。
-- models/marts/dim_customers.sql
select
c.customer_id,
c.customer_name,
c.plan,
count(o.order_id) as order_count,
min(o.ordered_on) as first_ordered_on,
coalesce(sum(o.amount_yen), 0) as lifetime_yen
from {{ ref('stg_customers') }} as c
left join {{ ref('fct_orders') }} as o
on o.customer_id = c.customer_id and o.status <> 'returned'
group by all
dbt はこの ref を、実際のテーブル名に置き換えてから DWH に送ります。target/run/ に残った、実際に実行された SQL は次のとおりでした。書いたのは SELECT だけですが、dbt が CREATE TABLE で包んでいます。
create table
"shop"."main"."dim_customers__dbt_tmp"
as (
select
c.customer_id,
...
from "shop"."main"."stg_customers" as c
left join "shop"."main"."fct_orders" as o
on o.customer_id = c.customer_id and o.status <> 'returned'
group by all
);
dbt はいったん __dbt_tmp の名前で作ってから、元のテーブルと入れ替えます。作成の途中で失敗しても、前のテーブルが壊れずに残るのはこのためです。
モデルの層の分け方
1staging(stg_)
元のテーブル1つに1モデル。列名・型・単位をそろえるだけで、結合や集計はしない。ビューで作ることが多い。
2intermediate(int_)
複数の staging をつなぐ途中の計算。大きなプロジェクトで marts の SQL が長くなったときに分ける。
3marts(fct_・dim_)
BI や分析で使う完成形。出来事の表は fct_(fact)、ものの表は dim_(dimension)と名付けることが多い。
この分け方と名前の付け方は dbt の公式ガイド「How we structure our dbt projects」で勧められている形で、決まりではありません。最初は staging と marts の2層で始めて、困ってから intermediate を足す進め方でも動きます。
dbt runとマテリアライズ(view・table・incremental)
この節では、dbt run の出力の読み方と、モデルをどう作るか(マテリアライズ)の選び方が分かります。
dbt run は、ref のつながりから順番を決めて、モデルを作ります。 実行すると次のように出ました。stg_orders と stg_payments が終わってから fct_orders、fct_orders が終わってから dim_customers が始まっています。threads: 2 なので、依存の無いものは2本ずつ並んで動きます。
14:44:28 Found 7 models, 8 data tests, 1 seed, 1 snapshot, 4 sources, 512 macros
14:44:28 Concurrency: 2 threads (target='dev')
14:44:28 1 of 7 START sql incremental model main.fct_events_daily ....................... [RUN]
14:44:28 2 of 7 START sql view model main.stg_customers ................................. [RUN]
14:44:29 2 of 7 OK created sql view model main.stg_customers ............................ [OK in 0.30s]
14:44:29 3 of 7 START sql view model main.stg_orders .................................... [RUN]
14:44:29 1 of 7 OK created sql incremental model main.fct_events_daily .................. [OK in 0.32s]
14:44:29 4 of 7 START sql view model main.stg_payments .................................. [RUN]
14:44:29 3 of 7 OK created sql view model main.stg_orders ............................... [OK in 0.04s]
14:44:29 4 of 7 OK created sql view model main.stg_payments ............................. [OK in 0.04s]
14:44:29 5 of 7 START sql table model main.fct_orders ................................... [RUN]
14:44:29 6 of 7 START sql table model main.payments_by_method ........................... [RUN]
14:44:29 6 of 7 OK created sql table model main.payments_by_method ...................... [OK in 0.10s]
14:44:29 5 of 7 OK created sql table model main.fct_orders .............................. [OK in 0.11s]
14:44:29 7 of 7 START sql table model main.dim_customers ................................ [RUN]
14:44:29 7 of 7 OK created sql table model main.dim_customers ........................... [OK in 0.03s]
14:44:29 Finished running 1 incremental model, 3 table models, 3 view models in 0 hours 0 minutes and 0.93 seconds (0.93s).
14:44:29 Done. PASS=7 WARN=0 ERROR=0 SKIP=0 NO-OP=0 REUSED=0 TOTAL=7
作った結果は、dbt show --select dim_customers で DWH に入らずに確かめられます。日本語の氏名もそのまま出ました(列の幅は少しずれます)。
| customer_id | customer_name | plan | order_count | first_ordered_on | lifetime_yen |
| ----------- | ------------- | ---- | ----------- | ---------------- | ------------ |
| 3 | 高橋 美咲 | free | 1 | 2026-09-03 | 3000 |
| 4 | 田中 健 | pro | 2 | 2026-09-03 | 19700 |
| 5 | 伊藤 さくら | free | 1 | 2026-09-05 | 4600 |
| 1 | 佐藤 花子 | free | 2 | 2026-09-01 | 2860 |
| 2 | 鈴木 一郎 | pro | 1 | 2026-09-01 | 5400 |
| マテリアライズ | 作られるもの | 向く場面 | 注意 |
|---|---|---|---|
| view | ビュー | staging・軽い変換 | 読むたびに計算される |
| table | テーブル(毎回作り直す) | BI から何度も読む marts | 毎回全件を計算する |
| incremental | テーブル(差分だけ足す) | 大きくて増え続けるログ・イベント | SQL に差分の条件を書く |
| ephemeral | 作らない(呼び出し側に CTE として埋め込む) | 小さな部品 | 単体で中身を見にくい |
| materialized_view | DWH のマテリアライズドビュー | 対応する DWH で自動更新したいとき | DWH ごとに対応が違う |
ビューとマテリアライズドビューの違いそのものは、関連記事「SQLビューとマテリアライズドビュー|違い・REFRESH・使い分け」で扱っています。
よく使う実行の絞り込みは次のとおりです。dbt ls で、実行する前に対象を確かめられます。
1本だけ
dbt run -s fct_orders。-s は --select の略。
-m(--models)は非推奨
上流も含める
dbt run -s +dim_customers。前に + を付けると、元になるモデルも全部。
作り直しのとき
下流も含める
dbt run -s stg_payments+。後ろに + で、影響を受けるモデルも全部。
列を変えたとき
全部を順に
dbt build。seed・run・snapshot・test を依存順にまとめて実行。
CI はこれ1本
dbt ls -s stg_payments+ --resource-type model の出力は次のとおりで、stg_payments を変えると3本のモデルに影響することが分かりました。
shop.marts.dim_customers
shop.marts.fct_orders
shop.marts.payments_by_method
shop.staging.stg_payments
dbt testの書き方:not_null・unique・relationshipsと自作テスト
この節では、データのテストを YAML で宣言する方法と、失敗したときの出力が分かります。
dbt のテストは「失敗した行を探す SELECT」です。0行なら合格、1行でも返れば不合格になります。 よく使う4つは YAML に書くだけで使えます(generic test)。
version: 2
models:
- name: fct_orders
description: "注文1件につき1行。支払方法ごとの金額と税込金額"
columns:
- name: order_id
data_tests:
- unique
- not_null
- name: customer_id
data_tests:
- not_null
- relationships:
arguments:
to: ref('stg_customers')
field: customer_id
- name: status
data_tests:
- accepted_values:
arguments:
values: ['placed', 'shipped', 'completed', 'returned']
| テスト | 確かめること | 失敗する例 |
|---|---|---|
| not_null | 列に NULL が無い | 顧客IDが空の注文 |
| unique | 列の値が重ならない | 結合で注文が2行に増えた |
| relationships | 相手の表に必ず存在する(外部キーの代わり) | 顧客表に無い顧客IDの注文 |
| accepted_values | 決めた値のどれか | 想定外の状態「canceled」 |
dbt 1.10 以降の版では、テストの引数を arguments: の下に書く形になりました。以前の書き方(to: と field: を直接書く)でも動きますが、次の警告が出ます。
[WARNING][MissingArgumentsPropertyInGenericTestDeprecation]: Deprecated
functionality
Found top-level arguments to test `relationships` defined on 'fct_orders' in
package 'shop' (models/marts/_marts.yml). Arguments to generic tests should be
nested under the `arguments` property.
決まった形に当てはまらない確認は、tests/ に SQL を置きます(singular test)。返った行が失敗の行です。
-- tests/assert_amount_not_negative.sql
-- 金額がマイナスの注文があれば失敗(返った行が失敗の行)
select order_id, amount_yen
from {{ ref('fct_orders') }}
where amount_yen < 0
わざと「顧客表に無い顧客ID 99」「想定外の状態 canceled」「マイナス300円の支払い」を元データに入れて dbt build すると、3つのテストが落ち、テストが落ちたモデルの下流(dim_customers)は作られずに SKIP になりました。壊れたデータが BI まで流れないための仕組みです。
14:49:20 5 of 9 FAIL 1 assert_amount_not_negative ....................................... [FAIL 1 in 0.08s]
14:49:20 4 of 9 FAIL 1 accepted_values_fct_orders_status__placed__shipped__completed__returned [FAIL 1 in 0.09s]
14:49:20 8 of 9 FAIL 1 relationships_fct_orders_customer_id__customer_id__ref_stg_customers_ [FAIL 1 in 0.04s]
14:49:20 [ERROR]: in test relationships_fct_orders_customer_id__customer_id__ref_stg_customers_ (models/marts/_marts.yml)
14:49:20 Got 1 result, configured to fail if != 0
14:49:32 15 of 17 SKIP relation main.dim_customers ...................................... [SKIP]
14:49:32 Done. PASS=11 WARN=0 ERROR=3 SKIP=3 NO-OP=0 REUSED=0 TOTAL=17
relationships のテストが実際に送った SQL は、target/compiled/ に残っています。LEFT JOIN して相手が NULL の行を探すだけの SQL です。
with child as (
select customer_id as from_field
from "shop"."main"."fct_orders"
where customer_id is not null
),
parent as (
select customer_id as to_field
from "shop"."main"."stg_customers"
)
select from_field
from child
left join parent on child.from_field = parent.to_field
where parent.to_field is null
失敗した行そのものを見たいときは、dbt test --store-failures で失敗した行をテーブルに残せます。出力に「See test failures: select * from "shop"."main_dbt_test__audit"."assert_amount_not_negative"」のように、見るための SQL が表示されました。止めるほどではない確認は、テストの config に severity: warn を書くと警告だけにできます(公式ドキュメント)。
dbt のテストは、アプリのテストと同じく Git のプルリクエストの CI で回すと効果が大きくなります。CI の組み立ては関連記事「CI/CDとは|GitHub Actionsで最初のパイプラインを作るまで」で扱っています。
dbtのJinjaとマクロ(ループ・関数の共通化)
この節では、SQL に Jinja を混ぜて繰り返しを減らす方法と、マクロの作り方が分かります。
dbt は SQL を DWH に送る前に Jinja を展開します。 支払方法ごとの列を4本作る SQL を、for ループで書きました。
-- models/marts/fct_orders.sql
{% set methods = ['card', 'bank', 'paypay', 'coupon'] %}
with payments as (
select * from {{ ref('stg_payments') }}
)
select
o.order_id,
o.customer_id,
o.ordered_on,
o.status,
{% for m in methods %}
sum(case when p.method = '{{ m }}' then p.amount_yen else 0 end) as {{ m }}_yen,
{% endfor %}
sum(p.amount_yen) as amount_yen,
{{ with_tax('sum(p.amount_yen)') }} as amount_with_tax_yen
from {{ ref('stg_orders') }} as o
left join payments as p using (order_id)
group by all
最後の with_tax は macros/ に置いた自作のマクロです。引数の既定値も書けます。
-- macros/with_tax.sql
{% macro with_tax(column, rate=0.10) %}
cast(round({{ column }} * (1 + {{ rate }})) as integer)
{% endmacro %}
dbt compile で展開後の SQL を確かめると、ループが4本の CASE 式に、マクロが計算式に置き換わっていました(空行は省略)。
sum(case when p.method = 'card' then p.amount_yen else 0 end) as card_yen,
sum(case when p.method = 'bank' then p.amount_yen else 0 end) as bank_yen,
sum(case when p.method = 'paypay' then p.amount_yen else 0 end) as paypay_yen,
sum(case when p.method = 'coupon' then p.amount_yen else 0 end) as coupon_yen,
sum(p.amount_yen) as amount_yen,
cast(round(sum(p.amount_yen) * (1 + 0.1)) as integer)
as amount_with_tax_yen
Jinja は便利ですが、増やしすぎると SQL として読めなくなります。「展開後の SQL を見て、人が読める範囲にとどめる」のが目安です。WITH 句(CTE)で段階を分ける書き方は関連記事「SQL WITH句の使い方|CTEと再帰クエリをコピペSQLと実行結果で解説」で扱っています。
よく使われるマクロの集まりは「パッケージ」として配られています。dbt_utils などを packages.yml に書いて dbt deps で入れる流れが公式ドキュメントで案内されています(この記事の環境では外部からの取得をしていないため、パッケージは入れていません)。
Python のモデル
SQL で書きにくい処理は、Python でもモデルを書けます。dbt-duckdb では次の .py を置くだけで、テーブルが作られました。
# models/marts/orders_rank_py.py
def model(dbt, session):
dbt.config(materialized="table")
rel = dbt.ref("fct_orders") # DuckDB のリレーション
return rel.filter("status = 'completed'").order("amount_yen desc").limit(3)
14:55:55 1 of 1 OK created python table model main.orders_rank_py ....................... [OK in 0.15s]
ただし、dbt show -s orders_rank_py は「Parser Error: zero-length delimited identifier」で失敗しました(Python のコードを SQL として送ろうとするため)。Python モデルの結果は、作られたテーブルを SQL で読んで確かめます。Python モデルを使える DWH は限られるので、公式ドキュメントの対応表で確かめてから使います。
dbtのseedsとsnapshots(コード表と変更履歴)
この節では、CSV を取り込む seeds と、データの変化を記録する snapshots の使い方が分かります。
seeds は「Git で管理したい小さな CSV」をテーブルにする機能です。 支払方法のコードと日本語名の対応表を seeds/payment_methods.csv に置きました。
method,label,is_cash
card,クレジットカード,false
bank,銀行振込,true
paypay,PayPay,false
coupon,クーポン,false
dbt seed で「INSERT 4」と出てテーブルになり、モデルからは {{ ref('payment_methods') }} で参照できます。結合した結果は次のとおりです(is_cash は自動で真偽値の型になりました)。
| method_label | is_cash | payments | amount_yen |
| ------------ | ------- | -------- | ---------- |
| クレジットカード | False | 5 | 24380 |
| 銀行振込 | True | 2 | 13100 |
| PayPay | False | 1 | 880 |
| クーポン | False | 1 | 500 |
seeds は数十〜数百行のコード表向けです。大きなデータや毎日変わるデータは、取り込みツールで入れて source として扱います。
snapshots は、上書きされる元データの「前の状態」を残す機能です。 顧客のプランやメールアドレスは上書きされるので、いつ free から pro に変わったかが分からなくなります。dbt 1.9 以降の YAML の書き方で設定しました。
snapshots:
- name: customers_snapshot
relation: source('raw', 'customers')
config:
schema: snapshots
unique_key: id
strategy: check
check_cols: ['plan', 'email']
1回目の dbt snapshot のあと、元データで「顧客1のプランを pro に」「顧客3のメールを登録」と書き換え、2回目を実行しました。変わった顧客は、前の行に終わりの時刻(dbt_valid_to)が入り、新しい行が足されています。
┌───────┬─────────┬────────────────────┬────────────┬──────────┐
│ id │ plan │ email │ valid_from │ valid_to │
├───────┼─────────┼────────────────────┼────────────┼──────────┤
│ 1 │ free │ hanako@example.com │ 14:45:11 │ 14:45:16 │
│ 1 │ pro │ hanako@example.com │ 14:45:16 │ NULL │
│ 3 │ free │ NULL │ 14:45:11 │ 14:45:16 │
│ 3 │ free │ misaki@example.com │ 14:45:16 │ NULL │
└───────┴─────────┴────────────────────┴────────────┴──────────┘
一つ気を付ける点として、schema: snapshots と書いたのに、作られたスキーマ名は main_snapshots でした。dbt の既定では「接続先のスキーマ名+_+指定したスキーマ名」になるためです(公式ドキュメントの custom schema の説明どおり)。指定どおりの名前にしたいときは generate_schema_name マクロを上書きします。
strategy は2種類あります。更新日時の列が信頼できるなら timestamp(その列が変わった行だけを記録)、無ければ check(指定した列の値を比べる)を使います。snapshot は「元データが上書きされる前に」実行しないと履歴を取りこぼすため、取り込みの直後に回します。
dbtのincrementalモデルの書き方と600万行での実測
この節では、差分だけを処理する incremental モデルの書き方と、実際にどれだけ読む量が減るかが分かります。
incremental は、初回は全件でテーブルを作り、2回目以降は条件に合う新しい行だけを処理して足します。 イベントを日ごとに集計するモデルを次のように書きました。
-- models/marts/fct_events_daily.sql
{{ config(materialized='incremental', unique_key='event_date') }}
select
cast(occurred_at as date) as event_date,
count(*) as events,
sum(amount) as amount
from {{ source('raw', 'events') }}
{% if is_incremental() %}
where occurred_at >= (select max(event_date) from {{ this }})
{% endif %}
group by 1
{{ this }} は「今あるこのモデルのテーブル」を指します。条件を「最後の日以降」にしたのは、最後の日の途中までしか集計できていない分を、次の回で作り直すためです。2回目の実行で dbt が送った SQL は、unique_key の日付の行を消してから入れ直す形でした(dbt-duckdb の既定の動き)。
delete from "shop"."main"."fct_events_daily"
where (event_date) in (
select (event_date)
from "fct_events_daily__dbt_tmp20261030144541786262"
);
insert into "shop"."main"."fct_events_daily" ("event_date", "events", "amount")
(
select "event_date", "events", "amount"
from "fct_events_daily__dbt_tmp20261030144541786262"
)
9月の30日分600万行を入れた状態から、10月1日分の5万行を足して比べました。
| 実行 | 読んだ行 | モデルの時間(3回) |
|---|---|---|
--full-refresh(全件) |
6,050,000行 | 0.22〜0.57秒 |
| incremental(差分) | 250,000行(9月30日と10月1日) | 0.56〜0.76秒 |
読む行は4.1%に減りましたが、DuckDB では時間は縮みませんでした。 600万行は DuckDB には小さく、差分のときに増える「一時テーブル→削除→挿入」の手間のほうが目立ったためです。incremental が効くのは、読む量に応じて料金がかかる BigQuery のような DWH や、全件の計算に何分もかかる大きさのときです。「大きいから incremental」ではなく、「全件だと時間か料金が困る」ときに使うのが判断の分かれ目です。
- ロジックを変えたら
--full-refresh:過去の行は前のロジックで作られたままです。 - unique_key を書かないと追記だけになる:同じ日を2回処理すると行が重なります(公式ドキュメント)。unique_key の列に NULL を入れないことも公式で注意されています。
- 列を足したとき:既定の
on_schema_change: ignoreでは新しい列が反映されません。append_new_columnsなどを選びます。
戦略(incremental_strategy)は append・merge・delete+insert・insert_overwrite・microbatch があり、DWH ごとに使えるものが違います(公式の Incremental strategy のページ)。
dbt docs generateとリネージ(依存関係の図)
この節では、dbt が作るドキュメントとリネージ(データの来た道)の見方が分かります。
dbt docs generate は、モデルの説明・列・型・依存関係を1つの Web ページにまとめます。 実行すると target/ に次のファイルができました。
| ファイル | 大きさ | 中身 |
|---|---|---|
| index.html | 1,818,461バイト | ドキュメントの画面(1ファイルで完結) |
| manifest.json | 670,210バイト | プロジェクトの全ノードと依存関係・設定 |
| catalog.json | 10,718バイト | DWH から読んだ列と型 |
| run_results.json | 16,609バイト | 直前の実行の結果と時間 |
dbt docs serve --host 127.0.0.1 --port 8154 --no-browser で手元に表示用のサーバーを立て、curl で index.html と manifest.json が200で返ることを確かめました。画面の右下のボタンでリネージのグラフが開きます。
manifest.json の parent_map から読み取った、このプロジェクトのリネージは次のとおりです(seed から作る payments_by_method と、イベントの fct_events_daily は省いています)。
flowchart TD
rc[raw<br>customers] --> sc[stg_<br>customers]
ro[raw<br>orders] --> so[stg_<br>orders]
rp[raw<br>payments] --> sp[stg_<br>payments]
so --> fo[fct_orders]
sp --> fo
fo --> dc[dim_customers]
sc --> dc
manifest.json は JSON なので、プログラムからも読めます。「このモデルを変えたら、どのダッシュボードの元になる表が変わるか」を調べる、CI で変わったモデルとその下流だけを実行する(--select state:modified+)、といった使い方の土台になります。また、利用統計を止めた設定は manifest.json の metadata にも send_anonymous_usage_stats: false として書き込まれていました。
dbtとSQLだけで書く場合の比較(判断表)
この節では、dbt を入れるべきか、SQL のスクリプトだけで済ませるべきかの判断材料が分かります。
同じ変換(stg_orders・stg_payments・fct_orders)を、dbt を使わない SQL のファイル1本でも作り、DuckDB で実行しました。
| 比べたこと | SQL のファイルだけ | dbt |
|---|---|---|
| 実行時間(起動から終了まで) | 0.22〜0.26秒 | dbt build 全体で4.9〜5.0秒(うち処理1.3〜1.5秒) |
| 書く量 | CREATE 文を自分で書く | SELECT だけ。モデル7本で69行 |
| 実行の順番 | ファイルに書いた順。間違えるとエラー | ref から自動で決まる |
| テスト | 自分で SELECT を書いて目で見る | YAML で宣言。落ちたら下流を止める |
| どこから来たか | SQL を読んで追う | docs のリネージと manifest.json |
SQL のファイルで、ビューを作る前にそのビューを使う CREATE を書くと、次のエラーで止まりました。表が増えるほど、この順番を人が管理するのが重くなります。
CatalogException Catalog Error: Table with name stg_orders does not exist!
Did you mean "shop.stg_orders"?
dbt の起動には毎回3秒前後かかります(v1)。 数本の表を作るだけなら SQL のほうが速く、仕組みも少なくて済みます。判断の目安をまとめます。
- 1集計の表が数本で、1人が管理している → SQL のファイルと crondbt の起動時間と学ぶ手間のほうが大きい
- 2表が増えて、作る順番を人が覚えている → dbt を検討ref で順番を任せられる
- 3「数字が合わない」問い合わせが繰り返し来る → dbt のテストunique・relationships で壊れた時点で止める
- 4複数人で SQL を直し、レビューしたい → dbt と Gitプルリクエストで差分が読める
- 5スケジュールや IDE も含めて運用を任せたい → dbt platformCI 基盤を誰が保守するかで決める
GUI で処理をつなぐ ETL ツールと比べると、dbt はコードなので差分のレビューと履歴が残る一方、SQL を書ける人が必要です。リアルタイムの処理や、DWH にまだ入っていないデータの加工は dbt の範囲外です。
モデルの中でテーブル名を直接書く
from main.stg_orders と書く。dbt が依存を知らず、順番が狂う・リネージに出ない{{ ref('stg_orders') }}・{{ source('raw', 'orders') }} で書くdev と prod でスキーマが違っても同じ SQL で動く
marts に何でも詰め込む
元の列名が変わっても、直すのは staging の1本で済む
dbt で書く SQL の中身は普通の SQL です。集計でよく使う書き方は関連記事「SQLウィンドウ関数の使い方|累計・前月比・上位N件を実行結果で解説」で扱っています。
dbtのよくあるエラー(原文つき)と直し方
この節では、dbt を使い始めたときに出やすいエラーの原文と、原因・直し方が分かります。
dbt のエラーは「Compilation Error(dbt が SQL を作る段階)」と「Runtime Error / Database Error(DWH が実行する段階)」に分かれます。 前者は ref・Jinja・YAML の誤り、後者は SQL そのものの誤りです。
| エラーの原文(要点) | 原因 | 直し方 |
|---|---|---|
| profiles.yml file [ERROR not found] | profiles.yml が無い・別の場所にある | ~/.dbt/ か DBT_PROFILES_DIR の場所に置く |
| No dbt_project.yml found at expected path | プロジェクトの外で dbt を実行した | dbt_project.yml のあるフォルダーへ移る |
| depends on a node named 'stg_order' which was not found | ref の名前の打ち間違い・モデルが無い | ファイル名(拡張子なし)と合わせる |
| depends on a source named 'raw.order' which was not found | source の YAML に登録が無い | sources の tables に足す |
| Binder Error: Referenced column "amount_jpy" not found | 元テーブルに無い列名(DWH のエラー) | 元の列名を確かめる |
| Unexpected end of template. Jinja was looking for ... 'endif' | {% if %} の閉じ忘れ | {% endif %} を書く |
| Parsing Error ... Syntax error near line 12 | YAML の字下げのずれ | 表示された行の前後の字下げをそろえる |
| Found a cycle: model.shop.dim_customers --> model.shop.fct_orders | ref が輪になっている | 共通部分を別のモデルに切り出す |
| Got 1 result, configured to fail if != 0 | テストが失敗の行を見つけた | compiled code の SQL を実行して行を見る |
| Could not set lock on file ... Conflicting lock is held | DuckDB のファイルを別のプロセスが開いている | 表示された PID のプロセスを閉じる |
ref の打ち間違い(Compilation Error)の原文です。どのファイルの、どの名前が見つからないかが出ます。
14:48:26 [ERROR]: Encountered an error:
Compilation Error
Model 'model.shop.fct_orders' (models/marts/fct_orders.sql) depends on a node named 'stg_order' which was not found
列名の誤り(Runtime Error)は、DWH が返したメッセージがそのまま出ます。DuckDB なら「Binder Error」、PostgreSQL なら「column ... does not exist」のように DWH ごとに文言が変わります。
14:48:30 [ERROR]: in model stg_payments (models/staging/stg_payments.sql)
14:48:30 Runtime Error in model stg_payments (models/staging/stg_payments.sql)
Binder Error: Referenced column "amount_jpy" not found in FROM clause!
YAML の字下げを1文字ずらしたときは、前後の行を示して教えてくれました。YAML の書き方の基本は関連記事「YAMLの書き方|構文一覧とパース結果、落とし穴10例を実測」で扱っています。
Parsing Error
Error reading shop: marts/_marts.yml - Runtime Error
Syntax error near line 12
------------------------------
9 | data_tests:
10 | - unique
11 | - not_null
12 | - name: customer_id
13 | data_tests:
Jinja の閉じ忘れと、ref の輪(循環)は次のとおりです。循環は dbt parse では出ず、dbt run のときに出ました。
Compilation Error in model broken (models/marts/broken.sql)
Unexpected end of template. Jinja was looking for the following tags: 'elif' or 'else' or 'endif'. The innermost block that needs to be closed is 'if'.
line 1
{% if is_incremental() %}
Compilation Error
Found a cycle: model.shop.dim_customers --> model.shop.fct_orders
DuckDB で多いのが、ファイルのロックです。DuckDB のファイルを Python や DBeaver で開いたまま dbt run すると、次のエラーになりました。書き込めるのは1つのプロセスだけなので、開いている側を閉じてから実行します。
14:49:54 [ERROR]: Encountered an error:
IO Error: Could not set lock on file "/srv/s4/r17/t154/data/shop.duckdb": Conflicting lock is held in /usr/bin/python3.12 (PID 27730). See also https://duckdb.org/docs/current/connect/concurrency
このほか、dbt run -m を使うと「Usage of `--models`, `--model`, and `-m` is deprecated in favor of `--select`」という警告が出ました。v2 では -m が使えなくなるため、-s に書き換えておきます。
エラーで止まったら、次の順に見ると早く原因に着きます。
よくある質問
この節では、dbt について検索でよく聞かれることへの短い答えが分かります。
Q. dbtとは何ですか? A. SQL の SELECT 文からテーブルやビューを作り、依存順の実行・データのテスト・ドキュメント化をまとめて行うデータ変換ツールです。data build tool の略で、ELT の T(変換)を担います。
Q. dbt は無料で使えますか? A. CLI(dbt Core v1・dbt v2・dbt-oss)は無料です。サービスの dbt platform は Developer プランが無料で、開発者1人・月3,000モデルビルド・1プロジェクトまでです(2026年10月29日時点の公式の料金ページ)。
Q. dbt Core と dbt Cloud の違いは? A. dbt Core は自分のパソコンや CI で動かす CLI です。dbt Cloud は今は dbt platform という名前のサービスで、ブラウザの IDE・スケジュール実行・ドキュメントの公開・権限管理が付きます。定期実行や CI の仕組みを自分たちで持つか、任せるかで選びます。
Q. dbt v2 と v1 のどちらを使えばよいですか? A. 新しく始めるなら、公式の案内は v2 です。この記事の環境では、解析が v1 の3.3秒から0.44秒に縮みました。使う DWH が v2 の対応一覧に無い場合や、社内のネットワークで v2 が取りに行く接続先を許可できない場合は v1 を使います。
Q. dbt を使うのに Python の知識は要りますか? A. モデルは SQL と YAML で書けます。インストールに pip を使う程度の知識があれば始められます。Jinja の書き方は少し覚える必要があり、Python のモデルを書くときだけ Python を使います。
Q. dbt でデータの取り込み(Extract・Load)はできますか? A. できません。dbt は DWH に入っているデータを変換するツールです。取り込みは Fivetran・Airbyte・trocco などのツールや自作のバッチで行い、dbt からは source として参照します。小さなコード表だけは seeds で CSV から入れられます。
Q. dbt run と dbt build の違いは? A. dbt run はモデルを作るだけです。dbt build は seed・モデル・snapshot・テストを依存順にまとめて実行し、テストが落ちたら下流を作りません。CI や本番の定期実行には dbt build が向いています。
Q. ref と source はどう使い分けますか? A. dbt が作るモデルは ref、dbt の外から入ってきた元のテーブルは source で指します。どちらも直接テーブル名を書く代わりに使い、dbt が依存関係とリネージを知るための目印になります。
Q. dbt で定期実行はできますか? A. dbt Core 単体には定期実行の機能はありません。cron・Airflow・GitHub Actions などから dbt build を呼ぶか、dbt platform のジョブで実行します。cron で動かすときは、ロックや同時実行に気を付けます。
Q. 手元で dbt を試すのにいちばん手軽な方法は?
A. DuckDB を使う方法です。pip install dbt-core dbt-duckdb で入り、サーバーもアカウントも要りません。profiles.yml にファイルの場所を書くだけで、この記事の手順がそのまま動きます。
Q. dbt を入れるのに向かないのはどんな場合ですか? A. 集計の表が数本しかない、データがまだ DWH に入っていない、秒単位で結果が要るリアルタイムの処理、といった場合です。dbt は起動に数秒かかるバッチの道具で、小さな処理なら SQL のファイルのほうが軽く済みます。
まとめ
この節では、記事の要点を5つに絞って確かめられます。
- dbt は ELT の T だけを担う:SELECT 文を書けば、dbt が CREATE で包んで DWH の中で実行する。取り込みはしない。
- ref と source で書けば、順番とリネージは dbt が持つ:dbt run は依存順に並べて実行し、docs generate で依存の図ができる。
- テストは「失敗した行を探す SELECT」:unique・not_null・relationships・accepted_values を YAML で宣言し、dbt build なら落ちた下流を止める。
- incremental は「全件だと時間か料金が困る」ときに使う:600万行の DuckDB では読む行が4.1%になったが、時間は縮まなかった。
- 2026年10月29日時点の新規インストールは v2 が既定:解析は0.44秒と速く、YAML の誤りを止めて教える。v1 も引き続きサポートされる。
参考・出典
確認日はいずれも 2026年10月29日です。
- dbt Developer Hub「What is dbt?」:https://docs.getdbt.com/docs/introduction
- dbt Developer Hub「Install dbt」:https://docs.getdbt.com/docs/local/install-dbt
- dbt Developer Hub「Upgrading to dbt v2」:https://docs.getdbt.com/docs/dbt-versions/dbt-upgrade/upgrading-to-v2
- dbt Developer Hub「Anonymous usage stats」:https://docs.getdbt.com/reference/global-configs/usage-stats
- dbt Developer Hub「Configure incremental models」:https://docs.getdbt.com/docs/build/incremental-models
- dbt Developer Hub「Quickstart for dbt Core using DuckDB」:https://docs.getdbt.com/guides/duckdb
- dbt Labs「dbt pricing」:https://www.getdbt.com/pricing
- dbt Labs「dbt とは」(日本語の製品ページ):https://www.getdbt.com/jp/product/what-is-dbt
- dbt-duckdb(GitHub):https://github.com/duckdb/dbt-duckdb
- DuckDB「Concurrency」:https://duckdb.org/docs/current/connect/concurrency
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「dbtとは?使い方をDuckDBで実行|test・incremental・v2の違い」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。