SQL 기초 — 데이터를 다루는 언어¶
설계 다음은 질문이다¶
5장에서 데이터베이스를 어떻게 설계하는지 배웠습니다.
이제 그 데이터베이스에 실제로 데이터를 넣고, 꺼내고, 수정하고, 삭제해야 합니다.
그 수단이 바로 SQL입니다.
SQL이란?¶
SQL = Structured Query Language
데이터베이스에게 명령을 내리는 언어입니다.
영어 문장처럼 읽히도록 설계되어 있습니다.
SELECT name FROM students WHERE age = 17;
-- "students에서, 나이가 17인 행의 name을 가져와라"
SQL은 어느 관계형 데이터베이스에서나 거의 동일하게 동작합니다.
MySQL, PostgreSQL, SQLite — 방언(dialect)이 약간 다를 뿐 핵심은 같습니다.
SQL의 분류¶
| 분류 | 이름 | 역할 | 대표 명령어 |
|---|---|---|---|
| DDL | Data Definition Language | 구조 정의 | CREATE, DROP, ALTER |
| DML | Data Manipulation Language | 데이터 조작 | SELECT, INSERT, UPDATE, DELETE |
| DCL | Data Control Language | 권한 제어 | GRANT, REVOKE |
실무에서 가장 많이 쓰는 것은 DML입니다.
이 장에서는 DML을 집중적으로 다룹니다.
테이블 만들기 — CREATE TABLE¶
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE todos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
is_done INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (user_id) REFERENCES users(id)
);
이 장에서는 SQLite 문법을 기준으로 합니다.
MySQL/PostgreSQL은 타입 이름이 약간 다릅니다 (VARCHAR, SERIAL, BOOLEAN 등).
SELECT — 데이터 조회¶
SELECT 컬럼명, ...
FROM 테이블명
WHERE 조건
ORDER BY 정렬기준
LIMIT 개수;
전체 조회¶
SELECT * FROM users;
-- *는 모든 컬럼을 의미
특정 컬럼만 조회¶
SELECT id, name FROM users;
WHERE — 조건 필터링¶
SELECT * FROM todos WHERE is_done = 0;
-- 완료되지 않은 할 일만
SELECT * FROM todos WHERE user_id = 1 AND is_done = 1;
-- user_id가 1이고 완료된 것만
자주 쓰는 조건 연산자¶
| 연산자 | 의미 | 예시 |
|---|---|---|
= |
같다 | WHERE id = 1 |
!= 또는 <> |
다르다 | WHERE name != '지민' |
>, <, >=, <= |
대소 비교 | WHERE age >= 18 |
AND |
둘 다 | WHERE age >= 18 AND city = '서울' |
OR |
하나라도 | WHERE city = '서울' OR city = '부산' |
NOT |
반대 | WHERE NOT is_done = 1 |
BETWEEN |
범위 | WHERE age BETWEEN 16 AND 19 |
LIKE |
패턴 매칭 | WHERE email LIKE '%@gmail.com' |
IN |
목록 중 하나 | WHERE city IN ('서울', '부산', '대구') |
IS NULL |
값 없음 | WHERE deleted_at IS NULL |
LIKE 패턴¶
WHERE name LIKE '김%' -- '김'으로 시작하는 모든 이름
WHERE name LIKE '%민' -- '민'으로 끝나는 모든 이름
WHERE name LIKE '%지%' -- '지'가 들어있는 모든 이름
ORDER BY — 정렬¶
SELECT * FROM todos ORDER BY created_at DESC;
-- 최신순 (내림차순)
SELECT * FROM todos ORDER BY created_at ASC;
-- 오래된 순 (오름차순, 기본값)
SELECT * FROM todos ORDER BY user_id ASC, created_at DESC;
-- 먼저 user_id로 오름차순, 같으면 created_at 기준 최신순
LIMIT — 개수 제한¶
SELECT * FROM todos LIMIT 10;
-- 최대 10개만
SELECT * FROM todos LIMIT 10 OFFSET 20;
-- 21번째부터 10개 (페이지네이션)
집계 함수 — 통계 구하기¶
SELECT COUNT(*) FROM todos; -- 전체 개수
SELECT COUNT(*) FROM todos WHERE is_done = 1; -- 완료된 개수
SELECT AVG(age) FROM users; -- 나이 평균
SELECT MAX(age) FROM users; -- 최고 나이
SELECT MIN(age) FROM users; -- 최저 나이
SELECT SUM(price) FROM order_items; -- 가격 합계
GROUP BY — 그룹별 집계¶
SELECT user_id, COUNT(*) AS todo_count
FROM todos
GROUP BY user_id;
-- 사용자별 할 일 개수
결과:
user_id | todo_count
--------|----------
1 | 5
2 | 3
3 | 8
HAVING — 그룹 필터링¶
WHERE는 행을 필터링하고, HAVING은 그룹을 필터링합니다.
SELECT user_id, COUNT(*) AS todo_count
FROM todos
GROUP BY user_id
HAVING COUNT(*) >= 5;
-- 할 일이 5개 이상인 사용자만
JOIN — 테이블 연결¶
관계형 모델에서 데이터는 여러 테이블에 나뉘어 저장됩니다.
JOIN은 그 테이블들을 연결해서 하나의 결과로 만드는 방법입니다.
INNER JOIN¶
두 테이블에 모두 존재하는 행만 반환합니다.
SELECT todos.id, todos.title, users.name
FROM todos
INNER JOIN users ON todos.user_id = users.id;
결과:
id | title | name
---|------------|------
1 | 운동하기 | 지민
2 | 책 읽기 | 지민
3 | 영어 공부 | 하나
LEFT JOIN¶
왼쪽 테이블의 모든 행을 반환합니다.
오른쪽에 매칭되는 값이 없으면 NULL로 채웁니다.
SELECT users.name, todos.title
FROM users
LEFT JOIN todos ON users.id = todos.user_id;
-- 할 일이 없는 사용자도 결과에 포함 (todos 컬럼은 NULL)
결과:
name | title
------|--------
지민 | 운동하기
지민 | 책 읽기
하나 | 영어 공부
준서 | NULL ← 할 일이 없는 사용자도 포함
JOIN 선택 기준¶
모든 A를 보고 싶고, 연결된 B도 있으면 함께 → LEFT JOIN
반드시 양쪽 모두 있는 것만 → INNER JOIN
별칭(Alias) 사용¶
JOIN이 길어지면 별칭으로 줄입니다.
SELECT t.id, t.title, u.name
FROM todos AS t
INNER JOIN users AS u ON t.user_id = u.id
WHERE u.id = 1;
INSERT — 데이터 삽입¶
INSERT INTO users (email, name)
VALUES ('[email protected]', '지민');
INSERT INTO todos (user_id, title)
VALUES (1, '운동하기');
여러 행을 한 번에:
INSERT INTO users (email, name) VALUES
('[email protected]', '지민'),
('[email protected]', '하나'),
('[email protected]', '준서');
UPDATE — 데이터 수정¶
UPDATE todos
SET is_done = 1
WHERE id = 3;
-- id가 3인 todo를 완료 상태로 변경
UPDATE users
SET name = '김지민', email = '[email protected]'
WHERE id = 1;
-- 여러 컬럼 동시 수정
주의: WHERE를 빠뜨리면 전체 행이 수정됩니다.
UPDATE todos SET is_done = 1;
-- 모든 할 일이 완료 상태로! (의도한 게 아니라면 재앙)
DELETE — 데이터 삭제¶
DELETE FROM todos WHERE id = 5;
-- id가 5인 행 삭제
DELETE FROM todos WHERE user_id = 1 AND is_done = 1;
-- user_id 1의 완료된 항목 전체 삭제
주의: WHERE를 빠뜨리면 테이블의 모든 행이 삭제됩니다.
소프트 삭제 (Soft Delete)¶
실무에서는 데이터를 실제로 지우지 않는 경우가 많습니다.
-- deleted_at 컬럼으로 삭제 시각을 기록하는 방식
ALTER TABLE todos ADD COLUMN deleted_at TEXT;
-- 삭제 대신:
UPDATE todos SET deleted_at = datetime('now') WHERE id = 5;
-- 조회 시 삭제된 것 제외:
SELECT * FROM todos WHERE deleted_at IS NULL;
실행 순서 — SQL은 위에서 아래로 읽히지 않는다¶
작성 순서와 실행 순서가 다릅니다.
작성 순서: 실행 순서:
SELECT ① → FROM ①
FROM ② → JOIN ②
WHERE ③ → WHERE ③
GROUP BY ④ → GROUP BY ④
HAVING ⑤ → HAVING ⑤
ORDER BY ⑥ → SELECT ⑥
LIMIT ⑦ → ORDER BY ⑦
LIMIT ⑧
이 순서를 알면 왜 WHERE에서 SELECT의 별칭을 쓸 수 없는지 이해됩니다.
-- 잘못된 예 — WHERE 실행 시점에 todo_count가 아직 없음
SELECT COUNT(*) AS todo_count FROM todos
WHERE todo_count > 5; -- ← 오류
-- 올바른 예
SELECT user_id, COUNT(*) AS todo_count FROM todos
GROUP BY user_id
HAVING COUNT(*) > 5; -- HAVING은 GROUP BY 이후 실행
실습 미션¶
미션 1: 기본 조회¶
아래 테이블이 있다고 가정합니다.
users: id, email, name, age, city
todos: id, user_id, title, is_done, created_at
다음 쿼리를 작성하세요.
1. 모든 사용자의 이름과 이메일을 조회하세요.
2. 서울에 사는 사용자만 조회하세요.
3. 나이가 18세 이상인 사용자를 나이 내림차순으로 조회하세요.
4. 완료되지 않은 할 일을 최신순으로 10개만 조회하세요.
5. 이름에 '민'이 들어있는 사용자를 조회하세요.
미션 2: 집계¶
1. 전체 사용자 수를 구하세요.
2. 완료된 할 일의 수를 구하세요.
3. 사용자별 할 일 개수를 구하세요 (user_id와 개수 출력).
4. 할 일이 3개 이상인 사용자만 골라서 개수 내림차순으로 출력하세요.
미션 3: JOIN¶
1. 모든 할 일을 조회할 때 작성자 이름도 함께 출력하세요.
2. 완료되지 않은 할 일을 조회할 때 작성자 이름과 이메일도 출력하세요.
3. 할 일이 아직 없는 사용자의 이름을 출력하세요. (힌트: LEFT JOIN + IS NULL)
미션 4: INSERT, UPDATE, DELETE¶
1. 사용자(email: '[email protected]', name: '테스트')를 추가하세요.
2. user_id = 1인 사용자의 완료된 할 일을 모두 삭제하세요.
3. id = 7인 할 일의 제목을 '영어 단어 30개'로 바꾸고 완료 상태로 수정하세요.
미션 5 (심화): 5장 ERD를 SQL로¶
5장에서 설계한 서비스의 ERD를 SQL로 변환하세요.
1. CREATE TABLE로 테이블을 만드세요.
2. 샘플 데이터를 INSERT하세요 (각 테이블 3행 이상).
3. 아래 질문에 답하는 SELECT 쿼리를 작성하세요:
- 특정 사용자의 모든 데이터를 조회하세요.
- 가장 많이 활동한 사용자는 누구인가요?
핵심 요약¶
| 명령 | 역할 | 주의점 |
|---|---|---|
SELECT |
조회 | WHERE, ORDER BY, LIMIT, GROUP BY, HAVING 활용 |
INSERT |
삽입 | 컬럼 순서와 값 순서가 일치해야 함 |
UPDATE |
수정 | WHERE 필수 — 없으면 전체 수정 |
DELETE |
삭제 | WHERE 필수 — 없으면 전체 삭제 |
INNER JOIN |
교집합 연결 | ON 조건으로 FK ↔ PK 연결 |
LEFT JOIN |
왼쪽 전체 연결 | 매칭 없으면 NULL |
UPDATE와 DELETE에는 항상 WHERE를 붙이는 습관을 들이세요.
WHERE 없이 실행하면 전체 데이터가 바뀌거나 사라집니다.