3부 · 데이터 넣고 고치고 지우기 (DML)
← 2부 관계와 외래키 · 목차 · 다음: 4부 SELECT 기초 →
그릇(테이블)이 완성됐으니 이제 음식(데이터)을 담습니다. 행을 추가(INSERT)·수정(UPDATE)·삭제(DELETE)하는 세 동작을 배우고, 잘못된 데이터가 애초에 못 들어오게 막는 제약조건(constraint) 을 정리합니다. 이 세 동작과 2부의 CREATE/DROP을 합쳐 데이터를 바꾸는 모든 명령을, 다음 부의 SELECT(읽기)와 구별해 부릅니다.
flowchart LR
A[테이블 구조 정하기] --> C["행 추가 · 수정 · 삭제<br/>INSERT · UPDATE · DELETE"]
C --> E[묻기]
A -.규칙 부여.-> H["제약조건으로 데이터 보호"]
classDef here fill:#d5e8f2,stroke:#3498db,color:#000,stroke-width:3px
classDef ops fill:#fdf0d5,stroke:#e67e22,color:#000,stroke-width:3px
classDef dim fill:#eee,stroke:#bbb,color:#888
class C here
class H ops
class A,E dim
용어 한 줄. 데이터를 바꾸는 명령(INSERT/UPDATE/DELETE)을 묶어 DML(Data Manipulation Language, 데이터 조작어), 테이블 구조를 다루는 명령(CREATE/ALTER/DROP)을 DDL(Data Definition Language, 데이터 정의어)이라고 합니다. 외울 필요는 없고, 둘이 구분된다는 것만 알면 됩니다.
3.1 서점에 데이터 채우기 (공용 실습 데이터)
이 데이터셋은 이후 모든 부에서 계속 씁니다. 2부에서 만든 세 테이블이 있는 상태에서 아래를 실행하세요. (다시 채우고 싶으면 각 테이블을 비운 뒤 재실행하면 됩니다 — 비우는 법은 3.4에서.)
PRAGMA foreign_keys = ON; -- 고객 4명 INSERT INTO customers (name, email, joined) VALUES ('김하나', 'hana@mail.com', '2025-11-02'), ('이두리', 'duri@mail.com', '2025-12-15'), ('박세찬', 'sechan@mail.com', '2026-01-08'), ('최넷별', 'netbyeol@mail.com', '2026-02-20'); -- 책 6권 (고서 필사본은 저자·출간일이 NULL) INSERT INTO books (title, author, price, stock, published) VALUES ('모비딕', '허먼 멜빌', 15000, 4, '1851-10-18'), ('데미안', '헤르만 헤세', 12000, 10, '1919-01-01'), ('1984', '조지 오웰', 11000, 0, '1949-06-08'), ('노인과 바다', '어니스트 헤밍웨이', 9000, 7, '1952-09-01'), ('이방인', '알베르 카뮈', 10000, 5, '1942-01-01'), ('고서 필사본', NULL, 50000, 1, NULL); -- 주문 8건 INSERT INTO orders (customer_id, book_id, order_date, quantity) VALUES (1, 1, '2026-01-03', 1), (2, 1, '2026-01-05', 1), (1, 2, '2026-01-05', 2), (3, 4, '2026-02-01', 1), (1, 5, '2026-02-10', 1), (2, 2, '2026-02-11', 3), (3, 1, '2026-03-02', 1), (4, 4, '2026-03-15', 2);
잘 들어갔는지 확인:
SELECT COUNT(*) FROM customers; -- 4 SELECT COUNT(*) FROM books; -- 6 SELECT COUNT(*) FROM orders; -- 8
순서가 중요합니다. 외래키를 켜 두면, 주문(orders)을 넣기 전에 그 주문이 가리키는 고객과 책이 먼저 존재해야 합니다. 그래서customers→books→orders순으로 넣습니다. 반대로 지울 때는 자식(orders)부터 지웁니다.
3.2 INSERT: 행 추가하기
기본 형태는 1부에서 봤습니다. 핵심 규칙 몇 가지를 정리합니다.
열을 명시하는 형태 (권장)
INSERT INTO books (title, author, price) VALUES ('총, 균, 쇠', '재레드 다이아몬드', 18000);
(title, author, price)처럼 어느 칸에 넣을지 이름을 적는 방식입니다. 적지 않은 칸(id, stock, published)은 자동값(자동증가) 또는 기본값(stock은 DEFAULT 0)으로 채워집니다. 칸 이름을 명시하면 테이블 구조가 나중에 바뀌어도 덜 깨져서, 실무에서 이 형태를 권합니다.
열을 생략하는 형태 (간편하지만 주의)
-- 모든 칸을 테이블에 정의된 순서대로 빠짐없이 적어야 함 INSERT INTO books VALUES (NULL, '총, 균, 쇠', '재레드 다이아몬드', 18000, 0, NULL);
칸 이름을 생략하면 정의된 순서 그대로 모든 값을 줘야 합니다. id에 NULL을 주면 자동증가가 동작합니다. 짧지만 순서를 틀리기 쉬워, 연습용이 아니면 위의 명시 형태를 쓰세요.
한 번에 여러 행
INSERT INTO books (title, author, price) VALUES ('파과', '구병모', 14000), ('소년이 온다', '한강', 13000);
VALUES 뒤에 괄호를 쉼표로 이어 붙이면 여러 행이 한 번에 들어갑니다. 한 줄씩 여러 번 INSERT하는 것보다 빠르고 깔끔합니다.
3.3 UPDATE: 기존 행 수정하기
가격이 바뀌거나 재고가 변할 때 씁니다. 형태는 "어느 테이블의 / 어떤 칸을 / 무엇으로 / 어떤 행에 대해"입니다.
-- 1984의 가격을 12000으로 올린다 UPDATE books SET price = 12000 WHERE title = '1984';
SET price = 12000이 바꿀 내용, WHERE title = '1984'가 바꿀 대상입니다. 여러 칸을 동시에:
-- 1984를 재입고: 가격 인상 + 재고 보충 UPDATE books SET price = 12000, stock = 8 WHERE title = '1984';
계산식도 됩니다. 모든 책을 10% 인상:
UPDATE books SET price = price * 1.1; -- WHERE가 없으면 '모든 행'!
⚠️ WHERE 없는 UPDATE의 공포
UPDATE와 DELETE에서 WHERE를 빠뜨리면 테이블의 모든 행이 대상이 됩니다. 위처럼 "전체 인상"이 의도라면 괜찮지만, 한 권만 고치려다 WHERE를 깜빡하면 전 재고가 한 값으로 덮어쓰입니다.
-- ❌ 실수: 모비딕만 고치려 했는데 WHERE를 빠뜨림 → 모든 책 재고가 4로! UPDATE books SET stock = 4;
flowchart TB
a["UPDATE books SET stock = 4;"] --> b["WHERE 없음<br/>→ 모든 행에 적용"]
b --> c["6권 전부 stock = 4 로 덮어씀 😱"]
fix["안전 습관"] --> d["먼저 SELECT 로 대상 확인<br/>SELECT * FROM books WHERE title='모비딕';"]
d --> e["같은 WHERE로 UPDATE 실행"]
classDef bad fill:#f5c6cb,stroke:#c0392b,color:#000
classDef good fill:#d5f2e0,stroke:#27ae60,color:#000
class a,b,c bad
class fix,d,e good
안전 습관: UPDATE/DELETE를 쓰기 전, 같은 WHERE로 먼저 SELECT를 돌려 "정확히 이 행들만 맞나"를 눈으로 확인하세요. 맞으면 그 WHERE를 그대로 UPDATE/DELETE에 붙입니다. 진짜 사고를 막아 주는 가장 값싼 습관입니다. (6부의 트랜잭션은 한 발 더 나아간 안전장치입니다.)
3.4 DELETE: 행 삭제하기
-- 특정 주문 한 건 취소 DELETE FROM orders WHERE id = 8; -- 재고가 0인 책 모두 삭제 DELETE FROM books WHERE stock = 0;
UPDATE와 똑같이 WHERE가 대상을 정합니다. WHERE 없는 DELETE FROM orders;는 주문을 전부 지웁니다(테이블 구조는 남고 내용만 비워짐). 데이터를 비우고 다시 채우고 싶을 때 일부러 쓰기도 합니다.
-- 실습 데이터를 처음부터 다시 넣고 싶을 때 (자식 → 부모 순서로 비우기) DELETE FROM orders; DELETE FROM books; DELETE FROM customers;
외래키와 삭제. 외래키가 켜진 상태에서customers의 김하나(id=1)를 지우려 하면, 그를 가리키는 주문이 남아 있어 거부됩니다(참조 무결성). 김하나의 주문을 먼저 지우거나, 외래키 선언에ON DELETE CASCADE(부모를 지우면 자식도 함께 삭제)를 걸어 둘 수 있습니다. 후자는 편하지만 "고객 한 명 지웠더니 주문 기록이 통째로 사라지는" 위험이 있어, 입문에선 기본 동작(막기)을 권합니다.
DELETE vs DROP
헷갈리기 쉬운 둘:
| 명령 | 지우는 것 | 비유 |
|---|---|---|
DELETE FROM books; | 테이블 안의 행(데이터) | 서랍 안 물건을 비움. 서랍은 남음 |
DROP TABLE books; | 테이블 자체(구조+데이터) | 서랍을 통째로 버림 |
3.5 제약조건: 나쁜 데이터를 문 앞에서 막기
지금까지 흩어져 나온 제약조건(constraint) 을 한자리에 모읍니다. 제약은 CREATE TABLE에서 열에 거는 규칙으로, 잘못된 데이터가 들어오는 것을 데이터베이스가 거부하게 합니다. 애플리케이션 코드에서 일일이 검사하는 것보다 훨씬 믿음직합니다 — 어떤 경로로 들어와도 데이터베이스가 마지막 문지기 역할을 하니까요.
| 제약 | 뜻 | 예 |
|---|---|---|
NOT NULL | 빈 값(NULL) 금지 | title TEXT NOT NULL |
UNIQUE | 같은 값 중복 금지 | email TEXT UNIQUE |
PRIMARY KEY | 유일 + 비어있지않음 (행의 대표) | id INTEGER PRIMARY KEY |
DEFAULT v | 값을 안 주면 v로 채움 | stock INTEGER DEFAULT 0 |
CHECK (조건) | 조건을 만족하는 값만 허용 | price INTEGER CHECK (price >= 0) |
FOREIGN KEY | 다른 테이블에 실제 존재하는 값만 | ... REFERENCES customers(id) |
새로 보는 건 CHECK입니다. "가격은 음수일 수 없다", "수량은 1 이상" 같은 업무 규칙을 데이터베이스에 직접 새겨 둘 수 있습니다.
-- 제약을 강화한 books (참고용 — 다시 만들 필요는 없음) CREATE TABLE books ( id INTEGER PRIMARY KEY, title TEXT NOT NULL, author TEXT, price INTEGER NOT NULL CHECK (price >= 0), -- 음수 가격 금지 stock INTEGER NOT NULL DEFAULT 0 CHECK (stock >= 0) );
이제 음수 가격을 넣으려 하면:
INSERT INTO books (title, price) VALUES ('이상한 책', -5000); -- 거부됨: CHECK constraint failed
flowchart LR
bad["price = -5000 입력 시도"] --> gate{"CHECK price >= 0 통과?"}
gate -->|아니오| reject["거부: CHECK constraint failed"]
gate -->|예| accept["저장됨"]
classDef q fill:#fff3a0,stroke:#f1c40f,color:#000
classDef bad fill:#f5c6cb,stroke:#c0392b,color:#000
classDef good fill:#d5f2e0,stroke:#27ae60,color:#000
class gate q
class bad,reject bad
class accept good
제약은 어디까지? 제약은 "데이터의 형태·관계가 항상 지켜야 할 규칙"에 씁니다(가격은 음수 불가, 이메일은 유일 등). 반면 "VIP 고객은 할인" 같은 자주 바뀌는 업무 로직은 제약보다 애플리케이션에서 다루는 게 유연합니다. 너무 많은 CHECK는 데이터를 못 넣게 만들어 답답할 수 있으니, 데이터를 더럽힐 만한 핵심 규칙부터 거세요.
3.6 정리
- 데이터를 바꾸는 세 동작:
INSERT(추가),UPDATE(수정),DELETE(삭제). 구조를 바꾸는CREATE/DROP과 구별된다. INSERT는 열을 명시하는 형태를 권한다.VALUES를 쉼표로 이어 여러 행을 한 번에 넣을 수 있다.UPDATE/DELETE는WHERE가 대상을 정한다.WHERE를 빠뜨리면 모든 행이 대상이 되니, 먼저 같은WHERE로SELECT해 확인하는 습관을 들인다.DELETE는 행을 비우고(테이블은 남음),DROP은 테이블을 통째로 없앤다.- 제약조건(
NOT NULL·UNIQUE·CHECK·DEFAULT·FOREIGN KEY)은 잘못된 데이터를 문 앞에서 막는 데이터베이스의 문지기다. 핵심 규칙부터 건다.
이제 서점에 데이터가 가득 찼습니다. 다음 부부터는 이 데이터에 질문을 던지는 본격적인 여정입니다. SELECT로 원하는 책과 고객을 골라내는 법부터 시작합니다.
직접 해 보기
3.1의 공용 데이터를 채운 상태에서 풀어 보세요.
- 좋아하는 책 두 권을 한 번의
INSERT로books에 추가하세요. - '데미안'의 재고를 현재보다 5 늘리세요. (힌트:
SET stock = stock + 5 WHERE ...) - 가격이 50000인 '고서 필사본'을 30000으로 내리세요. 실행 전, 같은
WHERE로SELECT를 먼저 돌려 대상이 한 권뿐인지 확인하세요. quantity가 2 이상인 주문을 모두 찾아SELECT로 확인한 뒤, 그중id가 가장 큰 주문 하나만DELETE하세요.- (생각해 보기)
orders.quantity에CHECK (quantity >= 1)을 걸면 어떤 잘못된 데이터를 막을 수 있을까요?
← 2부 관계와 외래키 · 목차 · 다음: 4부 SELECT 기초 →