Schema Evolution
Schema Evolution を使用すると、TiDB Cloud Lake は COPY INTO の実行時に、ソースファイルには存在するがターゲットテーブルには存在しないカラムを自動的に追加できます。現在は Parquet と NDJSON ファイルをサポートしています。
仕組み
有効にすると、TiDB Cloud Lake はロード前にソースファイルのスキーマを推論し、新しいカラムをテーブルの末尾に追加します。新しいカラムは NULL 許容となり、欠損値は NULL で埋められます。
ワークフローはファイル形式によって少し異なります。
- Parquet: テーブルオプションを有効にすると、
COPY INTOは Parquet ファイルのスキーマから新しいカラムを直接推論します。 - NDJSON: テーブルオプションを有効にすると、
COPY INTOはスキーマ推論のためにAUTOのサンプリング値を使用します。必要に応じてSCHEMA_EVOLUTION = (...)を追加し、ファイルおよびレコードのサンプリング上限を上書きできます。
Schema Evolution を有効にする
テーブルオプション ENABLE_SCHEMA_EVOLUTION を true に設定します。
-- On an existing table
ALTER TABLE my_table SET OPTIONS(ENABLE_SCHEMA_EVOLUTION = true);
-- Or when creating a new table
CREATE TABLE my_table(id INT) ENABLE_SCHEMA_EVOLUTION = true;
Schema Evolution を無効にするには、false に戻します。
ALTER TABLE my_table SET OPTIONS(ENABLE_SCHEMA_EVOLUTION = false);
権限
COPY INTO <table> が stage または外部ロケーションからファイルをロード (load) し、Schema Evolution の推論を実行する場合、ロードを実行するロールにはターゲットテーブルに対する INSERT 権限と ALTER 権限の両方が必要です。ALTER が必要なのは、TiDB Cloud Lake がロード前に新しいカラムを追加する可能性があるためです。
クエリベースの COPY は影響を受けません。たとえば、COPY INTO <table> FROM (SELECT ... FROM @stage) では、既存の権限要件がそのまま適用されます。
Parquet の例
次の例では、異なるスキーマを持つ Parquet ファイルをロードし、不足しているカラムを自動的に追加します。
ステップ 1: テーブルと stage を作成する
CREATE OR REPLACE TABLE invoices(order_id INT);
CREATE OR REPLACE STAGE my_stage;
ステップ 2: 異なるスキーマを持つ Parquet ファイルを生成する
-- File with columns: order_id, amount, currency
COPY INTO @my_stage FROM (
SELECT 1 AS order_id, 100.50::DOUBLE AS amount, 'USD' AS currency
UNION ALL
SELECT 2, 250.50::DOUBLE, 'EUR'
) FILE_FORMAT = (TYPE = parquet);
-- File with columns: order_id, amount (no currency)
COPY INTO @my_stage FROM (
SELECT 3 AS order_id, 75.50::DOUBLE AS amount
) FILE_FORMAT = (TYPE = parquet);
ステップ 3: Schema Evolution を有効にしてロードする
ALTER TABLE invoices SET OPTIONS(ENABLE_SCHEMA_EVOLUTION = true);
COPY INTO invoices
FROM @my_stage/
FILE_FORMAT = (TYPE = parquet MISSING_FIELD_AS = FIELD_DEFAULT);
ステップ 4: 結果を確認する
これでテーブルには 3 つのカラムがあります。amount と currency は自動的に追加されました。
DESC invoices;
┌─────────────────────────────────────────────────────────────┐
│ Field │ Type │ Null │ Default │ Extra │
├──────────┼────────────────┼────────┼─────────┼──────────────┤
│ order_id │ INT │ YES │ NULL │ │
│ amount │ DOUBLE │ YES │ NULL │ │
│ currency │ VARCHAR │ YES │ NULL │ │
└─────────────────────────────────────────────────────────────┘
SELECT * FROM invoices ORDER BY order_id;
┌──────────────────────────────────────────────────┐
│ order_id │ amount │ currency │
├──────────┼──────────┼─────────────────────────────┤
│ 1 │ 100.50 │ USD │
│ 2 │ 250.50 │ EUR │
│ 3 │ 75.50 │ NULL │
└──────────────────────────────────────────────────┘
3 行目が currency = NULL になっているのは、ソースファイルにそのカラムが含まれていなかったためです。
NDJSON の例
TiDB Cloud Lake は TYPE = ndjson を使用して NDJSON ファイルをロードします。NDJSON ファイルには Parquet ファイルのような埋め込みのカラムスキーマがないため、TiDB Cloud Lake はファイル内容をサンプリングし、ターゲットテーブルに存在しないフィールドを推論して、NULL 許容カラムとして追加します。
ステップ 1: テーブルと stage を作成する
CREATE OR REPLACE TABLE events(id INT);
CREATE OR REPLACE STAGE events_stage;
ステップ 2: 異なるフィールドを持つ NDJSON ファイルを生成する
-- File with fields: id, city, score
COPY INTO @events_stage FROM (
SELECT 1 AS id, 'SF' AS city, 9 AS score
UNION ALL
SELECT 2, 'NYC', 8
) FILE_FORMAT = (TYPE = ndjson);
-- File with fields: id, score (no city)
COPY INTO @events_stage FROM (
SELECT 3 AS id, 7 AS score
) FILE_FORMAT = (TYPE = ndjson);
ステップ 3: Schema Evolution を有効にしてロードする
ALTER TABLE events SET OPTIONS(ENABLE_SCHEMA_EVOLUTION = true);
COPY INTO events
FROM @events_stage/
FILE_FORMAT = (TYPE = ndjson MISSING_FIELD_AS = FIELD_DEFAULT)
SCHEMA_EVOLUTION = (
SAMPLE_FILES = AUTO,
SAMPLE_RECORDS_PER_FILE = AUTO,
SAMPLE_TOTAL_RECORDS = AUTO
);
3 つの SCHEMA_EVOLUTION サンプリングオプションは、AUTO または正の整数を受け付けます。
SCHEMA_EVOLUTION を省略した場合、TiDB Cloud Lake は 3 つすべてのサンプリングオプションに AUTO を使用します。現在の AUTO の動作では、最大 64 ファイル、各ファイルあたり 1,000 レコード、合計 10,000 レコードまでサンプリングします。これらの内部デフォルト値は、将来のバージョンで変更される可能性があります。ロードがサンプリング戦略に敏感な場合は、SAMPLE_FILES、SAMPLE_RECORDS_PER_FILE、SAMPLE_TOTAL_RECORDS を明示的に設定してください。
NDJSON 推論ルール
NDJSON に対して Schema Evolution を実行すると、TiDB Cloud Lake は次のルールを使用して新しいカラムを推論します。
- スキーマは、サンプリングされた NDJSON レコードからのみ推論されます。サンプルに含まれないフィールドは、事前にターゲットテーブルへ追加されません。
- 各行は JSON オブジェクトである必要があります。TiDB Cloud Lake は、トップレベルのオブジェクトのフィールド名を候補カラム名として使用します。
- ターゲットテーブルにすでに存在するカラムは再度追加されません。ターゲットテーブルに存在しないフィールドのみが追加されます。
- 新しいフィールドの型は、整数、浮動小数点数、文字列、ブール値など、サンプリングされた JSON 値から推論されます。
- Schema Evolution は浅い NDJSON 推論を使用します。トップレベルのフィールド値がオブジェクトまたは配列の場合、再帰的に展開するのではなく、
VARIANTカラムとして追加されます。 NULLのサンプルは、そのフィールドが NULL 許容であることを示すだけです。後続の非 null 値がVARCHARやVARIANTになることを強制しません。- ファイルまたはレコード間で同名のフィールドはマージされます。整数と浮動小数点数の競合は
DOUBLEになり、その他のスカラー型の競合はVARCHARになり、オブジェクト、配列、またはVARIANTを含む競合はVARIANTになります。 - ロード時に、サンプリング中に推論されなかった追加フィールドが見つかった場合、ロードは失敗し、それらのフィールド名が報告されます。
SAMPLE_FILES、SAMPLE_RECORDS_PER_FILE、またはSAMPLE_TOTAL_RECORDSを増やして再試行してください。
たとえば、次の NDJSON レコードからは、name、age、active、score、profile、tags の 6 つの新しいカラムが推論されます。
{"id":1,"name":"Alice","age":30,"active":true,"score":1,"profile":{"city":"SF"},"tags":["new"]}
{"id":2,"name":"Bob","age":null,"active":false,"score":1.5,"profile":{"city":"NYC"},"tags":["vip"]}
ターゲットテーブルに id INT しかない場合、TiDB Cloud Lake は次を追加します。
name VARCHAR NULL
age BIGINT NULL
active BOOLEAN NULL
score DOUBLE NULL
profile VARIANT NULL
tags VARIANT NULL
2 行目の age = NULL は、1 行目から推論された BIGINT 型を変更しません。score には整数と浮動小数点数の両方が含まれるため、DOUBLE になります。profile と tags はそれぞれオブジェクトと配列であるため、Schema Evolution はそれらを VARIANT カラムとして追加します。
ステップ 4: 結果を確認する
テーブルには現在 3 つのカラムがあります。city と score は自動的に追加されました。
DESC events;
┌─────────────────────────────────────────────────────────┐
│ Field │ Type │ Null │ Default │ Extra │
├───────┼──────────────┼────────┼─────────┼──────────────┤
│ id │ INT │ YES │ NULL │ │
│ city │ VARCHAR │ YES │ NULL │ │
│ score │ BIGINT │ YES │ NULL │ │
└─────────────────────────────────────────────────────────┘
SELECT * FROM events ORDER BY id;
┌────────────────────────────┐
│ id │ city │ score │
├────┼──────┼────────────────┤
│ 1 │ SF │ 9 │
│ 2 │ NYC │ 8 │
│ 3 │ NULL │ 7 │
└────────────────────────────┘
サンプルに、後でデータ内に現れるフィールドが含まれていない場合、ロードは失敗し、追加フィールド名を返します。SAMPLE_FILES、SAMPLE_RECORDS_PER_FILE、または SAMPLE_TOTAL_RECORDS を増やして再試行してください。
カラム一致モード
デフォルトでは、カラム名は大文字と小文字を区別せずに一致します。大文字と小文字を区別して一致させるには、COLUMN_MATCH_MODE を使用します。
COPY INTO invoices
FROM @my_stage/
FILE_FORMAT = (TYPE = parquet MISSING_FIELD_AS = FIELD_DEFAULT)
COLUMN_MATCH_MODE = CASE_SENSITIVE;
制限事項
- 現在サポートしているのは Parquet ファイルと NDJSON ファイルです。
- 新しいカラムはテーブルの末尾に追加され、常に NULL 許容です。
- 同じカラム名が複数のファイルに 異なるデータ型 で現れる場合、ロードは失敗します。
INTからBIGINTへのような自動的な型昇格はありません。- Schema Evolution では、カラムの削除および名前変更はサポートされていません。
- NDJSON はスキーマ推論にサンプリングを使用します。サンプリングですべてのフィールドをカバーできない場合は、
SCHEMA_EVOLUTIONのサンプリングオプションを増やしてください。