본문 바로가기
WIKI 기술 지식 베이스

CREATE TABLE

원문 보기 위키 갱신

CREATE TABLE은 컬럼 정의, 데이터 타입, 제약을 갖춘 새 테이블을 데이터베이스에 만들어요.

출처: 문서

본문

컬럼 정의, 데이터 타입, 제약을 담은 새 테이블을 데이터베이스에 만들어요.

문법

CREATE TABLE [IF NOT EXISTS] [schema-name.]table-name (
    column-def [, column-def ...] [, table-constraint [, table-constraint ...]]
) [STRICT];

컬럼 정의

column-name [type-name] [column-constraint ...]

컬럼 제약

PRIMARY KEY [ASC | DESC] [AUTOINCREMENT]
NOT NULL
UNIQUE
DEFAULT expression
CHECK (expression)
REFERENCES foreign-table [(column-name)] [foreign-key-actions]
COLLATE collation-name

테이블 제약

PRIMARY KEY (column-name [, column-name ...])
UNIQUE (column-name [, column-name ...])
CHECK (expression)
FOREIGN KEY (column-name [, column-name ...]) REFERENCES foreign-table [(column-name [, column-name ...])] [foreign-key-actions]

설명

CREATE TABLE은 데이터베이스에 새 테이블을 정의해요. 테이블 이름은 데이터베이스 스키마 안에서 고유해야 해요. 각 테이블은 하나 이상의 컬럼 정의와 선택적인 테이블 수준 제약으로 이루어져요.

매개변수

매개변수 설명
IF NOT EXISTS 같은 이름의 테이블이 이미 있을 때 오류를 막아줘요. 테이블이 있으면 문은 아무 것도 하지 않아요.
schema-name 붙은(attached) 데이터베이스 이름. 생략하면 main 데이터베이스
table-name 만들 테이블 이름
STRICT 테이블에서 엄격한 타입 검사를 켜요. STRICT 테이블을 참고하세요.

컬럼 정의

각 컬럼 정의는 컬럼 이름, 선택적 타입, 0개 이상의 컬럼 제약으로 이루어져요.

타입을 지정하지 않으면 그 컬럼은 어떤 저장 클래스도 받아들여요. 타입 이름을 지정하면 그 이름이 컬럼의 타입 어피니티를 결정하고, 삽입된 값이 어떻게 저장되는지에 영향을 줘요.

CREATE TABLE measurements (
    id INTEGER PRIMARY KEY,
    sensor_name TEXT NOT NULL,
    reading REAL,
    raw_data BLOB,
    notes                    -- no type: accepts any storage class
);

컬럼 제약

PRIMARY KEY

컬럼을 테이블의 기본 키로 지정해요. 테이블은 기본 키를 최대 하나만 가질 수 있어요.

CREATE TABLE users (
    id INTEGER PRIMARY KEY
);

컬럼이 INTEGER PRIMARY KEY로 선언되면 내부 rowid의 별칭이 되고, 삽입된 정수 값이 곧 행 식별자로 쓰여요. 이게 가장 효율적인 기본 키 형태예요.

AUTOINCREMENT

AUTOINCREMENT 키워드는 INTEGER PRIMARY KEY와만 쓸 수 있어요. sqlite_sequence 시스템 테이블에 카운터를 유지해서, 이전에 삭제된 행의 rowid 값을 재사용하지 못하게 막아줘요.

CREATE TABLE events (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    description TEXT
);

AUTOINCREMENT가 없으면 Turso는 행이 삭제된 후 rowid 값을 재사용할 수 있어요. AUTOINCREMENT가 있으면 자동 생성된 rowid가, 행이 삭제됐더라도, 그 테이블의 커밋된 행에 존재했던 어떤 rowid보다 항상 커요.

`AUTOINCREMENT`는 커밋된 고수위선(high-water mark)을 유지하기 때문에 약간의 오버헤드를 더해요. 롤백된 INSERT는 자기가 만든 rowid를 영구적으로 소실하지 않아도 되지만, 동시 트랜잭션이 나중에 만들어진 값을 커밋하면 갭은 여전히 가능해요. **MVCC 아래의 AUTOINCREMENT (`journal_mode=experimental_mvcc`)**: `sqlite_sequence`는 지연 갱신돼요 — WAL이 체크포인트될 때만요. 체크포인트 사이에는 실제 고수위선보다 늦거나, 새로 만든 AUTOINCREMENT 테이블에서는 비어 있을 수 있어요. MVCC 모드에서 현재 값을 복구할 때는 `sqlite_sequence`에 의존하면 **안 돼요**. 대신 내부 시퀀스 백킹 테이블을 직접 조회하세요:
SELECT MAX(value) FROM "__turso_internal_seq___turso_internal_autoincrement_<table_name>";

이 행은 커밋된(그리고 영구 소실된) 모든 nextval에 의해 즉시 갱신돼요. PRAGMA wal_checkpoint(TRUNCATE) 이후에는 sqlite_sequence가 AUTOINCREMENT 테이블마다 한 행으로 합쳐지고, 그때부터는 평범한 SQLite 호환 쿼리로 안전하게 읽을 수 있어요.

또한 MVCC autoincrement는 롤백된 rowid를 되돌리지 않아요 — INSERT 이후의 ROLLBACK은 rowid를 재활용하는 대신 갭을 남겨요. SQLite WAL 모드와 Turso의 WAL 모드는 둘 다 롤백 시 되돌리는데, MVCC 모드만 소실을 유지해요.

NOT NULL

컬럼에 NULL 값이 들어가지 못하게 막아요. 컬럼을 NULL로 만들려는 INSERT나 UPDATE는 제약 오류를 일으켜요.

CREATE TABLE products (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    price REAL NOT NULL
);

UNIQUE

컬럼의 모든 값이 서로 다르게 보장해요. NULL 값은 서로 다른 것으로 취급돼요 (UNIQUE 컬럼에는 NULL이 여러 개 들어갈 수 있어요).

CREATE TABLE accounts (
    id INTEGER PRIMARY KEY,
    email TEXT UNIQUE
);

DEFAULT

INSERT 문이 값을 주지 않았을 때 컬럼에 쓰일 기본값을 지정해요.

CREATE TABLE orders (
    id INTEGER PRIMARY KEY,
    status TEXT DEFAULT 'pending',
    created_at TEXT DEFAULT CURRENT_TIMESTAMP,
    quantity INTEGER DEFAULT 1
);

기본 표현식은 이런 것들이 될 수 있어요:

표현식 예제
리터럴 값 DEFAULT 42, DEFAULT 'active'
NULL DEFAULT NULL
CURRENT_TIME HH:MM:SS 형식의 현재 시각
CURRENT_DATE YYYY-MM-DD 형식의 현재 날짜
CURRENT_TIMESTAMP YYYY-MM-DD HH:MM:SS 형식의 현재 날짜·시각
괄호로 묶인 표현식 DEFAULT (1 + 1)

CHECK

테이블의 모든 행에 대해 참(또는 NULL)으로 평가되어야 하는 불리언 표현식을 정의해요. 표현식은 같은 행의 아무 컬럼이나 참조할 수 있어요.

CREATE TABLE employees (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    age INTEGER CHECK (age >= 18),
    salary REAL CHECK (salary > 0)
);

REFERENCES (외래 키)

컬럼과 다른 테이블의 컬럼 사이에 외래 키 관계를 맺어줘요. 전체 문법은 외래 키 제약을 참고하세요.

CREATE TABLE orders (
    id INTEGER PRIMARY KEY,
    customer_id INTEGER REFERENCES customers(id)
);

COLLATE

컬럼에서 텍스트 비교와 정렬에 쓰일 콜레이션 시퀀스를 지정해요.

콜레이션 설명
BINARY 원시 바이트 값으로 텍스트를 비교해요 (기본값)
NOCASE ASCII 문자에 대한 대소문자 무시 비교
RTRIM BINARY와 같지만 뒤쪽 공백은 무시해요

Turso는 로캘 인식 콜레이션과 연결에 등록된 커스텀 콜레이션도 해석해요. 알 수 없는 콜레이션 이름은 조용히 무시되는 대신 이제 no such collation sequence 오류를 일으켜요.

CREATE TABLE contacts (
    id INTEGER PRIMARY KEY,
    name TEXT COLLATE NOCASE,
    code TEXT COLLATE RTRIM
);

테이블 제약

테이블 제약은 하나 이상의 컬럼에 적용되고, 모든 컬럼 정의 뒤에 지정해요.

복합 PRIMARY KEY

여러 컬럼에 걸친 기본 키를 정의해요. 키 컬럼들의 값 조합은 각각 고유해야 해요.

CREATE TABLE enrollment (
    student_id INTEGER,
    course_id INTEGER,
    semester TEXT,
    grade REAL,
    PRIMARY KEY (student_id, course_id, semester)
);

복합 UNIQUE

지정한 컬럼들에 걸친 값 조합이 각각 고유하도록 보장해요.

CREATE TABLE assignments (
    employee_id INTEGER,
    project_id INTEGER,
    role TEXT,
    UNIQUE (employee_id, project_id)
);

테이블 수준 CHECK

테이블 수준의 CHECK 제약은 여러 컬럼을 참조할 수 있어요.

CREATE TABLE reservations (
    id INTEGER PRIMARY KEY,
    start_date TEXT NOT NULL,
    end_date TEXT NOT NULL,
    CHECK (end_date > start_date)
);

외래 키 제약

외래 키 제약은 테이블 사이의 참조 무결성을 강제해요. 자식 테이블의 값이 부모 테이블의 기존 값과 대응되도록 보장해요.

외래 키 강제는 기본으로 꺼져 있어요. 검사되어야 할 연산을 수행하기 전에 `PRAGMA foreign_keys = ON;`으로 켜세요.

문법

-- Column constraint form (single column)
column-name type REFERENCES parent-table(parent-column)
    [ON DELETE action] [ON UPDATE action]
    [DEFERRABLE INITIALLY DEFERRED]

-- Table constraint form (single or multiple columns)
FOREIGN KEY (column-name [, column-name ...])
    REFERENCES parent-table(parent-column [, parent-column ...])
    [ON DELETE action] [ON UPDATE action]
    [DEFERRABLE INITIALLY DEFERRED]

외래 키 동작

ON DELETE와 ON UPDATE 절은 참조된 부모 행이 삭제되거나 갱신될 때 자식 행에 무슨 일이 일어날지 지정해요.

동작 설명
NO ACTION 외래 키 제약이 위반되면 오류를 일으켜요 (기본값)
RESTRICT NO ACTION과 같지만, 문장 끝이 아니라 즉시 검사돼요
CASCADE 부모 행을 참조하는 모든 자식 행을 삭제하거나 갱신해요
SET NULL 자식 행의 외래 키 컬럼을 NULL로 설정해요
SET DEFAULT 자식 행의 외래 키 컬럼을 기본값으로 설정해요
CREATE TABLE departments (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL
);

CREATE TABLE employees (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    dept_id INTEGER,
    manager_id INTEGER,
    FOREIGN KEY (dept_id) REFERENCES departments(id) ON DELETE CASCADE,
    FOREIGN KEY (manager_id) REFERENCES employees(id) ON DELETE SET NULL
);

STRICT 테이블

STRICT 테이블은 저장 계층에서 타입 검사를 강제해요. STRICT 컬럼에 삽입되는 모든 값은 선언된 컬럼 타입과 일치하거나, 그 타입으로 손실 없이 변환 가능해야 해요.

CREATE TABLE sensor_data (
    id INTEGER PRIMARY KEY,
    reading REAL NOT NULL,
    label TEXT,
    payload BLOB
) STRICT;

허용되는 컬럼 타입

STRICT 테이블은 이 기본 컬럼 타입만 허용해요:

타입 설명
INTEGER 부호 있는 정수
REAL 부동소수점 수
TEXT UTF-8 문자열
BLOB 원시 이진 데이터
ANY 모든 저장 클래스 (이 컬럼의 타입 검사를 끔)

STRICT 테이블의 모든 컬럼은 명시적인 타입 선언을 가져야 해요.

-- This fails: STRICT tables require explicit types on every column
CREATE TABLE bad (
    id INTEGER PRIMARY KEY,
    data  -- error: missing type
) STRICT;
**Turso 확장 기능**: STRICT 테이블은 [CREATE TYPE](/sql-reference/statements/create-type)으로 정의한 커스텀 타입과 [배열 타입](/sql-reference/data-types#array-types)도 지원해요. 커스텀 타입은 사용자 정의 인코딩, 디코딩, 검증, 연산자 오버로딩으로 타입 시스템을 확장해요. 배열 컬럼은 기본 타입에 `[]`를 붙여서 선언해요.
CREATE TABLE events (
    id uuid PRIMARY KEY,
    name varchar(100) NOT NULL,
    event_date date,
    is_active boolean DEFAULT 1,
    tags TEXT[]
) STRICT;

지원되지 않는 기능

다음 CREATE TABLE 기능은 아직 지원되지 않아요:

기능 비고
CREATE TEMPORARY TABLE 임시 테이블은 지원되지 않아요
CREATE TABLE ... AS SELECT 쿼리 결과로 테이블을 만드는 건 지원되지 않아요
컬럼 제약의 ON CONFLICT 절 컬럼 수준 ON CONFLICT 절(예: NOT NULL ON CONFLICT REPLACE)은 지원되지 않아요. 대신 INSERT ... ON CONFLICT 문법을 쓰세요

실험적 기능

다음은 실험적 기능 플래그 뒤에서 사용할 수 있어요:

기능 플래그 비고
WITHOUT ROWID --experimental-without-rowid WITHOUT ROWID 테이블에는 제한이 있어요; 실험적 기능 페이지를 참고하세요
GENERATED ALWAYS AS --experimental-generated-columns 가상 생성(계산) 컬럼만; 저장 컬럼은 아직 지원되지 않아요

예제

제약이 있는 기본 테이블

CREATE TABLE users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    username TEXT NOT NULL UNIQUE,
    email TEXT NOT NULL UNIQUE,
    display_name TEXT,
    bio TEXT DEFAULT '',
    created_at TEXT DEFAULT CURRENT_TIMESTAMP
);

외래 키와 CHECK 제약이 있는 테이블

CREATE TABLE orders (
    id INTEGER PRIMARY KEY,
    customer_id INTEGER NOT NULL,
    total_amount REAL NOT NULL CHECK (total_amount >= 0),
    status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'shipped', 'delivered', 'cancelled')),
    order_date TEXT NOT NULL DEFAULT CURRENT_DATE,
    FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE RESTRICT
);

STRICT 테이블

CREATE TABLE inventory (
    product_id INTEGER PRIMARY KEY,
    sku TEXT NOT NULL UNIQUE,
    quantity INTEGER NOT NULL DEFAULT 0 CHECK (quantity >= 0),
    price REAL NOT NULL CHECK (price > 0),
    category TEXT NOT NULL
) STRICT;

-- This succeeds: '42' is losslessly convertible to INTEGER
INSERT INTO inventory VALUES (1, 'WIDGET-001', '42', 9.99, 'widgets');

-- This fails: 'abc' cannot be converted to INTEGER
INSERT INTO inventory VALUES (2, 'GADGET-001', 'abc', 19.99, 'gadgets');
-- Error: cannot store TEXT value in INTEGER column

외래 키가 있는 복합 기본 키

CREATE TABLE course_enrollment (
    student_id INTEGER NOT NULL,
    course_id INTEGER NOT NULL,
    enrolled_date TEXT DEFAULT CURRENT_DATE,
    grade REAL CHECK (grade >= 0.0 AND grade <= 4.0),
    PRIMARY KEY (student_id, course_id),
    FOREIGN KEY (student_id) REFERENCES students(id) ON DELETE CASCADE,
    FOREIGN KEY (course_id) REFERENCES courses(id) ON DELETE CASCADE
);

IF NOT EXISTS

-- Safe to run multiple times
CREATE TABLE IF NOT EXISTS sessions (
    id INTEGER PRIMARY KEY,
    user_id INTEGER NOT NULL,
    token TEXT NOT NULL UNIQUE,
    expires_at TEXT NOT NULL,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

더 알아보기 (Learn more)