---
title: 타입 어노테이션
description: 필요한 SQL에만 타입을 추가합니다. 어노테이션은 가정을 검사하며 결과를 바꾸지 않습니다.
species: concept
---

# 타입 어노테이션

Typesql은 선택적 타입이 있는 SQL입니다. 이 열은 null이 아니다, 이 숫자는 `decimal(28,2)` 범위에 들어간다, 이 json에는 `plan` 필드가 있다는 식으로 의존하는 가정에 어노테이션을 붙입니다. 검사기는 쿼리 실행 전과 그 아래 스키마가 바뀔 때마다 레이크의 실제 스키마를 기준으로 가정을 검증합니다.

```typesql check=p1
select
  id,
  amount satisfies integer!,
  metadata::json<{plan: string}> ->> 'plan' as plan
from titan.stripe.charges
```

## 보장

어노테이션은 쿼리의 반환 결과를 바꾸지 않습니다. typesql 문서에서 모든 어노테이션을 제거하면 동일하게 실행되는 일반 SQL이 됩니다. 행, 값, 모든 것이 같습니다. 이는 관례가 아닌 구조적 보장입니다. 타입은 검사 후 소거되며 어떤 실행 경로도 타입을 읽을 수조차 없습니다.

따라서 마이그레이션 여부를 결정할 필요가 없습니다. 한 쿼리의 표현식 하나에만 어노테이션을 붙여도 되고 모델 전체에 타입을 지정해도 됩니다. “일반 SQL”과 “완전히 타입이 지정된 SQL” 사이의 모든 단계가 동작하는 프로그램입니다.

## `satisfies`: 표현식 검사

표현식을 사용할 수 있는 곳이면 어디든 `expr satisfies type`을 쓸 수 있습니다.

```typesql check=p1
select
  round(sum(amount) / 100.0, 2) satisfies decimal(38,2) as gross_volume
from titan.stripe.charges
where not _deleted
```

스키마 변경으로 해당 표현식이 더 이상 decimal이 아니게 되면 대시보드가 조용히 잘못된 값을 표시하는 대신 모델 검사가 실패하고 정확한 소스 범위를 가리키는 진단이 나타납니다. 모든 진단에는 안정적인 `TS` 코드, 위치, 수정 방법을 담은 `help:` 줄이 있습니다.

`satisfies`는 형 변환이 아닙니다. `amount::string`은 값을 변환하고 `amount satisfies
string`은 사실을 단언하며 사실이 아니면 검사에 실패합니다. 데이터를 바꾸려면 `::`를, 변화를 감지하려면 `satisfies`를 사용하세요.

## null 제외: `!`와 타입 좁히기

`string!`은 “문자열이며 절대 null이 아님”을 뜻합니다. 검사기는 쿼리 로직이 null 허용성에 미치는 영향을 이해하므로 다음은 통과합니다.

```typesql check=p1
select email satisfies string!
from titan.stripe.customers
where email is not null
```

`where` 절을 제거하면 실패합니다. 스키마에서 `email`은 null을 허용하고 쿼리 어디에서도 null을 제외하지 않았기 때문입니다. 반대도 성립합니다. 입력에서 null이 아닌 열도 `left join`의 외부 쪽에서는 null을 허용하게 되며 검사기는 이것도 추적합니다. 검사는 스키마 선언만이 아니라 쿼리가 실제로 하는 일을 반영합니다.

## 타입이 지정된 CTE 헤더

CTE 헤더에서 열 타입을 선언하면 쿼리 단계 사이의 경계를 검증된 계약으로 만들 수 있습니다.

```typesql check=p1
with monthly(month: date!, gross_volume: decimal(38,2)) as (
  select
    date_trunc('month', created_at)::date,
    round(sum(amount) / 100.0, 2)
  from titan.stripe.charges
  where not _deleted
  group by 1
)
select * from monthly where gross_volume > 250000
```

본문은 헤더에 맞는지 검사되고 `monthly` 이후의 모든 부분은 선언된 타입을 받습니다. 소거할 때 헤더에서 타입이 제거되어 `with monthly(month, gross_volume) as (…)`가 되며 일반 SQL로 실행됩니다.

## 타입이 지정된 json

json 열에 구조를 지정하면 내부 필드에 접근할 때 추측할 필요가 없어집니다.

```typesql check=p1
select
  metadata::json<{plan: string, seats: integer}> as meta,
  meta ->> 'plan' as plan
from titan.stripe.charges
```

이제 `meta ->> 'paln'` 같은 오타는 나중에 대시보드 여러 개를 거쳐 null 열로 발견되는 대신 검사 실패로 나타납니다.

## 편집기에서 사용

쿼리 편집기는 입력하는 동안 같은 검사기를 실행합니다. 표현식에 마우스를 올리면 추론한 타입을 확인할 수 있습니다. 인레이 힌트는 검사기가 이미 아는 타입을 보여주고, 클릭하면 실제 `satisfies` 어노테이션으로 쿼리에 기록됩니다. 클릭할 때마다 점진적으로 타입을 지정할 수 있습니다. 빠른 수정은 진단이 제안하는 것과 동일한 편집을 제공합니다.

## 다음 단계

- [모델 작성](/docs/models/authoring): 모델은 실행할 때마다 레이크를 기준으로 검사되며 `-- lint:` 헤더로 어떤 진단이 실행을 막을지 조정합니다.
- 타입 시스템 페이지는 스칼라, `decimal(p,s)`, 목록과 구조체 타입, 테이블 타입, `_synced_at` 같은 시스템 열의 타입 지정 등 전체 문법을 설명합니다.
