Menu

SQLite CREATE TABLE 사용법: 문법과 제약조건 예제

SQLite에서 테이블을 만드는 방법을 정리했습니다. 컬럼 정의부터 제약조건, IF NOT EXISTS, 임시 테이블, CREATE TABLE AS SELECT까지 실전 예제로 살펴봅니다.

이 페이지에는 실행 가능한 에디터가 있습니다 — 편집하고 실행하면 결과를 바로 볼 수 있습니다.

CREATE TABLE 문으로 스키마 정의하기

SQLite에서 다루는 모든 정형 데이터는 테이블에 담기며, 테이블은 예외 없이 CREATE TABLE 문에서 출발합니다. 테이블 이름을 정하고, 컬럼을 나열한 뒤, 필요하다면 제약 조건을 덧붙이면 됩니다. 이 한 줄을 실행하면 SQLite가 데이터베이스 파일에 스키마를 기록하고, 곧바로 테이블을 사용할 수 있는 상태가 됩니다.

가장 단순한 예시부터 살펴보겠습니다:

컬럼 세 개, PRIMARY KEY 하나, NOT NULL 제약 하나. id는 정수형 PRIMARY KEY라서 SQLite가 알아서 값을 채워 줬고, 두 번째 행의 email은 따로 막아 두지 않았기 때문에 NULL로 들어갔습니다. 테이블의 기본 골격은 결국 이름, 컬럼, 제약 조건 이 세 가지가 전부고, 이 페이지에서 다룰 내용은 모두 이 패턴의 변주일 뿐입니다.

CREATE TABLE 문법 뜯어보기

컬럼 정의는 name TYPE constraint constraint ... 형태입니다. 고전적인 SQLite에서는 타입을 생략해도 동작하지만(자세한 건 type affinity 페이지에서 다룹니다), 코드를 읽는 사람이나 각종 도구가 타입 정보에 의존하는 경우가 많기 때문에 항상 명시해 두는 편이 좋습니다.

몇 가지 짚고 넘어갈 부분이 있습니다.

  • 제약 조건은 공백으로 이어 쓰면 됩니다. 예를 들어 skuNOT NULL UNIQUE라고 적으면 두 규칙이 모두 적용됩니다.
  • in_stockDEFAULT 1을 걸어 두면 INSERT 시 해당 컬럼을 생략할 수 있습니다.
  • SQLite는 불리언 값을 INTEGER로 처리합니다. 별도의 BOOLEAN 타입은 없고, 0이 false, 1이 true입니다.
  • 마지막 컬럼 뒤에 쉼표를 남기면 문법 오류가 납니다. SQL은 이런 부분에서 자바스크립트보다 엄격합니다.

IF NOT EXISTS: 재실행해도 에러 안 나게 만들기

이미 테이블이 존재하는 데이터베이스에 CREATE TABLE을 다시 실행하면 SQLite가 에러를 뱉습니다.

エラー: テーブル users はすでに存在します

처음 한 번이야 괜찮지만, 백 번쯤 마주치면 슬슬 짜증이 납니다. 이미 테이블이 있을 때는 IF NOT EXISTS를 붙여 해당 구문을 그냥 무시하도록 만들 수 있습니다:

두 번째 CREATE TABLE은 아무 일도 하지 않습니다. 에러도 없고, 스키마가 바뀌지도 않죠. 시작 시 실행되는 코드, 마이그레이션 스크립트, 그 외에 같은 SQL이 여러 번 실행될 수 있는 곳이라면 바로 이 형태를 써야 합니다.

다만 한 가지 주의할 점이 있습니다. IF NOT EXISTS는 _이름_만 확인합니다. 같은 이름의 테이블이 이미 있는데 컬럼 구성이 다르더라도, SQLite는 그대로 둡니다. 스키마를 알아서 "고쳐주거나" "업그레이드해주지" 않아요. 그건 마이그레이션이 할 일입니다.

제약 조건: 스키마와 함께 따라다니는 규칙

제약 조건(constraint)은 검증 로직을 데이터베이스 자체로 밀어넣는 방법입니다. 실무에서 자주 쓰게 될 네 가지는 다음과 같습니다.

  • PRIMARY KEY — 행을 고유하게 식별하는 제약 조건입니다. 자세한 내용은 기본 키 문서에서 다룹니다.
  • NOT NULL — 해당 컬럼에 반드시 값이 있어야 합니다.
  • DEFAULT valueINSERT 시 해당 컬럼을 생략하면 적용되는 기본값입니다. 리터럴 값뿐 아니라 datetime('now') 같은 표현식도 사용할 수 있습니다.
  • CHECK (expr) — 모든 행에 대해 참이어야 합니다.
  • UNIQUE (col, col) — 컬럼 조합의 유일성을 보장하는 테이블 수준 제약 조건입니다.

제약 조건은 모든 INSERTUPDATE마다 검사됩니다. 조건을 위반한 행은 거부되고 해당 구문은 실패합니다. 잘못된 데이터는 애플리케이션 곳곳으로 퍼진 뒤에 잡는 것보다, 데이터베이스 단계에서 걸러내는 편이 훨씬 비용이 적게 듭니다.

외래 키 (Foreign Key)

외래 키는 "이 컬럼이 다른 테이블의 어떤 행을 가리킨다"는 뜻입니다. 덕분에 데이터의 일관성이 유지되죠. 존재하지 않는 사용자를 참조할 수 없고, 옵션을 적절히 설정하면 사용자를 삭제할 때 그 사용자의 주문 데이터까지 자동으로 함께 삭제(cascade)되도록 만들 수 있습니다.

SQLite에서 꼭 기억해 둬야 할 함정 하나: 외래 키 제약은 기본적으로 꺼져 있다. 제약을 검사하고 싶다면 연결할 때마다 PRAGMA foreign_keys = ON을 실행해 줘야 한다. 다행히 대부분의 드라이버는 이걸 알아서 켜주거나 설정 옵션으로 제공하지만, 그렇지 않다면 연결 직후에 직접 이 pragma를 실행하자.

여기서 쓴 ON DELETE CASCADE는 사용자를 삭제하면 그 사용자의 게시글도 함께 자동 삭제된다는 뜻이다. 이 외에도 SET NULL, RESTRICT, 그리고 기본값인 NO ACTION이 있는데, NO ACTION은 자식 행이 남아 있으면 삭제 자체를 거부한다.

CREATE TABLE AS SELECT로 테이블 만들기

쿼리 결과를 통째로 새 테이블에 담아 두고 싶을 때가 있다. 스냅샷을 떠놓거나, 백업용으로 쓰거나, 분석 중에 임시로 데이터를 굴려볼 때 같은 경우다. 이럴 때 쓰는 게 CREATE TABLE ... AS SELECT 문법이다:

새로 만들어진 테이블에는 컬럼 이름과 타입(가능한 범위 내에서), 그리고 데이터가 그대로 복사됩니다. 그런데 복사되지 않는 것이 더 중요합니다. 기본 키(primary key), NOT NULL 제약, 인덱스, 외래 키(foreign key)는 전부 빠집니다. 말 그대로 평평한 스냅샷이라고 보면 됩니다. 실제 스키마를 복제하는 용도가 아니라, 임시 작업의 출발점 정도로만 활용하세요.

데이터는 빼고 구조만 가져오고 싶다면 WHERE 0을 붙이면 됩니다.

컬럼 구조만 똑같고 비어 있는 테이블이 만들어지죠. 나중에 데이터를 채워 넣을 아카이브용 테이블을 만들 때 유용합니다.

SQLite 임시 테이블 만들기

TEMP 테이블은 현재 데이터베이스 연결이 살아 있는 동안에만 존재합니다. 연결을 끊으면 테이블도 같이 사라지기 때문에 따로 정리할 것도 없고, 스키마가 남아서 지저분해질 일도 없어요:

좋은 활용 예: 다단계 쿼리에서 중간 행을 잠시 모아두기, CTE로 처리하기엔 너무 지저분한 중간 결과 담아두기, 장시간 유지되는 세션에서 커넥션별 데이터를 따로 격리하기 등이 있습니다. CREATE TEMP TABLECREATE TEMPORARY TABLE은 완전히 동일한 의미입니다.

AS SELECT와 함께 쓰는 것도 가능합니다. CREATE TEMP TABLE snapshot AS SELECT ... 같은 형태는 분석 도중에 특정 시점의 결과 집합을 그대로 박제해두고 싶을 때 자주 쓰는 패턴입니다.

이름 따옴표로 감싸기

대부분의 경우 컬럼명과 테이블명은 따옴표 없이 그대로 쓰면 됩니다. 하지만 예약어를 이름으로 쓰거나 이름에 공백이 들어가야 한다면, 큰따옴표(SQL 표준)나 백틱(MySQL에서 온 문법인데 SQLite도 받아줍니다)으로 감싸야 합니다:

동작은 하지만, 그 테이블을 참조할 때마다 매번 불편함이 따라옵니다. orders, selection, user_id처럼 평범한 이름을 쓰고 따옴표는 아예 빼는 편이 낫습니다.

실전 예제

지금까지 살펴본 내용을 한데 모아 보겠습니다. 할 일 관리 앱을 위한 작은 스키마를 만들고, 앱이 실행될 때마다 안전하게 돌릴 수 있도록 IF NOT EXISTS를 붙였습니다:

이 정도면 실전에 바로 투입해도 손색없는 스키마다. 멱등성 있는 테이블 생성, 외래 키 강제, done 값을 정직하게 지켜주는 CHECK, 합리적인 기본값, 그리고 알아서 채워지는 타임스탬프까지 모두 갖췄다.

다음 주제: 데이터 타입

CREATE TABLE을 쓸 때 INTEGER, TEXT, REAL 같은 타입을 적을 수 있지만, 정작 SQLite는 이 값들을 어떻게 저장할지에 대해 꽤나 느슨하기로 유명하다. 다음 페이지에서는 SQLite가 실제로 사용하는 다섯 가지 저장 클래스(storage class)를 살펴보고, 왜 우리가 적은 타입과 실제로 저장되는 타입이 항상 일치하지는 않는지 알아본다.

자주 묻는 질문

SQLite에서 테이블은 어떻게 만드나요?

기본 형식은 CREATE TABLE 이름 (컬럼1 타입, 컬럼2 타입, ...) 입니다. 각 컬럼에는 이름과 타입(생략 가능)을 지정하고, 필요하면 PRIMARY KEY, NOT NULL, DEFAULT 같은 제약조건을 붙일 수 있어요. 문장이 실행되는 즉시 테이블이 생성되고 데이터베이스 파일에 그대로 저장됩니다.

CREATE TABLE에서 IF NOT EXISTS는 어떤 역할을 하나요?

CREATE TABLE IF NOT EXISTS 이름 (...)을 쓰면 같은 이름의 테이블이 없을 때만 새로 만듭니다. 이 옵션 없이 같은 스크립트를 다시 돌리면 table already exists 에러가 나기 때문에, 마이그레이션 스크립트나 앱 시작 시 초기화 코드에서는 거의 필수적으로 붙여줍니다.

SELECT 결과로 테이블을 만들 수 있나요?

네, CREATE TABLE 새이름 AS SELECT ... 형태로 쿼리 결과를 그대로 새 테이블로 만들 수 있습니다. 다만 컬럼 이름과 데이터만 복사되고, 원본 테이블의 제약조건이나 PRIMARY KEY, 인덱스는 함께 따라오지 않는다는 점에 주의하세요. 스냅샷이나 임시 작업용 테이블에는 좋지만, 실제 스키마를 대체하는 용도로는 적합하지 않습니다.

임시 테이블(TEMP TABLE)과 일반 테이블의 차이는 무엇인가요?

CREATE TEMP TABLE(또는 CREATE TEMPORARY TABLE)로 만든 테이블은 현재 연결(Connection)에서만 유효하고, 연결이 끊기면 자동으로 사라집니다. 반면 일반 테이블은 데이터베이스 파일에 영구적으로 저장됩니다. 임시 테이블은 스키마를 어지럽히지 않으면서 중간 결과를 가공할 때 유용해요.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기