다음을 통해 공유


테이블 스키마 업데이트

테이블은 스키마 진화를 지원하므로 데이터 요구 사항이 변경되면 테이블 구조를 수정할 수 있습니다. 지원되는 변경 유형은 다음과 같습니다.

  • 임의의 위치에 새 열 추가
  • 기존 열 순서 다시 지정
  • 기존 열 이름 바꾸기

DDL을 명시적으로 사용하거나 DML을 암시적으로 사용하여 변경합니다.

중요합니다

스키마 업데이트는 모든 동시 쓰기 작업과 충돌합니다. Databricks는 쓰기 충돌을 방지하기 위해 스키마 변경 내용을 조정하는 것이 좋습니다.

테이블 스키마를 업데이트하면 해당 테이블에서 읽는 모든 스트림이 종료됩니다. 처리를 계속하려면 구조적 스트리밍에 대한 프로덕션 고려 사항에 설명된 방법을 사용하여 스트림을 다시 시작합니다.

스키마를 명시적으로 업데이트하여 열을 추가하기

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

기본적으로 null 허용 여부는 true입니다.

열을 중첩된 필드에 추가하려면 다음을 사용합니다.

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

예를 들어 ALTER TABLE boxes ADD COLUMNS (colB.nested STRING AFTER field1)를 실행하기 전의 스키마가 다음과 같은 경우가 있습니다.

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

실행 이후 스키마는 다음과 같습니다.

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

참고

중첩 열 추가는 구조체에만 지원됩니다. 배열 및 맵은 지원되지 않습니다.

스키마를 명시적으로 업데이트하여 열의 주석 또는 순서를 변경합니다.

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)

예를 들어 ALTER TABLE boxes ALTER COLUMN colB.field2 FIRST를 실행하기 전의 스키마가 다음과 같은 경우가 있습니다.

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

실행 이후 스키마는 다음과 같습니다.

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

열을 바꾸기 위해 스키마를 명시적으로 업데이트

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

스키마를 명시적으로 업데이트하여 열 이름 변경

참고

이 기능은 Databricks Runtime 10.4 LTS 이상에서 사용할 수 있습니다.

열의 기존 데이터를 다시 작성하지 않고 열 이름을 바꾸려면 테이블에 열 매핑을 사용하도록 설정해야 합니다. 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 열 매핑을 사용하여 열 이름 바꾸기 및 삭제를 참조하세요.

스키마를 명시적으로 업데이트하여 열 삭제

참고

이 기능은 Databricks Runtime 11.3 LTS 이상에서 사용할 수 있습니다.

데이터 파일을 다시 작성하지 않고 메타데이터 전용 작업으로 열을 삭제하려면 테이블에 대한 열 매핑을 사용하도록 설정해야 합니다. Delta Lake 열 매핑을 사용하여 열 이름 바꾸기 및 삭제를 참조하세요.

중요합니다

메타데이터에서 열을 삭제해도 파일의 열에 대한 기본 데이터는 삭제되지 않습니다. 삭제된 열 데이터를 제거하려면 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(...)
)

스키마 진화 활성화

다음 방법 중 하나를 사용하여 스키마 진화를 사용하도록 설정합니다.

Databricks는 쓰기 작업마다 스키마 진화를 활성화하기 위해 Spark 구성을 설정하는 대신 mergeSchema 옵션이나 WITH SCHEMA EVOLUTION 구문을 사용하는 것을 권장합니다.

옵션 또는 구문을 사용하여 쓰기 작업에서 스키마 진화를 사용하도록 설정하는 경우 Spark 구성보다 우선합니다.

참고

INSERT INTO 문장에 대한 WITH SCHEMA EVOLUTION 절은 없습니다. 대신 옵션을 mergeSchema 사용합니다. 새 열을 추가하려면 쓰기에 대한 스키마 진화 사용을 참조 하세요.

추가적인 열을 포함하기 위해 쓰기에 대한 스키마 진화를 활성화합니다.

원본 쿼리에 있지만 대상 테이블에서 누락된 열은 스키마 진화를 사용하도록 설정하면 쓰기 트랜잭션의 일부로 자동으로 추가됩니다. 스키마 진화 활성화를 참조하세요.

새 열을 추가할 때 대소문자가 유지됩니다. 테이블 스키마의 끝에 새 열이 추가됩니다. 추가 열이 구조체에 있는 경우 대상 테이블의 구조체 끝에 추가됩니다.

참고

SQL 구문을 MERGE 지원하는 INSERT 문은 스키마 진화를 mergeSchema 사용하도록 설정하는 옵션을 사용합니다. INSERT INTO문에 대한 WITH SCHEMA EVOLUTION절은 존재하지 않습니다.

INSERT DataFrame API를 사용하여 스키마 진화

다음 예제에서는 일괄 처리 쓰기 작업과 함께 옵션을 사용하는 mergeSchema 방법을 보여 줍니다.

파이썬

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

스칼라

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

INSERT 스트리밍에서 스키마 진화

다음 예제는 자동 로더에서 mergeSchema 옵션을 사용하는 방법을 보여 줍니다. 자동 로더란?을 참조하세요.

(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")
)

병합에 대한 자동 스키마 진화

스키마를 개선하면 병합에서 대상 테이블과 원본 테이블 간의 스키마 불일치를 해결할 수 있습니다. 다음 두 가지 경우를 처리합니다.

  1. 열은 원본 테이블에 있지만 대상 테이블에는 없으며 삽입 또는 업데이트 작업의 할당에서 이름으로 지정됩니다. 또는 UPDATE SET * 또는 INSERT * 동작이 있습니다.

    해당 열이 대상 스키마에 추가되고 해당 값은 원본의 해당 열에서 채워집니다.

    • 이는 병합 원본의 열 이름과 구조가 대상 할당과 정확히 일치하는 경우에만 적용됩니다.

    • 새 열은 원본 스키마에 있어야 합니다. 작업 절에 새 열을 할당해도 해당 열은 정의되지 않습니다.

    다음 예제는 스키마 진화를 허용합니다.

    -- 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 *
    

    다음 예제에서는 열 newcolsource 스키마에 없는 경우 스키마 진화가 유발되지 않습니다.

    UPDATE SET target.newcol = source.someothercol
    UPDATE SET target.newcol = source.x + source.y
    UPDATE SET target.newcol = source.output.newcol
    
  2. 대상 테이블에는 열이 있지만 원본 테이블에는 열이 없습니다.

    대상 스키마는 변경되지 않습니다. 다음 열은 다음과 같습니다.

    • UPDATE SET *에 대해 변경 없이 유지됩니다.

    • INSERT *에 대해 NULL로 설정됩니다.

    • 작업 절에 할당된 경우 계속 명시적으로 수정할 수 있습니다.

    다음은 그 예입니다.

    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.
    

자동 스키마 진화를 수동으로 사용하도록 설정해야 합니다. 스키마 진화 사용을 활성화 참조하세요.

참고

Databricks Runtime 12.2 LTS 이상에서는 원본 테이블에 있는 열 및 구조체 필드를 삽입 또는 업데이트 작업의 이름으로 지정할 수 있습니다. Databricks Runtime 11.3 LTS 이하에서는 병합을 사용하여 스키마 진화에만 INSERT * 또는 UPDATE SET * 작업을 사용할 수 있습니다.

Databricks Runtime 13.3 LTS 이상에서는 맵 내에 중첩된 구조체(예: map<int, struct<a: int, b: int>>)를 이용한 스키마 진화를 사용할 수 있습니다.

병합을 위한 스키마 진화 구문

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

파이썬

from delta.tables import *

(targetTable
  .merge(sourceDF, "source.key = target.key")
  .withSchemaEvolution()
  .whenMatchedUpdateAll()
  .whenNotMatchedInsertAll()
  .whenNotMatchedBySourceDelete()
  .execute()
)

스칼라

import io.delta.tables._

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

스키마 진화와 병합의 예제 작업

다음은 스키마 개선이 사용된 경우와 사용되지 않은 경우에 merge 작업의 효과를 보여 주는 몇 가지 예제입니다.

컬럼 쿼리(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 *
대상 열 UPDATE가 원본에 없기 때문에 INSERTold_value 동작이 오류를 throw합니다. 테이블 스키마가 (key, old_value, new_value)로 변경됩니다. 일치하는 항목이 있는 기존 레코드는 원본의 new_value을 사용하여 업데이트되며, old_value은 변경되지 않습니다. 새 레코드는 지정된 key, new_value, 및 NULL와 함께 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 열이 존재하지 않아 오류를 throw합니다. 테이블 스키마가 (key, old_value, new_value)로 변경됩니다. 일치하는 항목이 있는 기존 레코드는 원본의 new_value로 업데이트되며 old_value은 변경되지 않고, 일치하지 않는 레코드에는 new_valueNULL가 입력됩니다. 참고 (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)로 변경됩니다. 새 레코드는 old_value에 대해 지정된 key, new_value, 및 NULL와 함께 삽입됩니다. 기존 레코드는 new_value에 대해 NULL 입력되었고, old_value는 변경되지 않았습니다. 참고 (1)를 참조하세요.

(1) 이 동작은 Databricks Runtime 12.2 LTS 이상에서 사용할 수 있습니다. 이 조건의 Databricks Runtime 11.3 LTS 이하 오류입니다.

병합이 있는 열 제외

Databricks Runtime 12.2 LTS 이상에서는 병합 조건에서 절을 사용하여 EXCEPT 열을 명시적으로 제외할 수 있습니다. 키워드의 EXCEPT 동작은 스키마 진화를 사용하는지 여부에 따라 달라집니다.

스키마 진화가 비활성화된 상태에서는 EXCEPT 키워드가 대상 테이블의 열 목록에 적용되어 UPDATE 또는 INSERT 작업에서 열을 제외할 수 있습니다. 제외된 열은 null로 설정됩니다.

스키마 진화를 EXCEPT 사용하도록 설정하면 키워드가 원본 테이블의 열 목록에 적용되고 스키마 진화에서 열을 제외할 수 있습니다. 대상에 없는 원본의 새 열은 EXCEPT 절에 나열된 경우 대상 스키마에 추가되지 않습니다. 대상에 이미 있는 제외된 열은 null로 설정됩니다.

다음 예제에서는 이 구문을 보여 줍니다.

쿼리(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_updated이(가) null로 설정된 경우를 제외한 모든 원본 필드를 사용하여 새 행을 삽입합니다.
대상 열: 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가 오류를 throw합니다. 이는 internal_count 열이 대상 테이블에 존재하지 않기 때문입니다. 일치하는 행은 필드를 현재 날짜로 설정 last_updated 하여 업데이트됩니다. 필드 review 가 대상 테이블에 추가되지만 internal_count 필드는 무시됩니다. 새로운 행에는 last_updatednull로 설정됩니다.

Spark 구성을 사용하여 스키마 진화 사용(레거시)

현재 SparkSession의 모든 쓰기 작업에 대해 스키마 진화를 사용하도록 Spark 구성 spark.databricks.delta.schema.autoMerge.enabledtrue 을 설정할 수 있습니다.

파이썬

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

스칼라

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

SQL

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

중요합니다

이 방법은 프로덕션 용도에는 권장되지 않습니다. 대신 각 쓰기 작업에 대해 스키마 진화를 사용하도록 설정합니다.

세션 전체 구성을 설정하면 여러 작업에서 의도하지 않은 스키마 변경이 발생할 수 있으며 스키마를 발전시키는 작업을 추론하기가 더 어려워집니다.

옵션 또는 구문을 사용하여 쓰기 작업에서 스키마 진화를 사용하도록 설정하는 경우 Spark 구성보다 우선합니다.

스키마 업데이트에서 NullType 열을 처리하는 방법

Parquet은 NullTypeNullType 열을 지원하지 않으므로, 테이블에 쓸 때 이러한 열이 DataFrame에서 제외되지만 여전히 스키마에는 저장됩니다. 해당 열에 대해 다른 데이터 형식을 받으면 스키마가 새 데이터 형식으로 병합됩니다. NullType이 기존 열에 수신되면 이전 스키마가 유지되고 쓰기 중 새 열이 삭제됩니다.

스트리밍의 NullType은 지원되지 않습니다. 스트리밍을 사용할 때 스키마를 설정해야 하므로 이는 매우 드문 경우입니다. NullTypeArrayTypeMapType과 같은 복합 형식에도 허용되지 않습니다.

테이블 스키마 바꾸기

기본적으로 테이블의 데이터를 덮어쓰더라도 스키마는 덮어쓰지 않습니다. mode("overwrite") 없이 replaceWhere를 사용하여 테이블을 덮어쓰는 경우 여전히 쓰고 있는 데이터의 스키마를 덮어쓰려고 할 수 있습니다. overwriteSchema 옵션을 true로 설정하여 테이블의 스키마 및 분할을 바꿉니다.

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

중요합니다

동적 파티션 덮어쓰기를 사용할 때 overwriteSchematrue로 지정할 수 없습니다.