透過結構演進更新資料表結構

資料表支援結構演進,允許隨資料需求變化調整資料表結構。 支援下列類型的變更:

這些變更可以顯性地使用 DDL 或隱性地使用 DML。

Important

結構更新會與所有並發的寫入操作發生衝突。 Databricks 建議協調結構變更以避免寫入衝突。

更新資料表結構會終止從該資料表讀取的任何串流。 若要繼續處理,請使用結構 化串流生產考量中描述的方法重新啟動串流。

手動結構變更

使用 ALTER TABLE 語句明確更改資料表的結構,而不寫入新資料。

新增欄

可為 ALTER TABLE ... ADD COLUMNS 現有表格新增一個或多個欄位,並可選擇指定位置與註解:

ALTER TABLE table_name ADD COLUMNS (col_name data_type [COMMENT col_comment] [FIRST|AFTER colA_name], ...)

根據預設,可空性為 true

範例:新增巢狀欄位

如需新增欄至巢狀欄位,請使用:

ALTER TABLE table_name ADD COLUMNS (col_name.nested_col_name data_type [COMMENT col_comment] [FIRST|AFTER colA_name], ...)

舉例來說,如果你有一個包含以下結構的表格:

- root
| - colA
| - colB
| +-field1
| +-field2

執行 ALTER TABLE boxes ADD COLUMNS (colB.nested STRING AFTER field1)後,結構會改變為:

- root
| - colA
| - colB
| +-field1
| +-nested
| +-field2

若要在巢狀於 ARRAYMAP 內的結構中新增欄位,請在資料行路徑中使用下列其中一個特殊屬性:

  • element 對於 的元素 ARRAY
  • key 用於一個 MAP 的調
  • value 用於 MAP 的值

例如,下列敘述會根據其型別,在巢狀於 z 內的結構中各新增一個 points 欄位:

-- points is ARRAY<STRUCT<x: DOUBLE, y: DOUBLE>>
ALTER TABLE my_table ADD COLUMNS (points.element.z DOUBLE);

-- points is MAP<STRUCT<x: DOUBLE, y: DOUBLE>, BIGINT>
ALTER TABLE my_table ADD COLUMNS (points.key.z DOUBLE);

-- points is MAP<STRING, STRUCT<x: DOUBLE, y: DOUBLE>>
ALTER TABLE my_table ADD COLUMNS (points.value.z DOUBLE);

更改欄位評論與排序

ALTER TABLE ... ALTER COLUMN 來更新欄位的評論或將其相對於其他欄位重新排序:

ALTER TABLE table_name ALTER [COLUMN] col_name (COMMENT col_comment | FIRST | AFTER colA_name)

範例:變更巢狀欄位

若要變更巢狀欄位中的數據行,請使用:

ALTER TABLE table_name ALTER [COLUMN] col_name.nested_col_name (COMMENT col_comment | FIRST | AFTER colA_name)

舉例來說,如果你有一個包含以下結構的表格:

- root
| - colA
| - colB
| +-field1
| +-field2

執行 ALTER TABLE boxes ALTER COLUMN colB.field2 FIRST後,結構會改變為:

- root
| - colA
| - colB
| +-field2
| +-field1

替換欄位

可使用 ALTER TABLE ... REPLACE COLUMNS 此工具重新定義表格的完整欄位清單,包括在單一操作中新增、移除、重新排序或重新命名欄位:

ALTER TABLE table_name REPLACE COLUMNS (col_name1 col_type1 [COMMENT col_comment1], ...)

範例:替換巢狀欄位

例如,執行下列 DDL 時:

ALTER TABLE boxes REPLACE COLUMNS (colC STRING, colB STRUCT<field2:STRING, nested:STRING, field1:STRING>, colA STRING)

如果你有一個包含這個結構的表格:

- root
| - colA
| - colB
| +-field1
| +-field2

結構會改變為:

- root
| - colC
| - colB
| +-field2
| +-nested
| +-field1
| - colA

重新命名欄

若要重新命名數據行而不重寫任何數據行的現有數據,您必須啟用資料表的數據行對應。 請參閱 使用 Delta Lake 欄位映射重新命名和刪除欄位

若要重新命名欄位:

ALTER TABLE table_name RENAME COLUMN old_col_name TO new_col_name

範例:重新命名巢狀欄位

若要重新命名巢狀欄位:

ALTER TABLE table_name RENAME COLUMN col_name.old_nested_field TO new_nested_field

例如,當您執行下列命令時:

ALTER TABLE boxes RENAME COLUMN colB.field1 TO field001

如果你有一個包含這個結構的表格:

- root
| - colA
| - colB
| +-field1
| +-field2

結構會改變為:

- root
| - colA
| - colB
| +-field001
| +-field2

請參閱 使用 Delta Lake 欄位映射重新命名和刪除欄位

刪除欄位

若要在不重寫任何資料檔案的情況下,將欄位移除為僅限元資料的操作,您必須啟用資料表的欄位對應。 請參閱 使用 Delta Lake 欄位映射重新命名和刪除欄位

Note

從元數據移除欄位並不會刪除檔案中該欄位的基礎數據。 若要清除已刪除欄位的資料:

  • REORG TABLE 來重寫檔案。
  • 接著用 VACUUM 來物理刪除包含遺失欄位資料的檔案。

若要刪除欄:

ALTER TABLE table_name DROP COLUMN col_name

若要刪除多個欄位:

ALTER TABLE table_name DROP COLUMNS (col_name_1, col_name_2)

變更欄位類型或名稱

您可以藉由重寫資料表來變更資料行的類型或名稱,或卸除數據行。 若要這樣做,請使用 overwriteSchema 選項。

下列範例顯示如何變更資料行的類型:

(spark.read.table(...)
  .withColumn("birthDate", col("birthDate").cast("date"))
  .write
  .mode("overwrite")
  .option("overwriteSchema", "true")
  .saveAsTable(...)
)

下列範例顯示變更資料列名稱:

(spark.read.table(...)
  .withColumnRenamed("dateOfBirth", "birthDate")
  .write
  .mode("overwrite")
  .option("overwriteSchema", "true")
  .saveAsTable(...)
)

啟用架構演進

使用 WITH SCHEMA EVOLUTION 或將 mergeSchema 設為 true,根據你想要INSERTMERGE到現有資料表中的資料結構描述來變更結構描述。

啟用結構演化,請使用以下方法之一:

  • INSERT WITH SCHEMA EVOLUTION
  • MERGE WITH SCHEMA EVOLUTION 你可以在 SQL 語法中使用 WITH SCHEMA EVOLUTION,或在 Azure Databricks API 中使用 .withSchemaEvolution()
  • 設定mergeSchema批次寫入或串流寫入的選項。 個別寫入操作時設定 .option("mergeSchema", "true")
  • 設定 Spark 配置(舊版配置): 將整個 SparkSession 中的 spark.databricks.delta.schema.autoMerge.enabled 設定為 true

Databricks 建議使用 WITH SCHEMA EVOLUTION 語法或選項 mergeSchema 來啟用每次寫入操作的結構演化,而非設定 Spark 設定。

當你使用選項或語法來在寫入操作中啟用結構演化時,這會優先於 Spark 設定。

啟用寫入的架構演進以新增欄位

啟用 schema 演化時,來源查詢中存在但目標資料表中缺少的欄位會自動加入寫入交易。 請參閱 啟用架構演進

請考慮下列事項:

  • 附加新欄位時,會保留大小寫。
  • 新的欄位會添加至資料表架構的結尾。
  • 如果其他數據行位於結構中,則會附加至目標數據表中結構結尾。

INSERT 使用 SQL 進行結構描述演進

在 Databricks Runtime 18.1 及以上版本中,請在 WITH SCHEMA EVOLUTION 陳述式中使用 INSERT 子句來啟用結構演進:

INSERT WITH SCHEMA EVOLUTION INTO target_table
SELECT * FROM source_table

如果查詢 source_table 回傳的欄位不存在於目標資料表中,這些欄位會自動加入結構中 target_table 。 現有的列會在新欄位中獲得 NULL 值。

WITH SCHEMA EVOLUTION 子句支持 INSERT INTOINSERT OVERWRITEINSERT INTO ... REPLACE 形式。 目標必須是三角洲湖或阿帕奇冰山桌。 在插入至 Hive 或其他非 Delta 資料表中使用此子句時,會傳回錯誤。

如果你使用 Databricks Runtime 18.0 或更低版本,請改用 mergeSchema 選項來啟用結構描述演進。 請參閱 INSERT使用 DataFrame API 的結構描述演進

INSERT 並透過 DataFrame API 進行結構演化

下列範例示範搭配批次寫入作業使用 mergeSchema 選項:

Python
(spark.read
  .table("source_table")
  .write
  .option("mergeSchema", "true")
  .mode("append")
  .saveAsTable("target_table")
)
Scala
spark.read
  .table("source_table")
  .write
  .option("mergeSchema", "true")
  .mode("append")
  .saveAsTable("target_table")

INSERT 使用 Structured Streaming 的結構描述演進

以下範例示範了使用 mergeSchema 結構化串流自動載入器(Auto Loader)的選項。 請參閱 什麼是自動載入器?

(spark.readStream
  .format("cloudFiles")
  .option("cloudFiles.format", "json")
  .option("cloudFiles.schemaLocation", "<path-to-schema-location>")
  .load("<path-to-source-data>")
  .writeStream
  .option("mergeSchema", "true")
  .option("checkpointLocation", "<path-to-checkpoint>")
  .trigger(availableNow=True)
  .toTable("table_name")
)

合併時的自動結構演化

對於 MERGE,schema 演化可以讓你解決目標與來源資料表之間的結構不匹配。 它會處理下列兩種情況:

  1. 欄位存在於來源表格中,但不存在於目標表格中,且在插入或更新操作的指派中以名稱被指定。 或者,存在 UPDATE SET *INSERT * 動作。

    該數據行會新增至目標架構,而且其值將會從來源中的對應數據行填入。

    • 這隻適用於合併來源中的數據行名稱和結構完全符合目標指派時。

    • 新資料行必須存在於來源結構描述中。 在 action 子句中指派新的資料行並不等於定義該資料行。

    這些範例允許架構演進:

    -- The column newcol is present in the source but not in the target. It will be added to the target.
    UPDATE SET target.newcol = source.newcol
    
    -- The field newfield doesn't exist in struct column somestruct of the target. It will be added to that struct column.
    UPDATE SET target.somestruct.newfield = source.somestruct.newfield
    
    -- The column newcol is present in the source but not in the target.
    -- It will be added to the target.
    UPDATE SET target.newcol = source.newcol + 1
    
    -- Any columns and nested fields in the source that don't exist in target will be added to the target.
    UPDATE SET *
    INSERT *
    

    如果資料行 newcol 不存在於結構描述中 source ,這些範例不會觸發結構描述演進:

    UPDATE SET target.newcol = source.someothercol
    UPDATE SET target.newcol = source.x + source.y
    UPDATE SET target.newcol = source.output.newcol
    
  2. 目標數據表中存在一個列,但源數據表中不存在該列。

    目標架構不會變更。 這些欄位:

    • 保持不變 UPDATE SET *

    • 設定為 NULLINSERT *

    • 如果在行動條款中被指定,仍可能被明確修改。

    例如:

    UPDATE SET *  -- The target columns that are not in the source are left unchanged.
    INSERT *  -- The target columns that are not in the source are set to NULL.
    UPDATE SET target.onlyintarget = 5  -- The target column is explicitly updated.
    UPDATE SET target.onlyintarget = source.someothercol  -- The target column is explicitly updated from some other source column.
    

您必須手動啟用自動架構演進。 請參閱 啟用架構演進

Note

在 Databricks Runtime 11.3 LTS 和更早的版本中,架構演進只能使用 INSERT *UPDATE SET * 動作來進行合併。

在 Databricks Runtime 12.2 LTS 和更新版本中,源表中現有的列和結構欄位可以在插入或更新操作中透過名稱來指定。

Databricks Runtime 13.3 LTS 和更新版本中,您可以使用模式演進搭配巢狀結構,例如 map<int, struct<a: int, b: int>>

MERGE結構演化則使用 SQL、Python 和 Scala

在 Databricks Runtime 15.4 LTS 及以上版本中,你可以使用 SQL 或資料表 API 在合併語句中指定結構演進:

SQL
MERGE WITH SCHEMA EVOLUTION INTO target
USING source
ON source.key = target.key
WHEN MATCHED THEN
  UPDATE SET *
WHEN NOT MATCHED THEN
  INSERT *
WHEN NOT MATCHED BY SOURCE THEN
  DELETE
Python
from delta.tables import *

(targetTable
  .merge(sourceDF, "source.key = target.key")
  .withSchemaEvolution()
  .whenMatchedUpdateAll()
  .whenNotMatchedInsertAll()
  .whenNotMatchedBySourceDelete()
  .execute()
)
Scala
import io.delta.tables._

targetTable
  .merge(sourceDF, "source.key = target.key")
  .withSchemaEvolution()
  .whenMatched()
  .updateAll()
  .whenNotMatched()
  .insertAll()
  .whenNotMatchedBySource()
  .delete()
  .execute()

結構演化的操作 MERGE 範例

以下是 MERGE 操作在有和沒有架構演進情況下的效果範例。

Columns 查詢 (在 SQL 中) 沒有架構演進的行為 (預設值) 架構演進的行為
目標資料列: key, value
來源資料列: key, value, new_value
MERGE INTO target_table t
USING source_table s
ON t.key = s.key
WHEN MATCHED
THEN UPDATE SET *
WHEN NOT MATCHED
THEN INSERT *
數據表架構保持不變;只會更新/插入數據行keyvalue 資料表架構會變更為 (key, value, new_value)。 現有具有匹配項目的記錄會使用valuenew_value在來源中進行更新。 新的資料列會依照架構 (key, value, new_value) 插入。
目標資料列: key, old_value
來源資料列: key, new_value
MERGE INTO target_table t
USING source_table s
ON t.key = s.key
WHEN MATCHED
THEN UPDATE SET *
WHEN NOT MATCHED
THEN INSERT *
UPDATEINSERT 動作會擲回錯誤,因為目標數據行 old_value 不在來源中。 資料表架構會變更為 (key, old_value, new_value)。 現有具有相符項目的記錄會用來源中的 new_value 更新,同時保持 old_value 不變。 新的記錄會插入指定的keynew_valueNULL對應的old_value
目標資料列: key, old_value
來源資料列: key, new_value
MERGE INTO target_table t
USING source_table s
ON t.key = s.key
WHEN MATCHED
THEN UPDATE SET new_value = s.new_value
UPDATE 擲回錯誤,因為數據行 new_value 不存在於目標數據表中。 資料表架構會變更為 (key, old_value, new_value)。 具有相符項目的現有記錄會與來源中的new_value同步更新,同時保持old_value不變,而不相符的記錄則在NULL中輸入new_value。 請參閱附註 (1)
目標資料列: key, old_value
來源資料列: key, new_value
MERGE INTO target_table t
USING source_table s
ON t.key = s.key
WHEN NOT MATCHED
THEN INSERT (key, new_value) VALUES (s.key, s.new_value)
INSERT 擲回錯誤,因為數據行 new_value 不存在於目標數據表中。 資料表架構會變更為 (key, old_value, new_value)。 新的記錄會插入指定的keynew_valueNULL對應的old_value。 現有記錄 NULL 已輸入,讓 new_value 保持 old_value 不變。 請參閱附註 (1)

(1) 此行為可在 Databricks Runtime 12.2 LTS 及以上版本中提供;在此狀態下,Databricks 執行時 LTS 11.3 及以下版本出現錯誤。

合併時排除欄位

在 Databricks Runtime 12.2 LTS 和更新版本中,您可以在合併條件中使用 EXCEPT 子句來明確排除數據行。 EXCEPT 關鍵詞的行為會根據是否啟用架構演進而有所不同。

當結構描述演化停用時,EXCEPT 關鍵字會套用至目標資料表中的欄位清單,並允許從 UPDATEINSERT 動作中排除欄位。 被排除的欄位會設定為 null

啟用架構演進后, EXCEPT 關鍵詞會套用至源數據表中的數據行清單,並允許從架構演進中排除數據行。 如果來源中的新欄位不存在於目標資料表中,且該欄位列在 EXCEPT 子句中,則不會新增至目標綱要。 在目標中已存在的排除的欄位會設定為 null

搭配 EXCLUDEMERGE 範例

下列範例示範此語法:

Columns 查詢 (在 SQL 中) 沒有架構演進的行為 (預設值) 架構演進的行為
目標資料列: id, title, last_updated
來源資料列: id, title, review, last_updated
MERGE INTO target t
USING source s
ON t.id = s.id
WHEN MATCHED
THEN UPDATE SET last_updated = current_date()
WHEN NOT MATCHED
THEN INSERT * EXCEPT (last_updated)
比對的數據列會藉由將 last_updated 字段設定為目前日期來更新。 新的列是使用idtitle的值來插入的。 排除的欄位 last_updated 會設定為 null。 欄位 review 會被忽略,因為它不在目標中。 比對的數據列會藉由將 last_updated 字段設定為目前日期來更新。 架構會演進以新增欄位 review。 新的資料列會使用所有來源欄位插入,設定為last_updatednull除外。
目標資料列: id, title, last_updated
來源資料列: id, title, review, internal_count
MERGE INTO target t
USING source s
ON t.id = s.id
WHEN MATCHED
THEN UPDATE SET last_updated = current_date()
WHEN NOT MATCHED
THEN INSERT * EXCEPT (last_updated, internal_count)
INSERT 擲回錯誤,因為數據行 internal_count 不存在於目標數據表中。 比對的數據列會藉由將 last_updated 字段設定為目前日期來更新。 欄位 review 會新增至目標數據表,但 internal_count 會忽略欄位。 插入的新資料列已設定 last_updatednull

啟用 Spark 配置的結構演化(舊有系統)

你可以將 Spark 設定 spark.databricks.delta.schema.autoMerge.enabled 設為 true,讓目前 SparkSession 中的所有寫入操作都能進行結構演化:

Python

spark.conf.set("spark.databricks.delta.schema.autoMerge.enabled", True)

Scala

spark.conf.set("spark.databricks.delta.schema.autoMerge.enabled", true)

SQL

SET spark.databricks.delta.schema.autoMerge.enabled=true

Note

Databricks 不建議這種做法用於生產環境。 設定全會話配置可能導致多個操作間的非預期結構變更,並使判斷哪些操作演化結構變得困難。

取而代之的是,為每個寫入操作啟用結構演化:

當你使用選項或語法來在寫入操作中啟用結構演化時,這會優先於 Spark 設定。

替換表格結構

預設情況下,覆寫資料表中的資料不會覆寫結構描述。 當你使用 mode("overwrite") 而不使用 replaceWhere 來覆寫資料表時,可能仍會想要覆寫所寫入資料的結構描述。

要替換資料表的結構與分割,請將選項 overwriteSchema 設為 true

df.write.option("overwriteSchema", "true")

Note

您無法在使用動態分割覆寫時指定 overwriteSchematrue 。 請參見 使用 partitionOverwriteMode 覆寫動態分割區(legacy)