Delta Lake 生成的列

重要

此功能目前以公共预览版提供。

Delta Lake 支持生成的列,这些列是一种特殊类型的列,其值基于用户指定的函数在 Delta 表中的其他列上自动生成。 当写入具有生成的列的表且未显式为其提供值时,Delta Lake 会自动计算这些值。 例如,可以从时间戳列自动生成日期列(用于按日期对表进行分区);对表进行的任何写入都只需为时间戳列指定数据。 但是,如果显式为这些表提供值,则这些值必须满足约束 (<value> <=> <generation expression>) IS TRUE,否则写入将失败并出现错误。

重要

使用生成的列创建的表的表写入器协议版本高于默认版本。 请参阅 Azure Databricks 如何管理 Delta Lake 功能兼容性?,了解表协议版本控制以及拥有更高版本的表协议版本意味着什么。

创建包含生成的列的表

下面的示例介绍如何使用生成的列创建表:

SQL

CREATE TABLE default.people10m (
  id INT,
  firstName STRING,
  middleName STRING,
  lastName STRING,
  gender STRING,
  birthDate TIMESTAMP,
  dateOfBirth DATE GENERATED ALWAYS AS (CAST(birthDate AS DATE)),
  ssn STRING,
  salary INT
)

Python

DeltaTable.create(spark) \
  .tableName("default.people10m") \
  .addColumn("id", "INT") \
  .addColumn("firstName", "STRING") \
  .addColumn("middleName", "STRING") \
  .addColumn("lastName", "STRING", comment = "surname") \
  .addColumn("gender", "STRING") \
  .addColumn("birthDate", "TIMESTAMP") \
  .addColumn("dateOfBirth", DateType(), generatedAlwaysAs="CAST(birthDate AS DATE)") \
  .addColumn("ssn", "STRING") \
  .addColumn("salary", "INT") \
  .execute()

Scala

DeltaTable.create(spark)
  .tableName("default.people10m")
  .addColumn("id", "INT")
  .addColumn("firstName", "STRING")
  .addColumn("middleName", "STRING")
  .addColumn(
    DeltaTable.columnBuilder("lastName")
      .dataType("STRING")
      .comment("surname")
      .build())
  .addColumn("lastName", "STRING", comment = "surname")
  .addColumn("gender", "STRING")
  .addColumn("birthDate", "TIMESTAMP")
  .addColumn(
    DeltaTable.columnBuilder("dateOfBirth")
     .dataType(DateType)
     .generatedAlwaysAs("CAST(dateOfBirth AS DATE)")
     .build())
  .addColumn("ssn", "STRING")
  .addColumn("salary", "INT")
  .execute()

生成的列像普通列一样存储。 也就是说,它们会占用存储空间。

以下限制适用于生成的列:

  • 生成表达式可以使用 Spark 中的任何 SQL 函数,这些函数在给定相同的参数值时始终返回相同的结果,但以下类型的函数除外:
    • 用户定义的函数。
    • 聚合函数。
    • 窗口函数。
    • 返回多行的函数。

每当以下表达式之一定义了分区列时,Delta Lake 就可为查询生成分区筛选器:

注意

Databricks Runtime 10.4 LTS 及更低版本中需要 Photon。 Databricks Runtime 11.3 LTS 及更高版本中不需要 Photon。

  • CAST(col AS DATE)col 的类型为 TIMESTAMP
  • YEAR(col)col 的类型为 TIMESTAMP
  • YEAR(col), MONTH(col) 定义的两个分区列,col 的类型为 TIMESTAMP
  • YEAR(col), MONTH(col), DAY(col) 定义的三个分区列,col 的类型为 TIMESTAMP
  • YEAR(col), MONTH(col), DAY(col), HOUR(col) 定义的四个分区列,col 的类型为 TIMESTAMP
  • SUBSTRING(col, pos, len)col 的类型为 STRING
  • DATE_FORMAT(col, format)col 的类型为 TIMESTAMP
    • 只能使用以下模式的日期格式:yyyy-MMyyyy-MM-dd-HH
    • 在 Databricks Runtime 10.4 LTS 及更高版本中,还可以使用以下模式:yyyy-MM-dd

如果上述表达式之一定义了分区列,并且查询使用生成表达式的基础列筛选数据,则 Delta Lake 会查找基础列和生成的列之间的关系,并在可能的情况下,基于生成的分区列填充分区筛选器。 例如,如果提供了以下表:

CREATE TABLE events(
eventId BIGINT,
data STRING,
eventType STRING,
eventTime TIMESTAMP,
eventDate date GENERATED ALWAYS AS (CAST(eventTime AS DATE))
)
PARTITIONED BY (eventType, eventDate)

然后运行以下查询:

SELECT * FROM events
WHERE eventTime >= "2020-10-01 00:00:00" <= "2020-10-01 12:00:00"

则 Delta Lake 会自动生成一个分区筛选器,这样,即使未指定分区筛选器,上述查询也只会读取分区 date=2020-10-01 中的数据。

另举一例,如果提供了以下表:

CREATE TABLE events(
eventId BIGINT,
data STRING,
eventType STRING,
eventTime TIMESTAMP,
year INT GENERATED ALWAYS AS (YEAR(eventTime)),
month INT GENERATED ALWAYS AS (MONTH(eventTime)),
day INT GENERATED ALWAYS AS (DAY(eventTime))
)
PARTITIONED BY (eventType, year, month, day)

然后运行以下查询:

SELECT * FROM events
WHERE eventTime >= "2020-10-01 00:00:00" <= "2020-10-01 12:00:00"

则 Delta Lake 会自动生成一个分区筛选器,这样,即使未指定分区筛选器,上述查询也只会读取分区 year=2020/month=10/day=01 中的数据。

可以使用 EXPLAIN 子句并检查提供的计划,以查看 Delta Lake 是否自动生成任何分区筛选器。

在 Delta Lake 中使用标识列

重要

在 Delta 表上声明标识列会禁用并发事务。 仅在不需要对目标表进行并发写入的用例中使用标识列。

Delta Lake 标识列是一种生成的列,将为插入到表中的每条记录分配唯一值。 以下示例演示了在 create table 语句期间声明标识列的基本语法:

SQL

CREATE TABLE table_name (
  id_col1 BIGINT GENERATED ALWAYS AS IDENTITY,
  id_col2 BIGINT GENERATED ALWAYS AS IDENTITY (START WITH -1 INCREMENT BY 1),
  id_col3 BIGINT GENERATED BY DEFAULT AS IDENTITY,
  id_col4 BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH -1 INCREMENT BY 1)
 )

Python

from delta.tables import DeltaTable, IdentityGenerator
from pyspark.sql.types import LongType

DeltaTable.create()
  .tableName("table_name")
  .addColumn("id_col1", dataType=LongType(), generatedAlwaysAs=IdentityGenerator())
  .addColumn("id_col2", dataType=LongType(), generatedAlwaysAs=IdentityGenerator(start=-1, step=1))
  .addColumn("id_col3", dataType=LongType(), generatedByDefaultAs=IdentityGenerator())
  .addColumn("id_col4", dataType=LongType(), generatedByDefaultAs=IdentityGenerator(start=-1, step=1))
  .execute()

Scala

import io.delta.tables.DeltaTable
import org.apache.spark.sql.types.LongType

DeltaTable.create(spark)
  .tableName("table_name")
  .addColumn(
    DeltaTable.columnBuilder(spark, "id_col1")
      .dataType(LongType)
      .generatedAlwaysAsIdentity().build())
  .addColumn(
    DeltaTable.columnBuilder(spark, "id_col2")
      .dataType(LongType)
      .generatedAlwaysAsIdentity(start = -1L, step = 1L).build())
  .addColumn(
    DeltaTable.columnBuilder(spark, "id_col3")
      .dataType(LongType)
      .generatedByDefaultAsIdentity().build())
  .addColumn(
    DeltaTable.columnBuilder(spark, "id_col4")
      .dataType(LongType)
      .generatedByDefaultAsIdentity(start = -1L, step = 1L).build())
  .execute()

注意

Databricks Runtime 16.0 及更高版本提供用于标识列的 Scala 和 Python API。

若要查看用于创建包含标识列的表的所有 SQL 语法选项,请参阅 CREATE TABLE [USING]

可以选择指定以下内容:

  • 起始值。
  • 步长,可以是正值或负值。

起始值和步长默认为 1。 不能指定步长 0

标识列分配的值是唯一的,朝指定步长的方向按指定步长大小的倍数递增,但不保证是连续的。 例如,如果起始值为 0,步长为 2,则所有值都是正偶数,但有些偶数可能会被跳过。

使用子句 GENERATED BY DEFAULT AS IDENTITY 时,插入操作可以为标识列指定值。 将该子句修改为 GENERATED ALWAYS AS IDENTITY,以替代手动设置值的功能。

标识列仅支持 BIGINT 类型,如果分配的值超过 BIGINT 支持的范围,则操作将会失败。

若要了解如何将标识列值与数据同步,请参阅 ALTER TABLE ...COLUMN 子句

CTAS 和标识列

在使用 CREATE TABLE table_name AS SELECT (CTAS) 语句时,不能定义架构、标识列约束或任何其他表规范。

要使用标识列创建新表并使用现有数据填充该表,请执行以下操作:

  1. 创建具有正确架构的表,包括标识列定义和他其表属性。
  2. 运行 INSERT 操作。

以下示例使用 DEFAULT 关键字来定义标识列。 如果插入到表中的数据包括标识列的有效值,则使用这些值。

CREATE OR REPLACE TABLE new_table (
  id BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 5),
  event_date DATE,
  some_value BIGINT
);

-- Inserts records including existing IDs
INSERT INTO new_table
SELECT id, event_date, some_value FROM old_table;

-- Insert records and generate new IDs
INSERT INTO new_table
SELECT event_date, some_value FROM new_records;

标识列限制

使用标识列时存在以下限制:

  • 启用了标识列的表不支持并发事务。
  • 不能按标识列对表进行分区。
  • 不能使用 ALTER TABLEADDREPLACECHANGE 标识列。
  • 不能更新现有记录的标识列的值。

注意

若要更改现有记录的 IDENTITY 值,必须删除该记录并将其 INSERT 为新记录。