跳到主要内容

Schema Evolution

Schema Evolution 允许 Databend 在 COPY INTO 加载数据时,自动将源文件中存在但目标表中缺失的列添加到目标表。目前支持 ParquetNDJSON

工作原理

启用后,Databend 会在加载前推断源文件 schema,并将新列追加到表末尾。新列为可空列,缺失值会填充为 NULL

不同文件格式的用法略有差异:

  • Parquet:启用表选项后,COPY INTO 会直接从 Parquet 文件 schema 推断新列。
  • NDJSON:启用表选项后,COPY INTO 会使用 AUTO 采样值进行 schema 推断。可以选择设置 SCHEMA_EVOLUTION = (...) 来覆盖文件和记录采样限制。

启用 Schema Evolution

将表选项 ENABLE_SCHEMA_EVOLUTION 设置为 true

-- 对已有表启用
ALTER TABLE my_table SET OPTIONS(ENABLE_SCHEMA_EVOLUTION = true);

-- 或在建表时启用
CREATE TABLE my_table(id INT) ENABLE_SCHEMA_EVOLUTION = true;

如需禁用,设回 false

ALTER TABLE my_table SET OPTIONS(ENABLE_SCHEMA_EVOLUTION = false);

权限要求

COPY INTO <table> 从 Stage 或外部位置加载文件,并触发 Schema Evolution 推断时,执行加载的角色需要拥有目标表的 INSERTALTER 权限。ALTER 权限用于允许 Databend 在加载前自动追加新列。

基于查询的 COPY 不受此限制,例如 COPY INTO <table> FROM (SELECT ... FROM @stage),仍按原有权限要求检查。

Parquet 示例

以下示例展示如何从不同 schema 的 Parquet 文件加载数据,并自动添加缺失列。

步骤 1:创建表和 Stage

CREATE OR REPLACE TABLE invoices(order_id INT);
CREATE OR REPLACE STAGE my_stage;

步骤 2:生成不同 Schema 的 Parquet 文件

-- 文件包含列: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);

-- 文件包含列:order_id, amount(无 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:验证结果

表现在有三列,amountcurrency 被自动添加:

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 示例

Databend 使用 TYPE = ndjson 加载 NDJSON 文件。NDJSON 文件没有像 Parquet 那样的内嵌列式 schema,Databend 会先对文件内容进行采样,推断目标表中缺失的字段,再追加为可空列。

步骤 1:创建表和 Stage

CREATE OR REPLACE TABLE events(id INT);
CREATE OR REPLACE STAGE events_stage;

步骤 2:生成不同字段的 NDJSON 文件

-- 文件包含字段: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);

-- 文件包含字段:id, score(无 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
);

SCHEMA_EVOLUTION 的三个采样选项都可以设置为 AUTO 或正整数:

选项说明
SAMPLE_FILES采样的文件数量。
SAMPLE_RECORDS_PER_FILE每个采样文件中最多采样的记录数。
SAMPLE_TOTAL_RECORDS所有采样文件中最多采样的记录总数。

如果省略 SCHEMA_EVOLUTION,Databend 会对三个采样选项都使用 AUTO。当前 AUTO 行为最多采样 64 个文件、每个文件 1,000 条记录、总计 10,000 条记录。这些内部默认值未来版本可能会调整。如果加载结果对采样策略敏感,建议显式设置 SAMPLE_FILESSAMPLE_RECORDS_PER_FILESAMPLE_TOTAL_RECORDS

NDJSON 推断规则

对 NDJSON 执行 Schema Evolution 时,Databend 会按以下规则推断新列:

  • 只从采样到的 NDJSON 记录中推断 schema;未被采样覆盖的字段不会提前加入目标表。
  • 每行必须是一个 JSON 对象。Databend 会将对象的顶层字段名作为候选列名。
  • 目标表中已存在的列不会重复添加;只追加目标表中缺失的新字段。
  • 新字段类型由采样记录中的 JSON 值推断而来,例如整数、浮点数、字符串和布尔值会推断为对应的数据类型。
  • Schema Evolution 对 NDJSON 使用浅层推断:顶层字段值如果是对象或数组,会作为 VARIANT 列追加,不会递归展开为嵌套列。
  • NULL 样本只表示该字段可为空,不会把后续非空类型强制改成 VARCHARVARIANT
  • 多个文件或多条记录中的同名字段会合并类型:整数和浮点数冲突时合并为 DOUBLE;其他标量类型冲突时合并为 VARCHAR;任何涉及对象、数组或 VARIANT 的冲突都会合并为 VARIANT
  • 如果实际加载时遇到采样阶段没有推断出的额外字段,加载会失败并返回这些字段名。此时需要调大 SAMPLE_FILESSAMPLE_RECORDS_PER_FILESAMPLE_TOTAL_RECORDS
备注

INFER_SCHEMA 表函数对 NDJSON 默认不限制嵌套深度;这里描述的是 COPY INTO Schema Evolution 的浅层推断规则。

例如,下面的 NDJSON 记录会推断出 nameageactivescoreprofiletags 六个新列:

{"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,加载后 Databend 会追加:

name VARCHAR NULL
age BIGINT NULL
active BOOLEAN NULL
score DOUBLE NULL
profile VARIANT NULL
tags VARIANT NULL

第二行的 ageNULL,不会影响 age 根据第一行推断为 BIGINTscore 同时出现整数和浮点数,因此合并为 DOUBLEprofiletags 是对象和数组,因此在 Schema Evolution 中作为 VARIANT 列追加。

步骤 4:验证结果

表现在有三列,cityscore 被自动添加:

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_FILESSAMPLE_RECORDS_PER_FILESAMPLE_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;

限制

  • 目前支持 ParquetNDJSON 文件。
  • 新列追加到表末尾,始终为可空类型。
  • 如果同名列在多个文件中具有不同数据类型,加载将失败。
  • 不会自动提升类型(如 INTBIGINT)。
  • 不支持通过 Schema Evolution 删除或重命名列。
  • NDJSON 依赖采样推断 schema;如果采样未覆盖所有字段,需要调大 SCHEMA_EVOLUTION 采样参数。
欢迎体验 Databend Cloud

基于 Rust + 对象存储构建的新一代多模态数仓,一个平台即可进行 BI、向量、全文检索及地理空间分析。

支持标准 SQL,自动弹性伸缩,助您快速构建现代化数据平台。

注册即领 ¥200 代金券。

注册体验