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에서.)

SQL
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);

잘 들어갔는지 확인:

SQL
SELECT COUNT(*) FROM customers;  -- 4
SELECT COUNT(*) FROM books;      -- 6
SELECT COUNT(*) FROM orders;     -- 8
순서가 중요합니다. 외래키를 켜 두면, 주문(orders)을 넣기 전에 그 주문이 가리키는 고객과 책이 먼저 존재해야 합니다. 그래서 customersbooksorders 순으로 넣습니다. 반대로 지울 때는 자식(orders)부터 지웁니다.

3.2 INSERT: 행 추가하기

기본 형태는 1부에서 봤습니다. 핵심 규칙 몇 가지를 정리합니다.

열을 명시하는 형태 (권장)

SQL
INSERT INTO books (title, author, price) VALUES ('총, 균, 쇠', '재레드 다이아몬드', 18000);

(title, author, price)처럼 어느 칸에 넣을지 이름을 적는 방식입니다. 적지 않은 칸(id, stock, published)은 자동값(자동증가) 또는 기본값(stockDEFAULT 0)으로 채워집니다. 칸 이름을 명시하면 테이블 구조가 나중에 바뀌어도 덜 깨져서, 실무에서 이 형태를 권합니다.

열을 생략하는 형태 (간편하지만 주의)

SQL
-- 모든 칸을 테이블에 정의된 순서대로 빠짐없이 적어야 함
INSERT INTO books VALUES (NULL, '총, 균, 쇠', '재레드 다이아몬드', 18000, 0, NULL);

칸 이름을 생략하면 정의된 순서 그대로 모든 값을 줘야 합니다. idNULL을 주면 자동증가가 동작합니다. 짧지만 순서를 틀리기 쉬워, 연습용이 아니면 위의 명시 형태를 쓰세요.

한 번에 여러 행

SQL
INSERT INTO books (title, author, price) VALUES
  ('파과', '구병모', 14000),
  ('소년이 온다', '한강', 13000);

VALUES 뒤에 괄호를 쉼표로 이어 붙이면 여러 행이 한 번에 들어갑니다. 한 줄씩 여러 번 INSERT하는 것보다 빠르고 깔끔합니다.

3.3 UPDATE: 기존 행 수정하기

가격이 바뀌거나 재고가 변할 때 씁니다. 형태는 "어느 테이블의 / 어떤 칸을 / 무엇으로 / 어떤 행에 대해"입니다.

SQL
-- 1984의 가격을 12000으로 올린다
UPDATE books SET price = 12000 WHERE title = '1984';

SET price = 12000이 바꿀 내용, WHERE title = '1984'가 바꿀 대상입니다. 여러 칸을 동시에:

SQL
-- 1984를 재입고: 가격 인상 + 재고 보충
UPDATE books SET price = 12000, stock = 8 WHERE title = '1984';

계산식도 됩니다. 모든 책을 10% 인상:

SQL
UPDATE books SET price = price * 1.1;   -- WHERE가 없으면 '모든 행'!

⚠️ WHERE 없는 UPDATE의 공포

UPDATEDELETE에서 WHERE를 빠뜨리면 테이블의 모든 행이 대상이 됩니다. 위처럼 "전체 인상"이 의도라면 괜찮지만, 한 권만 고치려다 WHERE를 깜빡하면 전 재고가 한 값으로 덮어쓰입니다.

SQL
-- ❌ 실수: 모비딕만 고치려 했는데 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: 행 삭제하기

SQL
-- 특정 주문 한 건 취소
DELETE FROM orders WHERE id = 8;

-- 재고가 0인 책 모두 삭제
DELETE FROM books WHERE stock = 0;

UPDATE와 똑같이 WHERE가 대상을 정합니다. WHERE 없는 DELETE FROM orders;는 주문을 전부 지웁니다(테이블 구조는 남고 내용만 비워짐). 데이터를 비우고 다시 채우고 싶을 때 일부러 쓰기도 합니다.

SQL
-- 실습 데이터를 처음부터 다시 넣고 싶을 때 (자식 → 부모 순서로 비우기)
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 이상" 같은 업무 규칙을 데이터베이스에 직접 새겨 둘 수 있습니다.

SQL
-- 제약을 강화한 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)
);

이제 음수 가격을 넣으려 하면:

SQL
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/DELETEWHERE가 대상을 정한다. WHERE를 빠뜨리면 모든 행이 대상이 되니, 먼저 같은 WHERESELECT해 확인하는 습관을 들인다.
  • DELETE는 행을 비우고(테이블은 남음), DROP은 테이블을 통째로 없앤다.
  • 제약조건(NOT NULL·UNIQUE·CHECK·DEFAULT·FOREIGN KEY)은 잘못된 데이터를 문 앞에서 막는 데이터베이스의 문지기다. 핵심 규칙부터 건다.

이제 서점에 데이터가 가득 찼습니다. 다음 부부터는 이 데이터에 질문을 던지는 본격적인 여정입니다. SELECT로 원하는 책과 고객을 골라내는 법부터 시작합니다.

직접 해 보기

3.1의 공용 데이터를 채운 상태에서 풀어 보세요.

  1. 좋아하는 책 두 권을 한 번의 INSERTbooks에 추가하세요.
  2. '데미안'의 재고를 현재보다 5 늘리세요. (힌트: SET stock = stock + 5 WHERE ...)
  3. 가격이 50000인 '고서 필사본'을 30000으로 내리세요. 실행 전, 같은 WHERESELECT를 먼저 돌려 대상이 한 권뿐인지 확인하세요.
  4. quantity가 2 이상인 주문을 모두 찾아 SELECT로 확인한 뒤, 그중 id가 가장 큰 주문 하나만 DELETE하세요.
  5. (생각해 보기) orders.quantityCHECK (quantity >= 1)을 걸면 어떤 잘못된 데이터를 막을 수 있을까요?
← 2부 관계와 외래키 · 목차 · 다음: 4부 SELECT 기초 →