스키마는 바뀐다. SQLite도 (대부분은) 바꿀 수 있게 해준다.
테이블을 한 번 만들었다고 끝이 아니다. 시간이 지나면 이름을 바꾸고 싶거나, 컬럼을 추가·삭제하거나, 구조 자체를 갈아엎고 싶어진다. SQLite는 자주 쓰이는 작업은 DROP TABLE과 ALTER TABLE로 바로 지원하고, 나머지는 공식 문서에 정리된 우회 방법으로 해결한다.
문제는 SQLite의 ALTER TABLE이 Postgres나 MySQL에 비해 훨씬 제한적이라는 점이다. 무엇이 되고 무엇이 안 되는지, 그리고 안 되는 경우에 쓰는 테이블 재생성 패턴을 익히는 것이 사실상 이 주제의 핵심이다.
DROP TABLE: 테이블과 거기 딸린 모든 것을 지운다
drop table은 테이블 자체는 물론이고 그 안의 데이터, 인덱스, 정의된 트리거까지 모두 삭제한다. 되돌리는 기능은 없다:
테이블이 사라졌습니다. 이 상태에서 쿼리를 날리면 no such table: scratch 에러가 발생합니다.
테이블이 실제로 있는지 확신이 서지 않을 때 — 초기 셋업 스크립트에서 흔히 겪는 상황이죠 — IF EXISTS를 붙여 두면 테이블이 없을 때 아무 일도 일어나지 않고 조용히 넘어갑니다:
IF EXISTS 없이 실행하면 두 번째 DROP에서 에러가 납니다. IF EXISTS를 붙이면 두 번 모두 깔끔하게 실행되죠.
외래 키가 걸려 있으면 DROP이 막힙니다
외래 키 제약이 켜져 있고(PRAGMA foreign_keys = ON;) 다른 테이블이 지금 삭제하려는 테이블을 참조하고 있다면, DROP TABLE은 실패합니다:
sqlite> PRAGMA foreign_keys = ON;
sqlite> DROP TABLE users;
Runtime error: FOREIGN KEY constraint failed
방법은 몇 가지가 있습니다. 참조하는 테이블을 먼저 삭제하거나, 참조 중인 행들을 지우거나, 아니면 처음 테이블을 만들 때 외래 키에 ON DELETE CASCADE를 걸어두면 됩니다. SQLite는 참조 무결성을 슬쩍 깨뜨려 주는 법이 없거든요.
ALTER TABLE로 할 수 있는 네 가지
SQLite의 ALTER TABLE이 지원하는 작업은 딱 네 가지입니다:
각 문은 하나의 단일 SQL 문으로 실행됩니다. 처음 두 개는 사실상 비용이 거의 들지 않습니다 — 스키마 정보만 업데이트하면 끝이거든요. ADD COLUMN도 빠릅니다. SQLite는 테이블을 다시 쓰지 않고 새 컬럼 정의만 기록해 두기 때문이죠. 반면 DROP COLUMN은 부담이 큽니다. 해당 컬럼 데이터를 물리적으로 제거하려면 모든 행을 다시 써야 하니까요.
기본값과 함께 컬럼 추가하기 (ADD COLUMN)
기존 테이블에 컬럼을 새로 추가하면, 기본값을 지정하지 않는 한 모든 행에서 해당 컬럼은 NULL로 시작합니다:
기존 행들은 모두 'active' 값으로 채워집니다. 여기서 기본값은 반드시 상수여야 합니다. CURRENT_TIMESTAMP처럼 상수가 아닌 표현식은 ADD COLUMN의 기본값으로 쓸 수 없는데요, 모든 기존 행에 행마다 평가하지 않고 일괄 적용할 수 있는 값이 필요하기 때문입니다.
기본값 없이 NOT NULL 컬럼을 추가하고 싶다면, 먼저 nullable로 컬럼을 추가한 뒤 UPDATE로 값을 채우고, 마지막에 테이블을 재생성해서 제약 조건을 붙이는 방식으로 가야 합니다. 자연스럽게 다음 주제, ALTER TABLE의 한계로 넘어가 봅시다.
sqlite alter table의 한계
PostgreSQL이나 MySQL에서는 되지만 SQLite에서는 안 되는 작업들입니다.
- 컬럼 타입 변경 (
ALTER COLUMN ... TYPE ...) - 기존 컬럼의 기본값을 그 자리에서 변경
- 기존 컬럼에
NOT NULL,CHECK,UNIQUE,PRIMARY KEY추가하거나 제거 - 기존 컬럼에 외래 키(foreign key) 추가
- 컬럼 순서 재정렬
이 중 어느 것을 시도해도 문법 오류가 납니다. SQLite에는 ALTER COLUMN 절 자체가 존재하지 않습니다. 공식 문서가 권하는 해법은 모두 동일합니다. 테이블을 다시 만드는 것이죠.
sqlite 테이블 재생성 패턴
ALTER TABLE로 해결되지 않는 경우에는 원하는 스키마로 새 테이블을 만들고, 데이터를 옮겨 담은 뒤, 기존 테이블을 삭제하고, 새 테이블의 이름을 원래 이름으로 바꾸는 식으로 처리합니다. 중간에 실패하면 안 되니까 트랜잭션으로 감싸서 전부 적용되거나 전부 롤백되도록 합니다.
이제 users.age는 체크 제약 조건이 붙은 정수형이 되었고, email은 NOT NULL이 됐습니다. 데이터도 그대로 따라왔죠.
실전에서 이 작업을 할 때 꼭 챙겨야 할 몇 가지가 있습니다.
- 작업하는 동안 외래 키를 꺼두세요. 다른 테이블이 이 테이블을 참조하고 있다면, 트랜잭션을 시작하기 전에
PRAGMA foreign_keys = OFF;를 실행하고 끝난 뒤에PRAGMA foreign_keys = ON;으로 다시 켜야 합니다. 안 그러면DROP TABLE이 실패합니다. 이 pragma는 트랜잭션 안에서는 바꿀 수 없으니 반드시 바깥에서 설정해야 합니다. - 인덱스와 트리거를 다시 만들어 주세요. 기존 테이블을 드롭하면 거기에 딸린 인덱스와 트리거도 같이 사라집니다. 이름을 바꾼 뒤 새 테이블에 다시 붙여줘야 합니다.
- 뷰도 점검하세요. 해당 테이블을 참조하는 뷰는 저장된 SQL 안에 여전히 옛날 이름을 그대로 들고 있습니다. 변경된 컬럼에 의존하는 뷰가 있다면 다시 만들어 주세요.
이 테이블 재생성 패턴은 코드가 길어서 번거롭지만 확실합니다. Alembic이나 Rails 같은 마이그레이션 도구가 SQLite를 다룰 때 내부적으로 하는 일도 결국 이 방식이에요.
여러 테이블 한 번에 삭제하기
여러 테이블을 한 문장으로 한꺼번에 드롭하는 구문은 없습니다 — 테이블마다 DROP TABLE을 따로 실행해야 합니다. 묶어서 처리하고 싶다면 트랜잭션 안에서 실행하면 됩니다:
트랜잭션으로 묶어두면 세 개의 DROP이 모두 성공하거나 모두 실패하게 됩니다. 외래 키 때문에 중간에 실패할 수 있는 연관 테이블들을 한꺼번에 정리할 때 특히 유용한 방식이죠.
핵심 정리
DROP TABLE은 테이블과 그에 딸린 인덱스, 트리거를 함께 제거합니다. 스크립트를 멱등하게 만들고 싶다면IF EXISTS를 붙이세요.ALTER TABLE로 할 수 있는 일은 딱 네 가지뿐입니다. 테이블 이름 변경, 컬럼명 변경, 컬럼 추가, 컬럼 삭제.- 그 외의 변경 — 타입 수정, 새 제약 조건 추가, 기존 컬럼에 외래 키 걸기 등 — 은 트랜잭션 안에서 테이블을 재생성하는 패턴으로 처리해야 합니다.
- 테이블을 다시 만들 때는 외래 키, 인덱스, 트리거, 뷰를 잊지 마세요. 데이터처럼 자동으로 따라오지 않습니다.
다음 장: 데이터 넣기
지금까지 한 장 내내 테이블 구조와 제약 조건을 다뤘습니다. 이제 그 안에 데이터를 채워 넣을 차례입니다. 다음 장은 INSERT로 시작해서, 여러 행을 한 번에 넣는 방법, 기본값 처리, 그리고 제약 조건과 충돌하는 INSERT를 SQLite가 어떻게 다루는지까지 살펴봅니다.
자주 묻는 질문
SQLite에서 테이블은 어떻게 삭제하나요?
DROP TABLE table_name; 한 줄이면 됩니다. 테이블이 없을 때 에러를 내고 싶지 않다면 IF EXISTS를 붙여 DROP TABLE IF EXISTS users;처럼 쓰세요. 테이블을 삭제하면 그 테이블에 딸린 인덱스와 트리거도 함께 사라집니다. 단, 외래 키 제약(PRAGMA foreign_keys = ON)이 켜져 있고 다른 테이블이 이 테이블을 참조하고 있다면 DROP은 실패합니다.
SQLite의 ALTER TABLE로 할 수 있는 작업은 뭔가요?
딱 네 가지입니다. RENAME TO(테이블 이름 변경), RENAME COLUMN ... TO ...(컬럼 이름 변경), ADD COLUMN(새 컬럼 추가), 그리고 DROP COLUMN(컬럼 삭제, SQLite 3.35부터 지원). 이게 전부예요. 컬럼 타입을 바꾸거나, 기존 컬럼의 기본값을 그 자리에서 수정하거나, 이미 만들어진 컬럼에 제약조건을 추가하는 건 ALTER TABLE로 안 됩니다.
그러면 컬럼의 타입이나 제약조건은 어떻게 바꾸죠?
SQLite는 이걸 직접 지원하지 않기 때문에 흔히 "테이블 재생성 패턴"을 씁니다. 원하는 스키마로 새 테이블을 만든 뒤 INSERT INTO new SELECT ... FROM old로 데이터를 옮기고, DROP TABLE old로 기존 테이블을 지운 다음, ALTER TABLE new RENAME TO old로 이름을 바꿔주는 거죠. 중간에 문제가 생겨도 안전하도록 전체를 트랜잭션(BEGIN ... COMMIT)으로 감싸는 걸 잊지 마세요.