Data Engineer Associate — 第 2 章
この章で学ぶこと
この章に出てくる用語
「テーブルを DROP したらクラウドストレージのファイルまで消えた」「別のチームが DROP したのに、ファイルだけ残ってストレージ課金が止まらない」。どちらも実務で普通に起きます。そしてどちらが起きるかは、そのテーブルがマネージドテーブルなのか外部テーブルなのかという一点で決まります。ここを取り違えたまま先に進むと、後の章で組み立てるジョブも監視も、消えたデータの上に載ることになります。
Unity Catalog のテーブルは 3 種類です。どれも `catalog.schema.table` の 3 階層名で呼ぶところは同じで、違うのはデータの実体を誰が持つかです。
| 比較軸 | マネージドテーブル | 外部テーブル | フォリンテーブル |
|---|---|---|---|
| データ実体の管理者 | Databricks (Unity Catalog) | 自社のバケット | 接続先のデータベース |
| DDL での見分け方 | `LOCATION` を書かない | `LOCATION` を書く | 接続経由で自動生成 |
| 使える形式 | Delta Lake と Apache Iceberg の 2 つだけ | DELTA / CSV / JSON / AVRO / PARQUET / ORC / TEXT | 接続先に依存 |
| `DROP TABLE` の結果 | 復旧期間の後にファイルも削除 | メタデータのみ削除 | 参照定義のみ削除 |
| 自動最適化 | 予測的最適化と自動メンテナンスの対象 | 限定的 | 対象外 |
形式の行が効きます。マネージドテーブルは Delta Lake と Apache Iceberg しか持てません。他システムが出力した CSV をファイル形式のまま Unity Catalog で管理したい、という要件なら答えは外部テーブル一択です。Iceberg にしたいときだけ `USING iceberg` を明示し、省略すれば Delta Lake になります。
自動最適化の行は、Databricks がマネージドを既定として推す理由そのものです。予測的最適化 (predictive optimization) は、`OPTIMIZE` によるファイル圧縮、`VACUUM` による不要ファイル削除、`ANALYZE` による統計収集を人手なしで走らせる機能で、マネージドテーブルが主な対象です。外部テーブルでは Unity Catalog がアクセスを統制するだけで、データの寿命や配置までは面倒を見ません。フォリンテーブルは Lakehouse Federation で PostgreSQL や Snowflake を参照する読み取り専用の窓なので、最適化の対象外です。
`LOCATION` を書かずに `CREATE TABLE` すればマネージドテーブルです。ではファイルはどこへ行くのか。スキーマ、カタログ、メタストアの順に探し、最初に見つかったマネージドロケーションが使われます。下位のレベルほど優先されるので、「常にメタストアのロケーション」という覚え方は誤りです。
-- LOCATION を書かない → マネージドテーブル (既定は Delta Lake)
CREATE TABLE main.sales.orders (order_id BIGINT, amount DECIMAL(18,2));
-- LOCATION を書く → 外部テーブル
CREATE TABLE main.sales.orders_ext (order_id BIGINT, amount DECIMAL(18,2))
LOCATION 's3://acme-lake/sales/orders/';マネージドテーブルを `DROP TABLE` しても、ファイルはその場では消えません。既定で 7 日間は `UNDROP TABLE` で戻せます。復旧期間はカタログまたはスキーマの単位で変えられ、`ALTER CATALOG my_catalog RETAIN DROPPED TO 30 DAYS;` のように 7 日から 30 日で指定するか、0 時間にして復旧を無効化します。期間が切れると Databricks は 48 時間以内にクラウドストレージのファイルを削除します。外部テーブルの `DROP TABLE` はメタデータだけを消すので、ファイルは自分で消す必要があります。なお外部テーブルの実体を Databricks 以外のクライアントが書き換えても Unity Catalog には自動同期されず、カタログの情報が実体とずれます。
目の前のテーブルがどちらの種類で、実体がどこにあるのかは `DESCRIBE DETAIL` で分かります。返るのは現在のスナップショットのメタデータ 1 行だけで、`format`、`id`、`name`、`location`、`createdAt`、`lastModified`、`partitionColumns`、`numFiles`、`sizeInBytes`、`properties` などの列が含まれます。`location` と `format` を見れば種別と置き場が確定し、`numFiles` と `sizeInBytes` は小ファイルの調査や容量監視にそのまま使えます。
似た名前のコマンドと混ぜないでください。コミットの履歴一覧は `DESCRIBE HISTORY`、列名と型の定義は `DESCRIBE TABLE`、クエリの実行プランは `EXPLAIN` です。Python からは `DeltaTable.forPath(spark, path).detail()` が `DESCRIBE DETAIL` と同じ 1 行を DataFrame で返します。
PDF、画像、機械学習のモデルファイル、取引先から届いた固定長テキスト。表の形をしていないのでテーブルにできませんが、放置すると誰が触ったのか分からなくなります。Unity Catalog の Volumes がこの穴を埋めます。`catalog.schema.volume` の 3 階層名を持ち、ファイルは `/Volumes/<catalog>/<schema>/<volume>/…` というパスで読み書きします。権限はテーブルの `SELECT` ではなく `READ VOLUME` と `WRITE VOLUME`、作成にはスキーマの `CREATE VOLUME` です。マネージドボリュームと外部ボリュームの 2 種類があり、Volumes の中のファイルをそのままテーブルとして登録することはできません。使い分けの詳細は既存記事「Unity Catalog Volumes」に譲ります。
古い資料には `dbfs:/mnt/raw` にマウントしてから読み書きする手順が出てきます。DBFS root はワークスペース作成時に用意される領域で、DBFS マウントは非推奨です。理由は機能ではなくガバナンスにあります。DBFS は Unity Catalog の権限、監査ログ、リネージ (どのデータからどのテーブルが作られたかの追跡) のどれも効きません。新しく作るものは Volumes か外部ロケーションを使います。両者の比較そのものは既存記事「DBFS と Volumes の違い」で扱っています。
確認 — 穴あけ 3 問
0 / 3
空欄を押すと選択肢が出ます。間違えても減点はありません。
マネージドテーブルの保存先はスキーマ・カタログ・メタストアの順に探され、 のレベルが優先される。
CSV をファイル形式のまま Unity Catalog で管理したいときは として登録する。
テーブルの location・numFiles・sizeInBytes を 1 行で返すのは である。
「AWS のコンソールからはそのバケットを開けるのに、Databricks から読むと 403 で弾かれる」。クラウドストレージにつなぐ作業でいちばん多い詰まり方です。原因はたいてい単純で、権限のチェックが 2 か所にあり、その片方しか通していないからです。かといってノートブックにアクセスキーを直書きすれば、キーは実行履歴に残り、そのノートブックを開ける人全員に同じ権限が渡ります。Unity Catalog は認証情報を 1 か所に閉じ込め、パスと組にして配ることでこれを解きます。
CREATE EXTERNAL LOCATION IF NOT EXISTS sales_raw
URL 's3://acme-lake/sales/'
WITH (STORAGE CREDENTIAL acme_cred)
COMMENT '売上生データ';関係の向きを間違えないでください。1 つのストレージ資格情報を複数の外部ロケーションが参照できます。バケット全体を許可する資格情報を 1 つ作り、その下のサブパスごとに外部ロケーションを切って権限を分ける、という設計が普通です。「外部ロケーションごとに資格情報が要る」は誤りで、不要な作業を生みます。そしてもう 1 つ、外部テーブルや外部ボリュームを作れるパスは、必ずどこかの外部ロケーションの配下でなければなりません。登録していないパスを `LOCATION` に書けば作成は失敗します。「バケットは見えているのにテーブルが作れない」の大半はこれです。
外部ロケーションに付けられる権限はテーブルとは別系統で、`READ FILES`、`WRITE FILES`、`CREATE EXTERNAL TABLE`、`CREATE EXTERNAL VOLUME`、`BROWSE`、`MANAGE` などがあります。名前の変更や資格情報の差し替えといった管理操作には `MANAGE` が要ります。
GRANT READ FILES, CREATE EXTERNAL TABLE
ON EXTERNAL LOCATION sales_raw
TO `data-engineers`;外部テーブルを 1 つ作るのに必要な権限は 4 つです。データを置く外部ロケーションへの `CREATE EXTERNAL TABLE`、親カタログの `USE CATALOG`、親スキーマの `USE SCHEMA` と `CREATE TABLE`。スキーマの `CREATE TABLE` だけでは作れません。外部ボリュームなら、外部ロケーションの `CREATE EXTERNAL VOLUME` と親スキーマの `CREATE VOLUME` の組になります。
読み取り側も形は同じです。あるテーブルを `SELECT` するには、テーブルへの `SELECT`、親カタログの `USE CATALOG`、親スキーマの `USE SCHEMA` の 3 つが揃っていなければなりません。`SELECT` だけ渡して「見えない」と言われる相談はほぼこれです。中身は見せずに、どんなテーブルがあるかだけ探させたいときは `BROWSE` を使います。権限を渡す相手にも制約があり、ワークスペース内で作ったワークスペースローカルグループには Unity Catalog の権限を付与できません。アカウントレベルで管理されるアカウントグループを使います。
ここが本節の中心です。外部ロケーションに `READ FILES` を GRANT しても、IAM ロールにそのバケットの `s3:GetObject` が付いていなければ読めません。逆に IAM ロールがどれだけ強くても、Unity Catalog 側で GRANT されていなければ Databricks からは読めません。2 つの層は AND で効きます。
| 層 | 設定する場所 | 設定するもの | 抜けたときの症状 |
|---|---|---|---|
| クラウド IAM | AWS / Azure / GCP のコンソール | IAM ロール、バケットポリシー、信頼関係 | ストレージ資格情報の検証が失敗する |
| Unity Catalog | Databricks の GRANT | READ FILES、CREATE EXTERNAL TABLE など | 資格情報は正常なのに操作が拒否される |
403 が出たら、まず `SHOW GRANTS ON EXTERNAL LOCATION sales_raw;` で Unity Catalog 側を確認し、次にクラウド側のロールとバケットポリシーを見る、という順に切り分けます。`spark.read.parquet("s3://acme-lake/sales/…")` のようなパスの直読みもこの二層を通ります。裏を返せば、パス直読みを禁じたいなら `READ FILES` を配らなければよい、ということです。
「昔 S3 に自前で作った外部テーブルを、Databricks に運用ごと任せたい」。以前は CTAS (`CREATE TABLE AS SELECT` でクエリ結果から作り直すこと) しか手がなく、履歴は失われました。今は 1 行で移せます。
ALTER TABLE main.sales.orders_ext SET MANAGED;
ALTER TABLE main.sales.orders_ext UNSET MANAGED; -- 14 日以内なら戻せる確認 — 穴あけ 3 問
0 / 3
空欄を押すと選択肢が出ます。間違えても減点はありません。
外部テーブルの作成には外部ロケーションの CREATE EXTERNAL TABLE、親カタログの USE CATALOG、親スキーマの USE SCHEMA と が要る。
1 つのストレージ資格情報は の外部ロケーションから参照できる。
外部テーブルを SET MANAGED で変換した後、 日以内なら UNSET MANAGED で元に戻せる。
1 億行のテーブルに追記している途中でクラスタが落ちたら、書きかけの Parquet は他の人のクエリに混ざって読まれるのでしょうか。オブジェクトストレージ自体にトランザクションという概念はないので、素の Parquet ディレクトリならまさにそれが起きます。Delta Lake がこれを防ぐ仕組みが、テーブルディレクトリ直下の `_delta_log/` です。ACID・Time Travel・Z-Order という用語の解説は既存記事「Delta Lake とは」に置き、ここでは中身の構造だけを扱います。
s3://acme-lake/sales/orders/
├── part-00000-....snappy.parquet
├── part-00001-....snappy.parquet
└── _delta_log/
├── 00000000000000000000.json
├── 00000000000000000001.json
├── …
├── 00000000000000000010.checkpoint.parquet
└── _last_checkpointコミットファイルの名前はバージョン番号を 20 桁にゼロ埋めした JSON です。バージョンは 0 から始まり、書き込みが 1 回成功するたびに 1 増えます。`VERSION AS OF 5` と書いたときの 5 がこの番号です。中身は 1 行 1 アクションで、`metaData` (スキーマやパーティション定義)、`add` (ファイルの追加)、`remove` (ファイルの除外)、`protocol`、`txn`、`commitInfo` (操作種別や実行者)、`cdc`、`domainMetadata`、`sidecar` が並びます。`protocol` のプロトコルバージョンは、読み書きに必要な機能水準を表す番号で、新しい機能を有効にすると上がり、古いクライアントからは読めなくなります。
ここで押さえるべきは、Delta が更新を Parquet の書き換えではなく `remove` と `add` の組で表現する点です。古いファイルは remove されても物理的には残り続けます。これが次の節のタイムトラベルの正体です。
`add` アクションには `path`、`partitionValues`、`size`、`modificationTime`、`dataChange`、`stats`、`deletionVector` が入ります。この `stats` が肝で、列ごとの最小値・最大値・NULL 件数が記録されており、クエリは読まなくてよいファイルを丸ごと飛ばせます。統計を取る列数は `delta.dataSkippingNumIndexedCols` で決まり、既定は先頭 32 列です。絞り込みに使う列を 33 列目以降に置いたテーブルが遅いのは、これが理由です。この統計の効きを最大化するようデータの並びをそろえるのが `OPTIMIZE … ZORDER BY` と Liquid Clustering で、Liquid はキーが最大 4 列、`PARTITIONED BY` とは併用できません。詳しくは後の最適化の章で扱います。
下流に「増えた行と消えた行だけ」を渡したい場面があります。`cdc` アクションを使う Change Data Feed がそれで、既定は無効、テーブルプロパティで有効にします。
ALTER TABLE main.sales.orders
SET TBLPROPERTIES (delta.enableChangeDataFeed = true);
SELECT * FROM table_changes('main.sales.orders', 5, 10);有効化した後の変更行には `_change_type`、`_commit_version`、`_commit_timestamp` の 3 列が付きます。`_change_type` の値は `insert`、`update_preimage`、`update_postimage`、`delete` の 4 つで、`UPDATE` は変更前と変更後の 2 行になります。ストリーミングで読むときは `option("readChangeFeed", "true")` に `startingVersion` か `startingTimestamp` を添えます。有効化する前のバージョンは取れません。
読み手はログを再生して「このバージョン時点のファイル一覧」を組み立てます。コミットが 5 万回あるテーブルで毎回 5 万個の JSON を読むのは無理なので、Delta はときどきその時点の状態をまるごと 1 つの Parquet にまとめたチェックポイントを書き出します。読み手は直近のチェックポイントまで飛び、その後の JSON だけを追加で読みます。どれが最新かは `_last_checkpoint` が指します。間隔はテーブルプロパティ `delta.checkpointInterval` で指定でき、Delta Lake では 10 コミットごとが基準として知られています。ただし Databricks の公式ドキュメントは生成頻度を数値で約束していません。
用語の衝突に注意してください。ここでいうチェックポイントは `_delta_log/` の中の Parquet で、テーブルの状態を復元するためのものです。ストリーミングで指定する `checkpointLocation` はまったくの別物で、こちらはクエリの進捗を保存するディレクトリです。中は `offsets`、`commits`、`state`、`metadata` のサブディレクトリに分かれています。試験ではどちらも同じ文脈に出るので、毎回どちらの話かを確かめてください。
2 つのジョブが同じテーブルへ同時に書いたらどうなるか。Delta はテーブルをロックしません。衝突はめったに起きないと楽観的に仮定して先に進み、コミットの瞬間だけ確かめる方式です。手順は 3 段階で、開始時点のバージョンを記録して対象ファイルを特定し、新しい Parquet を書き、最後に「自分が読んだ後に競合する変更が入っていないか」を検証して次の番号の JSON を書きます。同じ番号の JSON は 1 つしか作れないので、必ず片方だけが勝ちます。負けたらリトライする前提でジョブを書く必要があります。
分離レベルは 2 つあり、既定は `WriteSerializable` です。何も読まずに追記するだけのブラインド追記は他の操作と衝突しないので、取り込みジョブを追記専用に設計する意味があります。より厳密にしたいときだけ切り替えます。
ALTER TABLE main.sales.orders
SET TBLPROPERTIES ('delta.isolationLevel' = 'Serializable');| 例外 | 起きる状況 |
|---|---|
| `ConcurrentAppendException` | 自分が読んだパーティションに他の操作がファイルを追加した |
| `ConcurrentDeleteReadException` | 自分が読んだファイルを他の操作が削除・書き換えした |
| `ConcurrentDeleteDeleteException` | 同じファイルを 2 つの操作が削除しようとした |
| `MetadataChangedException` | 処理中にスキーマやテーブルプロパティが変わった |
| `ProtocolChangedException` | 処理中にプロトコルバージョンが上がった |
| `ConcurrentTransactionException` | 同じストリーミングクエリが二重に動いた |
衝突そのものを減らすのが行レベル同時実行です。同じファイルの中の別々の行を触っているだけなら、競合と判定しません。条件は Databricks Runtime 14.3 LTS 以上、Deletion Vectors が有効、テーブルがパーティション分割されていないの 3 つです。パーティションを切っていると効きません。
確認 — 穴あけ 3 問
0 / 3
空欄を押すと選択肢が出ます。間違えても減点はありません。
_delta_log 内のコミット JSON はバージョン番号を 桁でゼロ埋めしたファイル名になる。
統計値を集める列数は delta.dataSkippingNumIndexedCols で決まり、既定は先頭 列である。
Databricks の Delta テーブルの既定の分離レベルは である。
本番テーブルに `WHERE` を付け忘れた `UPDATE` を流してしまった。この瞬間に何ができるかは、そのテーブルの保持設定を事前にどう決めていたかで決まります。あとから設定しても手遅れです。前の節で見たとおり Delta は古いファイルを `remove` で外すだけなので、ファイルが残っている間は過去のバージョンを読み直せます。同じ仕組みは「本番と同じデータで検証したい」という平時の要求にも効きます。
`DESCRIBE HISTORY` は `_delta_log/` の `commitInfo` を読み、`version`、`timestamp`、`userName`、`operation`、`operationParameters`、`operationMetrics`、`isolationLevel`、`isBlindAppend` などを新しい順に返します。事故の直前がどのバージョンかはここで特定します。サイズやパスを 1 行で返す `DESCRIBE DETAIL` とは別のコマンドです。
DESCRIBE HISTORY main.sales.orders;
SELECT * FROM main.sales.orders VERSION AS OF 123;
SELECT * FROM main.sales.orders TIMESTAMP AS OF '2026-08-01T00:00:00';
SELECT * FROM main.sales.orders@v123;短縮記法の `@` は、バージョンなら `v` を付け、タイムスタンプなら `yyyyMMddHHmmssSSS` の形式で書きます。PySpark では `option("versionAsOf", 123)` と `option("timestampAsOf", "2026-08-01")` が同じ働きです。読むだけなのでテーブルは変わりません。本体を戻すのは `RESTORE` です。
RESTORE TABLE main.sales.orders TO VERSION AS OF 123;
RESTORE TABLE main.sales.orders TO TIMESTAMP AS OF '2026-08-01 00:00:00';`RESTORE` は履歴を消しません。過去の状態に戻すための新しいコミットを積む実装なので、戻したこと自体も履歴に残り、戻す前の状態へもう一度移動できます。「参照だけなら `VERSION AS OF`、テーブルを書き換えるなら `RESTORE`」で選び分けます。
過去バージョンを読むには、`_delta_log/` のコミット記録と、そのバージョンが参照する Parquet の両方が残っている必要があります。記録の寿命は `delta.logRetentionDuration` で既定 `interval 30 days`、ファイルの寿命は `delta.deletedFileRetentionDuration` で既定 `interval 1 week`、つまり 168 時間 (7 日) です。短いほうで決まるので、既定のまま実際に遡れるのは 7 日分です。30 日と覚えていると、事故のときに読めない過去を掴みに行きます。
不要になったファイルを実際に消すのが `VACUUM` で、構文は `VACUUM table_name { { FULL | LITE } | DRY RUN }` です。既定は `FULL`、`LITE` は Databricks Runtime 16.1 以上で使え、ディレクトリを走査せずトランザクションログだけを見ます。`DRY RUN` は削除予定のファイルを最大 1000 件返すだけで、消しません。
VACUUM main.sales.orders DRY RUN;
VACUUM main.sales.orders RETAIN 168 HOURS; -- 既定と同じ 7 日
VACUUM main.sales.orders RETAIN 12 HOURS; -- 安全チェックに阻まれて失敗する既定の 168 時間より短い `RETAIN` を指定すると、Databricks はエラーで止めます。長時間走っているジョブが参照中のファイルを消してしまう事故を防ぐ安全装置です。どうしても短くしたいときだけ `spark.databricks.delta.retentionDurationCheck.enabled` を `false` にして `RETAIN 1 HOURS` のように指定します。`RETAIN 0 HOURS` は過去バージョンを一掃するので本番では実行しません。`VACUUM` はアンダースコアで始まるディレクトリを飛ばすため、`_delta_log/` が消えることはありません。予測的最適化が有効なら `VACUUM` は自動で走ります。
CREATE TABLE main.dev.orders_deep DEEP CLONE main.sales.orders;
CREATE TABLE main.dev.orders_shallow SHALLOW CLONE main.sales.orders;
CREATE TABLE main.dev.orders_v5 SHALLOW CLONE main.sales.orders VERSION AS OF 5;`DEEP CLONE` はデータファイルまでコピーして独立したテーブルを作ります。`SHALLOW CLONE` はメタデータだけをコピーし、データファイルは元テーブルのものを参照し続けます。一瞬で終わりストレージも増えませんが、落とし穴があります。元テーブルで `VACUUM` を走らせると、クローンが参照していたファイルが消えて `FileNotFoundException` になります。検証環境を長く保つなら `DEEP CLONE` にするか、元テーブルの `delta.deletedFileRetentionDuration` を十分に長くします。Unity Catalog では既存のシャロークローンを `CREATE OR REPLACE` で上書きできず、`DROP TABLE` してから作り直します。なお行フィルタ (条件に合う行だけを見せる関数) や列マスク (値を伏せて返す関数) が設定されたテーブルでは、タイムトラベルもクローンも使えません。クローンの使い分けは既存記事「Delta Lake の CLONE」でも扱っています。
1 行だけ `DELETE` するために 1 GB の Parquet を丸ごと書き直すのは無駄です。Deletion Vectors は「このファイルのこの行はもう無い」という印を別ファイルに書き、Parquet 本体には触りません。
ALTER TABLE main.sales.orders
SET TBLPROPERTIES ('delta.enableDeletionVectors' = true);必要な Databricks Runtime は操作ごとに違います。読み取りは 12.2 LTS 以上、Photon ありなら `DELETE`・`UPDATE`・`MERGE` のすべてが 12.2 LTS 以上、Photon なしでは `DELETE` が 12.2 LTS 以上、`UPDATE` が 14.1 以上、`MERGE` が 14.3 LTS 以上です。印を付けただけの行を本当にファイルから消すには `REORG TABLE … APPLY (PURGE)` で書き直し、そのあと `VACUUM` で回収します。有効化するとプロトコルバージョンが上がる点にも注意してください。
確認 — 穴あけ 3 問
0 / 3
空欄を押すと選択肢が出ます。間違えても減点はありません。
VACUUM の既定の保持期間は 時間で、これより短い RETAIN は安全チェックで拒否される。
SHALLOW CLONE 後に元テーブルへ VACUUM を実行すると、クローン側の SELECT が で失敗しうる。
Deletion Vectors で印を付けた行を Parquet から実際に消すには を実行してから VACUUM する。
上流のシステムが仕様変更し、必須のはずの `order_id` に NULL が混ざるようになった。気付いたのは 3 日後、集計が合わないという問い合わせが来てからだった。パイプラインのコードで毎回チェックを書くこともできますが、そのテーブルへ書き込む経路が増えるたびに漏れが生まれます。Delta Lake はテーブル自身に条件を宣言でき、違反した書き込みをトランザクションごと失敗させます。書き手が誰であっても効く、最後の砦です。
ALTER TABLE people10m ALTER COLUMN ssn SET NOT NULL;
ALTER TABLE people10m ALTER COLUMN middleName DROP NOT NULL;
ALTER TABLE people10m
ADD CONSTRAINT dateWithinRange CHECK (birthDate > '1900-01-01');
ALTER TABLE people10m DROP CONSTRAINT dateWithinRange;後付けの `SET NOT NULL` は、既存の行に NULL があると失敗します。だから「先に既存の NULL を掃除してから制約を付ける」という順番になります。違反する書き込みが来ると、その行だけが落ちるのではなくトランザクション全体がエラーで失敗します。ここが宣言的パイプライン (Lakeflow Spark Declarative Pipelines、旧 Delta Live Tables) の Expectations との決定的な違いです。Expectations は違反行を落とす、警告だけ出す、パイプラインを止める、の 3 つから選べますが、`CHECK` 制約は止めることしかできません。違反行を別テーブルに退避したいなら、それは制約ではなく Expectations の仕事です。
付けすぎも失敗です。全カラムに `NOT NULL` を一律で付けると、NULL が正当な列まで弾いて毎晩取り込みが止まります。欠けてはいけないキー列や金額列に絞るのが定石です。なお主キー・外部キー・ユニークも宣言できますが、これらは情報目的のみで強制されません。重複した主キーを INSERT してもエラーになりません。設定済みの制約は `DESCRIBE TABLE EXTENDED` や `SHOW TBLPROPERTIES` で確認できます。
イベント時刻から日付列を作ってパーティションキーにしたい。取り込み側に毎回計算させると、書き手ごとに式がずれます。生成列は式をテーブル定義に埋め込み、書き込み時に Delta が値を計算します。
CREATE TABLE main.sales.events (
event_id BIGINT,
event_ts TIMESTAMP,
event_date DATE GENERATED ALWAYS AS (CAST(event_ts AS DATE))
)
PARTITIONED BY (event_date);構文は `GENERATED ALWAYS AS (式)` です。式に使えるのは決定的な SQL 関数だけで、四則演算、`CONCAT` や `SUBSTRING`、`CAST` や `YEAR` は使えますが、ユーザー定義関数、集約関数、ウィンドウ関数、複数行を返す関数は使えません。あえて値を渡す場合は `(<value> <=> <generation expression>) IS TRUE` を満たす必要があり、満たさなければ書き込みが失敗します。生成列でパーティションを切ると、基底列だけを条件に書いたクエリからも Delta が可能な範囲でパーティションフィルタを導出します。分析者に `event_date` を意識させずに枝刈りを効かせられる、というのが狙いです。
CONVERT TO DELTA parquet.`s3://acme-lake/legacy/orders`;
CONVERT TO DELTA parquet.`s3://acme-lake/legacy/orders` NO STATISTICS;構文は `CONVERT TO DELTA table_name [ NO STATISTICS ] [ PARTITIONED BY clause ]` です。この操作は Parquet ファイル自体を書き直しません。すべての Parquet のフッタを読んでスキーマを推論し、`_delta_log/` を作るだけなので、大規模テーブルでも短時間で終わります。既定では各ファイルの統計値も集めるため、変換直後からファイル単位の読み飛ばしが効きます。`NO STATISTICS` を付けると統計収集を省いて変換を早く終わらせられます。パス指定でパーティションがある場合は `PARTITIONED BY` が必須です。基盤の形式が Parquet なら Apache Iceberg のテーブルも変換でき、その場合はパス指定のみが対象です。`ALTER TABLE … SET TBLPROPERTIES ('format'='delta')` のような書き換えでは `_delta_log/` が作られず、変換にはなりません。
確認 — 穴あけ 3 問
0 / 3
空欄を押すと選択肢が出ます。間違えても減点はありません。
Databricks が実際に強制する Delta の制約は NOT NULL と の 2 種類である。
生成列は 構文で宣言し、決定的な SQL 関数だけを式に使える。
CONVERT TO DELTA に を付けると統計値の収集を省いて変換を早く終わらせられる。
この章のまとめ
この端末にだけ保存されます(登録不要)
← 前の章
Databricks とレイクハウスの全体像 — なぜ必要か、何がどこに属し、どの計算資源で動くか
次の章 →
計算資源を作り込み、コードを書く場所を決める — クラスタ設定とノートブック/ローカル IDE
この章の根拠
最終確認 2026-08-09 / 対応バージョン DEA 2026-05-04 改訂版
コース全体