이 글에서 얻는 것
- SELECT/INSERT/UPDATE/DELETE 기본 문법을 이해하고 실무에서 바로 사용할 수 있습니다.
- WHERE 조건문으로 데이터를 필터링하고, ORDER BY/LIMIT로 정렬과 제한을 적용합니다.
- **NULL 처리, 문자열 검색(LIKE), 범위 조건(BETWEEN, IN)**을 활용합니다.
- 트랜잭션(BEGIN/COMMIT/ROLLBACK) 기초를 이해하고 데이터 정합성을 유지합니다.
0) SQL은 “데이터베이스와 대화하는 언어”
SQL (Structured Query Language)은 관계형 데이터베이스에서 데이터를 조작하고 조회하는 표준 언어입니다.
SQL의 주요 카테고리:
- DML (Data Manipulation Language): 데이터 조작
- SELECT, INSERT, UPDATE, DELETE
- DDL (Data Definition Language): 테이블/스키마 정의
- CREATE, ALTER, DROP
- DCL (Data Control Language): 권한 제어
- GRANT, REVOKE
- TCL (Transaction Control Language): 트랜잭션 제어
- COMMIT, ROLLBACK
이 글에서는 **DML (CRUD)**에 집중합니다.
1) SELECT: 데이터 조회
1-1) 기본 조회
-- 모든 컬럼 조회
SELECT * FROM users;
-- 특정 컬럼만 조회
SELECT id, name, email FROM users;
-- 별칭(alias) 사용
SELECT
id AS user_id,
name AS user_name,
email
FROM users;
-- 중복 제거
SELECT DISTINCT city FROM users;
1-2) WHERE: 조건 필터링
-- 단일 조건
SELECT * FROM users WHERE id = 1;
SELECT * FROM users WHERE age >= 20;
SELECT * FROM users WHERE city = 'Seoul';
-- 다중 조건 (AND, OR)
SELECT * FROM users
WHERE age >= 20 AND city = 'Seoul';
SELECT * FROM users
WHERE city = 'Seoul' OR city = 'Busan';
-- NOT
SELECT * FROM users WHERE NOT city = 'Seoul';
-- = SELECT * FROM users WHERE city != 'Seoul';
-- BETWEEN (범위)
SELECT * FROM products
WHERE price BETWEEN 1000 AND 5000;
-- = price >= 1000 AND price <= 5000
-- IN (여러 값 중 하나)
SELECT * FROM users
WHERE city IN ('Seoul', 'Busan', 'Incheon');
-- LIKE (패턴 검색)
SELECT * FROM users WHERE name LIKE '김%'; -- '김'으로 시작
SELECT * FROM users WHERE email LIKE '%@gmail.com'; -- '@gmail.com'으로 끝
SELECT * FROM users WHERE phone LIKE '010-____-1234'; -- _ 는 한 글자
-- IS NULL / IS NOT NULL
SELECT * FROM users WHERE deleted_at IS NULL; -- 삭제되지 않은 유저
SELECT * FROM users WHERE deleted_at IS NOT NULL; -- 삭제된 유저
1-3) ORDER BY: 정렬
-- 오름차순 (기본)
SELECT * FROM users ORDER BY age;
SELECT * FROM users ORDER BY age ASC;
-- 내림차순
SELECT * FROM users ORDER BY age DESC;
-- 다중 정렬
SELECT * FROM users
ORDER BY city ASC, age DESC;
-- city로 먼저 정렬, 같으면 age로 정렬
-- NULL 정렬 순서 지정 (MySQL 8.0+)
SELECT * FROM users
ORDER BY deleted_at ASC NULLS FIRST;
1-4) LIMIT: 결과 개수 제한
-- 상위 10개
SELECT * FROM users LIMIT 10;
-- 페이징: OFFSET
SELECT * FROM users LIMIT 10 OFFSET 20;
-- 21번째부터 10개 (20개 건너뛰고)
-- 더 직관적인 표현 (MySQL)
SELECT * FROM users LIMIT 20, 10;
-- OFFSET 20, LIMIT 10
-- 가장 나이 많은 5명
SELECT * FROM users
ORDER BY age DESC
LIMIT 5;
1-5) 집계 함수
-- COUNT: 개수
SELECT COUNT(*) FROM users; -- 전체 행 수
SELECT COUNT(DISTINCT city) FROM users; -- 도시 종류 수
-- SUM: 합계
SELECT SUM(price) FROM orders;
-- AVG: 평균
SELECT AVG(age) FROM users;
-- MIN/MAX: 최솟값/최댓값
SELECT MIN(price), MAX(price) FROM products;
-- 조건과 함께
SELECT COUNT(*) FROM users WHERE age >= 20;
SELECT AVG(price) FROM orders WHERE status = 'COMPLETED';
2) INSERT: 데이터 삽입
2-1) 단일 행 삽입
-- 모든 컬럼 값 지정
INSERT INTO users (id, name, email, age, city)
VALUES (1, 'Alice', 'alice@example.com', 25, 'Seoul');
-- 일부 컬럼만 지정 (나머지는 NULL 또는 기본값)
INSERT INTO users (name, email)
VALUES ('Bob', 'bob@example.com');
-- AUTO_INCREMENT 컬럼은 생략
INSERT INTO users (name, email, age)
VALUES ('Charlie', 'charlie@example.com', 30);
2-2) 다중 행 삽입
INSERT INTO users (name, email, age) VALUES
('Alice', 'alice@example.com', 25),
('Bob', 'bob@example.com', 30),
('Charlie', 'charlie@example.com', 28);
-- 한 번에 여러 행 삽입이 더 빠름 (트랜잭션 비용 감소)
2-3) INSERT … ON DUPLICATE KEY UPDATE
-- MySQL: 중복 시 업데이트
INSERT INTO users (id, name, email, age)
VALUES (1, 'Alice', 'alice@example.com', 25)
ON DUPLICATE KEY UPDATE
name = VALUES(name),
age = VALUES(age);
-- id가 1인 행이 있으면 UPDATE, 없으면 INSERT
2-4) INSERT … SELECT
-- 다른 테이블에서 데이터 복사
INSERT INTO users_backup (id, name, email)
SELECT id, name, email FROM users WHERE created_at < '2024-01-01';
3) UPDATE: 데이터 수정
3-1) 기본 UPDATE
-- 특정 행 수정
UPDATE users
SET age = 26
WHERE id = 1;
-- 다중 컬럼 수정
UPDATE users
SET
age = 26,
city = 'Busan',
updated_at = NOW()
WHERE id = 1;
-- 조건 없이 UPDATE (전체 행 수정 - 위험!)
UPDATE users SET status = 'ACTIVE';
-- 모든 유저의 status가 'ACTIVE'로 변경됨
3-2) 조건부 UPDATE
-- 여러 행 수정
UPDATE users
SET status = 'INACTIVE'
WHERE last_login < '2024-01-01';
-- 계산식 사용
UPDATE products
SET price = price * 1.1
WHERE category = 'Electronics';
-- CASE WHEN (조건부 값 설정)
UPDATE users
SET grade = CASE
WHEN age < 20 THEN 'Junior'
WHEN age < 30 THEN 'Senior'
ELSE 'Master'
END
WHERE status = 'ACTIVE';
3-3) UPDATE 주의사항
-- ❌ WHERE 절 없이 UPDATE (위험!)
UPDATE users SET password = 'reset123';
-- 모든 유저의 비밀번호가 같아짐!
-- ✅ 항상 WHERE 절 확인
UPDATE users
SET password = 'reset123'
WHERE id = 1;
-- ✅ UPDATE 전 SELECT로 확인
SELECT * FROM users WHERE id = 1;
-- 결과 확인 후
UPDATE users SET password = 'reset123' WHERE id = 1;
4) DELETE: 데이터 삭제
4-1) 기본 DELETE
-- 특정 행 삭제
DELETE FROM users WHERE id = 1;
-- 조건에 맞는 여러 행 삭제
DELETE FROM users
WHERE status = 'INACTIVE' AND last_login < '2024-01-01';
-- 전체 삭제 (위험!)
DELETE FROM users;
-- 모든 데이터 삭제됨!
4-2) DELETE vs TRUNCATE
-- DELETE: 행 단위 삭제, 트랜잭션 로그 기록, 느림
DELETE FROM users;
-- TRUNCATE: 테이블 전체 초기화, 빠름, 롤백 불가
TRUNCATE TABLE users;
-- AUTO_INCREMENT도 초기화됨
4-3) 소프트 삭제 (Soft Delete)
실제로 삭제하지 않고, 삭제 표시만 함
-- 삭제 표시
UPDATE users
SET deleted_at = NOW()
WHERE id = 1;
-- 삭제되지 않은 데이터만 조회
SELECT * FROM users WHERE deleted_at IS NULL;
-- 실제 삭제는 배치 작업으로 (예: 1년 후)
DELETE FROM users
WHERE deleted_at < DATE_SUB(NOW(), INTERVAL 1 YEAR);
5) 트랜잭션 (Transaction)
5-1) 트랜잭션 기본
-- 트랜잭션 시작
START TRANSACTION;
-- 또는 BEGIN;
-- 여러 작업 수행
UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
UPDATE accounts SET balance = balance + 1000 WHERE id = 2;
-- 성공 시 커밋
COMMIT;
-- 실패 시 롤백
ROLLBACK;
5-2) 실전 예제
-- 계좌 이체
START TRANSACTION;
-- A 계좌에서 출금
UPDATE accounts
SET balance = balance - 100000
WHERE id = 1 AND balance >= 100000;
-- 잔액 부족 체크
SELECT ROW_COUNT() INTO @rows_affected;
IF @rows_affected = 0 THEN
ROLLBACK;
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '잔액 부족';
END IF;
-- B 계좌로 입금
UPDATE accounts
SET balance = balance + 100000
WHERE id = 2;
COMMIT;
5-3) 자동 커밋 설정
-- 자동 커밋 상태 확인
SELECT @@autocommit;
-- 자동 커밋 비활성화
SET autocommit = 0;
-- 작업 수행
UPDATE users SET age = 26 WHERE id = 1;
-- 수동 커밋 필요
COMMIT;
-- 자동 커밋 재활성화
SET autocommit = 1;
6) 실전 쿼리 패턴
6-1) 페이징
-- 1페이지 (1~10)
SELECT * FROM posts
ORDER BY created_at DESC
LIMIT 10 OFFSET 0;
-- 2페이지 (11~20)
SELECT * FROM posts
ORDER BY created_at DESC
LIMIT 10 OFFSET 10;
-- 페이지 번호로 계산
-- page = 3, pageSize = 10
-- OFFSET = (page - 1) * pageSize = 20
SELECT * FROM posts
ORDER BY created_at DESC
LIMIT 10 OFFSET 20;
6-2) 검색
-- 제목 또는 내용에 키워드 포함
SELECT * FROM posts
WHERE title LIKE '%spring%' OR content LIKE '%spring%';
-- 여러 키워드 검색 (AND)
SELECT * FROM posts
WHERE title LIKE '%spring%' AND title LIKE '%boot%';
-- 대소문자 구분 없이 검색 (MySQL)
SELECT * FROM posts
WHERE LOWER(title) LIKE LOWER('%Spring%');
6-3) 최신순 N개
-- 최신 게시글 10개
SELECT * FROM posts
ORDER BY created_at DESC
LIMIT 10;
-- 가장 비싼 상품 5개
SELECT * FROM products
ORDER BY price DESC
LIMIT 5;
6-4) 특정 기간 데이터
-- 오늘 생성된 주문
SELECT * FROM orders
WHERE DATE(created_at) = CURDATE();
-- 최근 7일 데이터
SELECT * FROM orders
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY);
-- 특정 월 데이터
SELECT * FROM orders
WHERE YEAR(created_at) = 2025 AND MONTH(created_at) = 1;
6-5) 중복 제거 후 카운트
-- 유니크한 도시 개수
SELECT COUNT(DISTINCT city) FROM users;
-- 각 도시별 유저 수
SELECT city, COUNT(*) as user_count
FROM users
GROUP BY city;
7) 자주 하는 실수
7-1) WHERE 절 누락
-- ❌ 의도: id=1인 유저만 삭제
DELETE FROM users; -- WHERE 절 누락 → 전체 삭제!
-- ✅ WHERE 절 필수
DELETE FROM users WHERE id = 1;
7-2) NULL 비교
-- ❌ NULL은 = 로 비교 불가
SELECT * FROM users WHERE deleted_at = NULL; -- 결과 없음!
-- ✅ IS NULL 사용
SELECT * FROM users WHERE deleted_at IS NULL;
7-3) LIKE 성능 문제
-- ❌ 앞에 %가 있으면 인덱스 사용 불가
SELECT * FROM users WHERE email LIKE '%@gmail.com';
-- ✅ 앞에 %가 없으면 인덱스 사용 가능
SELECT * FROM users WHERE email LIKE 'alice%';
7-4) ORDER BY 없이 LIMIT
-- ❌ 순서 보장 안 됨
SELECT * FROM users LIMIT 10;
-- 어떤 10개가 나올지 불확실!
-- ✅ ORDER BY로 순서 명시
SELECT * FROM users
ORDER BY created_at DESC
LIMIT 10;
8) SQL 스타일 가이드
-- ✅ 읽기 쉬운 SQL
SELECT
id,
name,
email,
created_at
FROM users
WHERE
status = 'ACTIVE'
AND age >= 20
ORDER BY created_at DESC
LIMIT 10;
-- ❌ 읽기 어려운 SQL
SELECT id,name,email,created_at FROM users WHERE status='ACTIVE' AND age>=20 ORDER BY created_at DESC LIMIT 10;
-- 예약어는 대문자 (또는 소문자로 일관성 유지)
-- 테이블/컬럼명은 소문자
-- 들여쓰기로 가독성 확보
연습 (추천)
간단한 테이블 생성 후 CRUD 연습
CREATE TABLE practice ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50), age INT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );다양한 조건 조합
- WHERE + ORDER BY + LIMIT
- LIKE + AND
- BETWEEN + IN
트랜잭션 연습
- START TRANSACTION → UPDATE → ROLLBACK
- COMMIT 전후 데이터 확인
요약: 스스로 점검할 것
- SELECT/INSERT/UPDATE/DELETE 기본 문법을 사용할 수 있다
- WHERE 조건문으로 데이터를 필터링할 수 있다
- ORDER BY로 정렬하고, LIMIT으로 개수를 제한할 수 있다
- NULL 비교는 IS NULL / IS NOT NULL을 사용한다
- UPDATE/DELETE 전에 WHERE 절을 항상 확인한다
- 트랜잭션으로 여러 작업을 하나로 묶을 수 있다
다음 단계
- SQL 서브쿼리와 집계:
/learning/deep-dive/deep-dive-sql-subquery-aggregation/ - SQL JOIN:
/learning/deep-dive/deep-dive-sql-basics-joins-explain/ - 데이터베이스 인덱스:
/learning/deep-dive/deep-dive-database-indexing/
💬 댓글