constraints 속성
constraints 속성
constraints 속성은 모델 테이블에 데이터가 채워질 때 데이터 플랫폼이 추가 검증을 수행하도록 지정해요. 검증에 실패하면 테이블 생성·갱신이 실패하고 롤백되며 명확한 에러 메시지를 보게 돼요. 강제(enforce)되면 모델로 구체화된 테이블에 잘못된 데이터가 절대 생기지 않음을 보장해요.
출처: 문서
본문
제약 조건(constraint)은 많은 데이터 플랫폼의 기능이에요. 지정하면 플랫폼이 새 테이블에 데이터를 채우거나 기존 테이블에 삽입할 때 추가 검증을 수행해요. 검증이 실패하면 테이블 생성·갱신이 실패하고 작업이 롤백되며 명확한 에러 메시지가 보여요. 강제되면 제약 조건은 모델로 구체화된 테이블에서 잘못된 데이터를 절대 볼 수 없다는 것을 보장해요. 다만 강제 수준은 데이터 플랫폼마다 크게 달라요.
Prerequisites
제약 조건을 사용하기 전에 다음 요구사항을 확인해 주세요:
- 지원되는 구체화를 사용하세요 — 제약 조건은
table과incremental모델에서만 동작해요.ephemeral모델이나view로 구체화된 모델에는 절대 적용되지 않아요. - 계약(contract)을 강제하세요 — 제약 조건을 사용하려면 모델이 계약을 선언하고 강제해야 해요. 즉 모델의 스키마 구성에서 모든 컬럼의
data_type을 명시적으로 정의해야 해요.
Platform constraint support
플랫폼별 제약 조건 지원에 대한 자세한 내용은 어댑터별 탭을 선택해 주세요. 제약 조건은 정의 가능성과 플랫폼 강제 여부에 따라 세 가지 범주로 나뉘어요:
- 정의·강제 가능(Definable and enforced) — 제약 조건을 위반하면 모델이 빌드되지 않아요.
- 정의 가능·강제 안 됨(Definable and not enforced) — 플랫폼이 제약 조건 유형 지정은 지원하지만, 이를 위반하더라도 모델이 여전히 빌드될 수 있어요. 이 제약 조건은 메타데이터 용도로만 존재해요. 이 접근 방식은 엄격한 규칙 강제가 더 흔한 트랜잭션 데이터베이스보다 클라우드 데이터 웨어하우스에서 더 일반적이에요.
- 정의·강제 불가(Not definable and not enforced) — 플랫폼에 제약 조건 유형을 지정할 수 없어요.
| Constraint type | Redshift Definable/Enforced | Snowflake Definable/Enforced | BigQuery Definable/Enforced | Postgres Definable/Enforced | Spark Definable/Enforced | Databricks Definable/Enforced | Athena Definable/Enforced |
|---|---|---|---|---|---|---|---|
| not_null | ✅ / ✅ | ✅ / ✅ | ✅ / ✅ | ✅ / ✅ | ✅ / ❌ | ✅ / ✅ | ❌ / ❌ |
| primary_key | ✅ / ❌ | ✅ / ❌ | ✅ / ❌ | ✅ / ✅ | ✅ / ❌ | ✅ / ❌ | ❌ / ❌ |
| foreign_key | ✅ / ❌ | ✅ / ❌ | ✅ / ❌ | ✅ / ✅ | ✅ / ❌ | ✅ / ❌ | ❌ / ❌ |
| unique | ✅ / ❌ | ✅ / ❌ | ❌ / ❌ | ✅ / ✅ | ✅ / ❌ | ❌ / ❌ | ❌ / ❌ |
| check | ❌ / ❌ | ❌ / ❌ | ❌ / ❌ | ✅ / ✅ | ✅ / ❌ | ✅ / ✅ | ❌ / ❌ |
참고: Spark에서는
not_null과check제약 조건이 모델 빌드 후에만 강제돼요. 이런 플랫폼 제약 때문에 dbt는 이들을 정의 가능하지만 강제되지 않는 것으로 간주해요. 즉 빌드 시점에 강제할 수 없으므로 모델 계약의 일부가 아니에요. 이 표는 기능이 발전함에 따라 달라질 수 있어요.
Defining constraints
제약 조건은 단일 컬럼에 정의하거나, 모델 레벨에서 하나 이상의 컬럼에 대해 정의할 수 있어요. 일반적으로 단일 컬럼 제약 조건은 해당 컬럼에 직접 정의하는 걸 권장해요.
단일 모델에 여러 primary_key 제약 조건을 정의한다면 반드시 모델 레벨에서 정의해야 해요. 컬럼 레벨에서 여러 primary_key 제약 조건을 정의하는 것은 지원되지 않아요.
제약 조건의 구조는 다음과 같아요:
type(필수):not_null,unique,primary_key,foreign_key,check,custom중 하나expression: 제약 조건을 한정하는 자유 텍스트 입력. 특정 제약 조건 유형에는 필수이고, 다른 것에는 선택적이에요.name(선택): 이 제약 조건의 사람이 읽기 좋은 이름. 일부 데이터 플랫폼에서 지원돼요.columns(모델 레벨만): 제약 조건을 적용할 컬럼 이름 목록. (dbt v1.9 이상 적용) 외래 키(foreign key) 제약 조건은 두 가지 추가 입력을 받아요:to: 참조되는 테이블을 나타내는 관계 입력(ref(), source() 등).to_columns: 해당 테이블의 대응하는 기본 키 또는 고유 키를 담은 컬럼 목록. 외래 키 정의 구문은ref를 사용하므로 종속성을 캡처하고 서로 다른 환경에서도 동작해요. dbt v1 Latest와 dbt v1.9+에서 사용할 수 있어요. 제약 조건 지원·강제가 플랫폼별로 다르므로, 어떤 제약 조건에도 지정할 수 있는 선택 필드 두 개가 있어요:warn_unenforced: 플랫폼이 지원하지만 강제하지 않는 제약 조건(예: Snowflake의primary_key)에 대한 경고를 건너뛰려면False로 설정해요.warn_unsupported: 플랫폼이 전혀 지원하지 않는 제약 조건(예: Redshift의check)에 대한 경고를 건너뛰려면False로 설정해요.
models/schema.yml
models:
- name: <model_name> # required
config:
materialized: table
contract: {enforced: true}
# model-level constraints
constraints:
- type: primary_key
columns: [first_column, second_column, ...]
warn_unsupported: True # show a warning if unsupported
- type: foreign_key # multi_column
columns: [first_column, second_column, ...]
to: ref('my_model_to') | source('source', 'source_table')
to_columns: [other_model_first_column, other_model_second_columns, ...]
- type: check
columns: [first_column, second_column, ...]
expression: "first_column != second_column"
name: human_friendly_name
- type: ...
columns:
- name: first_column
data_type: string
# column-level constraints
constraints:
- type: not_null
- type: unique
- type: foreign_key
to: ref('my_model_to') | source('source', 'source_table')
to_columns: [other_model_column]
- warn_unenforced: False # skips warning if supported but not enforced
- type: ...
지원되는 dbt 어댑터는 이 필드들이 채워지면 expression 대신 외래 키 제약 조건을 렌더링할 때 사용해요. 외래 키 제약 조건을 지원하는 어댑터에 대한 자세한 내용은 Platform constraint support 가이드를 참고해 주세요.
Platform-specific support
트랜잭션 데이터베이스에서는 특정 컬럼의 허용 값에 "제약 조건"을 정의할 수 있는데, 이는 값의 데이터 타입보다 더 엄격해요. 예를 들어 Postgres는 ANSI SQL 표준의 모든 제약 조건(not null, unique, primary key, foreign key)을 지원·강제하며, 불리언 표현식으로 평가되는 유연한 행 레벨 check 제약 조건도 추가로 지원해요.
대부분의 분석 데이터 플랫폼은 not null 제약 조건을 지원·강제하지만, 나머지는 지원하지 않거나 강제하지 않아요. 레거시 데이터 카탈로그나 ER 다이어그램 도구와 통합하기 위해, 강제되지는 않는 "정보 제공용" 제약 조건을 추가하고 싶을 때도 있어요 (dbt-labs/dbt#3295). 일부 데이터 플랫폼은 추가 키워드를 지정하면 쿼리 최적화에 기본 키·외래 키 제약 조건을 선택적으로 사용할 수 있어요.
이를 위해 어떤 제약 조건에도 지정할 수 있는 선택 필드 두 개가 있어요:
warn_unenforced: False— 이 데이터 플랫폼이 지원하지만 강제하지 않는 제약 조건에 대한 경고를 건너뛰게 해요. 템플릿 DDL에는 제약 조건이 포함돼요.warn_unsupported: False— 이 데이터 플랫폼이 지원하지 않아 템플릿 DDL에 포함되지 않는 제약 조건에 대한 경고를 건너뛰게 해요.
Postgres
PostgreSQL 제약 조건 문서는 여기에 있어요.
models/constraints_example.sql
{{ config(
materialized = "table"
)}}
select 1 as id, 'My Favorite Customer' as customer_name, cast('2019-01-01' as date) as first_transaction_date
models/schema.yml
models:
- name: dim_customers
config:
contract:
enforced: true
columns:
- name: id
data_type: int
constraints:
- type: not_null
- type: primary_key
- type: check
expression: "id > 0"
- name: customer_name
data_type: text
- name: first_transaction_date
data_type: date
제약 조건을 강제하는 예상 DDL:
target/run/.../constraints_example.sql
create table "database_name"."schema_name"."constraints_example__dbt_tmp"(
id integer not null primary key check (id > 0),
customer_name text,
first_transaction_date date
);
insert into "database_name"."schema_name"."constraints_example__dbt_tmp" (
id,
customer_name,
first_transaction_date
) (
select 1 as id, 'My Favorite Customer' as customer_name, cast('2019-01-01' as date) as first_transaction_date
);
Redshift
Redshift는 현재 not null 제약 조건만 강제하며, 나머지 제약 조건은 메타데이터일 뿐이에요. 또한 Redshift는 테이블 생성 시점에 컬럼 checks를 허용하지 않아요. 자세한 내용은 Redshift 문서를 참고해 주세요.
models/constraints_example.sql
{{ config(
materialized = "table"
)}}
select 1 as id, 'My Favorite Customer' as customer_name, cast('2019-01-01' as date) as first_transaction_date
models/schema.yml
models:
- name: dim_customers
config:
contract:
enforced: true
columns:
- name: id
data_type: integer
constraints:
- type: not_null
- type: primary_key # not enforced -- will warn & include
- type: check # not supported -- will warn & skip
expression: "id > 0"
data_tests:
- unique # primary_key constraint is not enforced
- name: customer_name
data_type: varchar
- name: first_transaction_date
data_type: date
Snowflake
Snowflake 제약 조건 문서는 여기, 데이터 타입은 여기에 있어요.
Snowflake는 unique, not null, primary key, foreign key 네 가지 제약 조건을 지원해요. 현재 실제로 검사되는 것은 not null(그리고 primary key의 not null 속성)뿐이라는 점이 중요해요.
나머지 제약 조건은 순수한 메타데이터로, 데이터 삽입 시 검증되지 않아요. Snowflake는 unique, primary, foreign_key 제약 조건을 검증하지 않지만, 제약 조건 expression 필드에 rely를 지정해 쿼리 최적화에 사용하도록 지시할 수는 있어요. 현재 Snowflake는 check 구문을 지원하지 않으며, dbt 프로젝트의 일부 모델에 설정된 check 구성은 dbt가 건너뛰고 경고 메시지를 띄워요.
models/constraints_example.sql
{{ config(
materialized = "table"
)}}
select 1 as id, 'My Favorite Customer' as customer_name, cast('2019-01-01' as date) as first_transaction_date
models/schema.yml
models:
- name: dim_customers
config:
contract:
enforced: true
columns:
- name: id
data_type: integer
description: hello
constraints:
- type: not_null
- type: primary_key # not enforced -- will warn & include
- type: check # not supported -- will warn & skip
expression: "id > 0"
data_tests:
- unique # need this test because primary_key constraint is not enforced
- name: customer_name
data_type: text
- name: first_transaction_date
data_type: date
제약 조건을 강제하는 예상 DDL:
target/run/.../constraints_example.sql
create or replace transient table <database>.<schema>.constraints_model (
id integer not null primary key,
customer_name text,
first_transaction_date date
)
as (
select 1 as id, 'My Favorite Customer' as customer_name, cast('2019-01-01' as date) as first_transaction_date
);
BigQuery
BigQuery는 not null 제약 조건을 정의·강제할 수 있고, primary key와 foreign key 제약 조건은 정의(강제는 안 함)할 수 있어요(쿼리 최적화에 사용 가능). BigQuery는 그 외 다른 제약 조건의 정의·강제를 지원하지 않아요. 자세한 내용은 Platform constraint support를 참고해 주세요.
Databricks
Databricks는 not null 제약 조건과 하나 이상의 컬럼을 포함하는 조건부 표현식으로 된 추가 check 제약 조건을 지원해요.
Databricks는 트랜잭션을 지원하지 않고 컬럼 스키마와 함께 create or replace table을 사용할 수 없기 때문에, 테이블을 먼저 스키마 없이 생성한 뒤 alter 문으로 각 제약 조건을 추가해요. 이는 다음을 의미해요:
- 컬럼의 이름과 순서는 검사되지만 타입은 검사되지 않아요.
constraints및/또는constraint_check가 실패해도 실패 데이터가 있는 테이블은 여전히 웨어하우스에 존재해요.
Databricks의 제약 조건 지원에 대한 자세한 내용은 이 페이지를 참고해 주세요.
models/constraints_example.sql
{{ config(
materialized = "table"
)}}
select 1 as id, 'My Favorite Customer' as customer_name, cast('2019-01-01' as date) as first_transaction_date
models/schema.yml
models:
- name: dim_customers
config:
contract:
enforced: true
columns:
- name: id
data_type: int
constraints:
- type: not_null
- type: primary_key # not enforced -- will warn & include
- type: check # not supported -- will warn & skip
expression: "id > 0"
data_tests:
- unique # primary_key constraint is not enforced
- name: customer_name
data_type: text
- name: first_transaction_date
data_type: date
제약 조건을 강제하는 예상 DDL:
target/run/.../constraints_example.sql
create or replace table schema_name.my_model using delta as
select 1 as id, 'My Favorite Customer' as customer_name, cast('2019-01-01' as date) as first_transaction_date
그 뒤에 다음 문장이 실행돼요:
alter table schema_name.my_model change column id set not null;
alter table schema_name.my_model add constraint 472394792387497234 check (id > 0);
Custom constraints
dbt에서는 모델에 커스텀 제약 조건을 사용해 테이블을 고급 구성할 수 있어요. 데이터 웨어하우스마다 다른 구문과 기능을 지원해요. 커스텀 제약 조건은 특정 컬럼에 구성을 추가할 수 있게 해 줘요. 예를 들어:
- Create Table As Select(CTAS)를 사용할 때 Snowflake에서 마스킹 정책(masking policies)을 설정하기.
- 다른 데이터 웨어하우스(Databricks, BigQuery 등)는 자체 CTAS 문에서 컬럼에 설정할 수 있는 파라미터 세트가 있어요.
커스텀 제약 조건은 몇 가지 방식으로 구현할 수 있어요:
태그가 있는 커스텀 제약 조건
태그 기반 마스킹 정책을 제약 조건과 함께 구현하는 예시예요. models/constraints_example.yml
models:
- name: my_model
config:
contract:
enforced: true
materialized: table
columns:
- name: id
data_type: int
constraints:
- type: custom
expression: "tag (my_tag = 'my_value')" # A custom SQL expression used to enforce a specific constraint on a column.
이 구문을 쓰려면 모든 컬럼과 타입을 구성해야 해요. create or replace <cols_info_with_masking> mytable as ...를 보내는 유일한 방법이기 때문이에요. 일부 컬럼만 있는 것으로는 불가능해요. 즉 columns와 constraints 필드를 완전히 정의해야 해요. 모든 컬럼이 있는 YAML을 생성하려면 dbt-codegen의 generate_model_yaml을 사용할 수 있어요.
태그가 없는 커스텀 제약 조건
또는 태그 없이 마스킹 정책을 추가할 수도 있어요. models/constraints_example.yml
models:
- name: my_model
config:
contract:
enforced: true
materialized: table
columns:
- name: id
data_type: int
constraints:
- type: custom
expression: "masking policy my_policy"
더 알아보기 (Learn more)
- contract 구성 — 모델 계약 강제
- Model contracts — 계약·제약 조건 거버넌스
- data_tests 속성 — 데이터 테스트로 검증