백엔드 데이터 인프라 8편. SQL의 다섯 가지 핵심 동사 SELECT·INSERT·UPDATE·DELETE·MERGE를 PG 18 기준으로 풀어쓴 학습 노트.
이 글은 백엔드 데이터 인프라 시리즈 70편 중 8편이에요. 7편 관계형 모델 까지 "개념" 을 다뤘으니, 이번 8편은 그 개념을 코드로 표현하는 언어 — SQL.
SQL이 헷갈리는 이유
SQL = "Structured Query Language". 단어 "Query" 가 박혀 있어서 처음 보면 "조회 언어인가?" 헷갈려요. 사실 SQL은 조회 + 생성·수정·삭제 까지 다 하는 만능 언어. "동사 5개" 만 외우면 90% 시나리오 끝.
이 글에서는 다섯 가지 동사 를 한 번에 짚어요. SELECT는 조회, INSERT는 생성, UPDATE는 수정, DELETE는 삭제, 그리고 MERGE는 있으면 수정·없으면 생성하는 UPSERT(있으면 갱신, 없으면 추가). 다섯 동사가 SQL의 뼈대예요.
SELECT — 데이터 조회
가장 자주 쓰는 동사. 표준 형태:
SELECT 컬럼1, 컬럼2, ...
FROM 테이블
WHERE 조건
ORDER BY 컬럼
LIMIT 개수
OFFSET 시작 위치;
기본 예
-- 모든 컬럼
SELECT * FROM users;
-- 특정 컬럼
SELECT id, name, email FROM users;
-- 조건 필터
SELECT name, email FROM users WHERE id = 1;
-- 정렬
SELECT * FROM users ORDER BY created_at DESC;
-- 페이지네이션
SELECT * FROM users ORDER BY id LIMIT 10 OFFSET 20;
별칭 (AS)
SELECT
name AS user_name,
email AS user_email,
COUNT(*) AS total
FROM users
GROUP BY name, email;
AS 키워드로 결과 컬럼명 변경. 생략 가능 (name user_name) 이지만 명시 권장.
DISTINCT
SELECT DISTINCT country FROM users; -- 중복 제거
집계 함수
SELECT COUNT(*) FROM users;
SELECT AVG(amount) FROM orders;
SELECT MIN(created_at), MAX(created_at) FROM orders;
SELECT SUM(amount) FROM orders WHERE status = 'COMPLETED';
COUNT·AVG·MIN·MAX·SUM 5대 집계 함수. 11~14편에서 깊이.
INSERT — 데이터 생성
INSERT INTO 테이블 (컬럼1, 컬럼2, ...)
VALUES (값1, 값2, ...);
단건
INSERT INTO users (name, email)
VALUES ('Alice', 'alice@example.com');
기본 키(id)는 BIGSERIAL(자동 증가 정수 타입)이라 자동.
여러 건 (Bulk)
INSERT INTO users (name, email) VALUES
('Alice', 'alice@example.com'),
('Bob', 'bob@example.com'),
('Charlie', 'charlie@example.com');
한 SQL로 여러 행 — 단건씩 N번보다 N배 이상 빠름. 대량 입력 표준.
RETURNING — PG 특별 기능
INSERT INTO users (name, email)
VALUES ('Alice', 'alice@example.com')
RETURNING id, created_at;
INSERT 직후 — 생성된 행을 결과로 반환. "자동 증가 ID 알아내기" 표준 패턴. JPA(자바 객체-DB 매핑 표준)의 @GeneratedValue 도 내부적으로 이걸 활용.
SELECT로 INSERT
INSERT INTO archive_users (id, name, email)
SELECT id, name, email FROM users WHERE created_at < '2025-01-01';
다른 쿼리 결과를 그대로 입력. 데이터 이관·아카이브 표준.
ON CONFLICT — UPSERT
INSERT INTO users (id, name, email)
VALUES (1, 'Alice', 'alice@example.com')
ON CONFLICT (id)
DO UPDATE SET name = EXCLUDED.name, email = EXCLUDED.email;
기본 키 충돌 시 UPDATE — "있으면 수정·없으면 생성". PG 9.5+ 표준. MERGE 와 함께 자주.
-- 충돌 시 그냥 무시
INSERT INTO users (id, name) VALUES (1, 'Alice')
ON CONFLICT (id) DO NOTHING;
UPDATE — 데이터 수정
UPDATE 테이블
SET 컬럼1 = 값1, 컬럼2 = 값2, ...
WHERE 조건;
단건
UPDATE users
SET name = 'Alice Smith'
WHERE id = 1;
여러 컬럼 동시
UPDATE users
SET name = 'Alice Smith',
email = 'alice.smith@example.com'
WHERE id = 1;
다른 테이블 값으로 (FROM)
UPDATE users
SET last_login = activities.last_at
FROM activities
WHERE users.id = activities.user_id;
UPDATE FROM 구문 — PG의 강점. 다른 테이블 값을 한 번에 반영.
RETURNING
UPDATE users SET name = 'Alice Smith' WHERE id = 1
RETURNING id, name, updated_at;
수정된 행을 결과로. INSERT 와 동일.
함정 — WHERE 누락
UPDATE users SET name = 'Test'; -- ❌ 모든 행 수정!
운영 환경에서 가장 무서운 실수. 항상 WHERE + 가능하면 트랜잭션 BEGIN 후 SELECT 검증 → UPDATE → COMMIT.
DELETE — 데이터 삭제
DELETE FROM 테이블 WHERE 조건;
기본
DELETE FROM users WHERE id = 1;
RETURNING
DELETE FROM users WHERE id = 1
RETURNING id, name, email;
USING (다른 테이블 참조)
DELETE FROM users
USING bans
WHERE users.id = bans.user_id;
TRUNCATE — 전체 삭제 빠르게
TRUNCATE TABLE users;
DELETE 와 차이를 보면, TRUNCATE는 행 단위 처리를 건너뛰어서 매우 빠르고 AUTO_INCREMENT까지 초기화돼요. 반면 DELETE WHERE 1=1 은 행마다 트리거·로그가 돌아서 느려요.
대량 삭제 = TRUNCATE 권장. 단 — 외래 키로 참조되는 테이블은 못 비움.
함정 — WHERE 누락
DELETE FROM users; -- ❌ 모든 행 삭제!
UPDATE 와 같은 무서운 실수. 운영 = BEGIN; DELETE ...; SELECT COUNT(*); COMMIT; 흐름.
MERGE — UPSERT 표준 (PG 15+)
PG 15 부터 SQL 표준 MERGE 지원.
MERGE INTO users AS target
USING (VALUES (1, 'Alice', 'alice@example.com')) AS source(id, name, email)
ON target.id = source.id
WHEN MATCHED THEN
UPDATE SET name = source.name, email = source.email
WHEN NOT MATCHED THEN
INSERT (id, name, email) VALUES (source.id, source.name, source.email);
복잡한 "있으면 수정·없으면 생성·조건에 따라 삭제" 시나리오에 강함. 단순한 경우 = INSERT ... ON CONFLICT 가 더 깔끔.
동사 5개 한눈에
| 동사 | 의미 | 표준 패턴 |
|---|---|---|
| SELECT | 조회 | SELECT 컬럼 FROM 테이블 WHERE 조건 |
| INSERT | 생성 | INSERT INTO 테이블 (컬럼) VALUES (값) RETURNING * |
| UPDATE | 수정 | UPDATE 테이블 SET 컬럼=값 WHERE 조건 RETURNING * |
| DELETE | 삭제 | DELETE FROM 테이블 WHERE 조건 RETURNING * |
| MERGE | UPSERT | MERGE INTO ... USING ... WHEN MATCHED THEN ... WHEN NOT MATCHED THEN ... |
트랜잭션과의 조합
자바 백엔드 입문 43편 @Transactional 패턴 PG 직접:
BEGIN;
INSERT INTO orders (user_id, amount) VALUES (1, 10000) RETURNING id;
-- 12345 라는 ID 반환됐다 가정
UPDATE users SET point = point - 10000 WHERE id = 1;
COMMIT;
-- 실패 시
ROLLBACK;
5개 동사 + BEGIN·COMMIT·ROLLBACK 3개 = SQL의 핵심 8개 키워드.
Spring 백엔드 흐름
JPA가 자바 객체 ↔ 이 5개 동사 자동 매핑.
| JPA 메서드 | 자동 생성 SQL |
|---|---|
repository.save(user) (신규) |
INSERT + RETURNING id |
repository.save(user) (기존) |
UPDATE |
repository.findById(id) |
SELECT |
repository.delete(user) |
DELETE |
JPA가 "자동 짜주지만" — 자동 생성된 SQL이 어떤 형태인지 알아야 트러블슈팅 가능. 40편 EXPLAIN 에서 깊이.
함정 5가지
(1) WHERE 누락의 재앙
UPDATE·DELETE 시 WHERE 없으면 — 모든 행. 운영 = BEGIN 후 SELECT 검증 → UPDATE/DELETE → 확인 → COMMIT.
(2) NULL 처리
WHERE col = NULL ❌
WHERE col IS NULL ✅
7편 NULL 참고.
(3) 문자열 따옴표 — 작은 따옴표 (single quote)
SELECT * FROM users WHERE name = 'Alice'; ✅
SELECT * FROM users WHERE name = "Alice"; ❌ — 큰 따옴표는 식별자(컬럼명)
PG에서 작은 따옴표 '문자열' 은 문자열 리터럴, 큰 따옴표 "이름" 은 식별자(테이블·컬럼 이름)예요. 둘은 완전히 다른 역할.
(4) RETURNING 잊음
JPA가 안 알려줘서 잘 안 쓰지만 — 직접 SQL 쓸 때 RETURNING 매우 유용.
(5) UPSERT 시 EXCLUDED 안 박음
INSERT INTO users (id, name) VALUES (1, 'Alice')
ON CONFLICT (id)
DO UPDATE SET name = 'Alice'; -- ❌ 항상 'Alice' (의도 X)
ON CONFLICT (id)
DO UPDATE SET name = EXCLUDED.name; -- ✅ INSERT 시 값
EXCLUDED = "INSERT 하려던 값" 의미. 모르면 UPSERT 헷갈림.
UPDATE·DELETE 운영 실행은 BEGIN → SELECT 검증 → 실행 → COUNT 확인 → COMMIT 흐름. 의도와 다른 결과면 ROLLBACK. 매번 백업·확인. 다른 사람에게 한 번 더 검토.
한 줄 정리 — SQL 5대 동사 = SELECT·INSERT·UPDATE·DELETE·MERGE. PG 특별 = RETURNING·ON CONFLICT·FROM/USING. WHERE 누락이 운영 가장 무서운 함정. JPA의 자동 SQL을 직접 짤 줄 알아야 운영 단계 가능.
시험 직전 한 번 더 — SQL 기초 입문자가 매번 헷갈리는 것
- SQL 5대 동사 = SELECT·INSERT·UPDATE·DELETE·MERGE
- 표준 SELECT =
SELECT 컬럼 FROM 테이블 WHERE 조건 ORDER BY ... LIMIT ... - 별칭 =
AS alias - 집계 = COUNT·AVG·MIN·MAX·SUM
- DISTINCT = 중복 제거
- INSERT 여러 건 = 한 SQL에 콤마로 여러 VALUES
- RETURNING = 생성·수정된 행 반환 (PG 특별)
- ON CONFLICT = UPSERT (PG 9.5+)
- EXCLUDED = 충돌 시 INSERT 하려던 값
- INSERT SELECT = 다른 쿼리 결과 입력
- UPDATE SET col=val WHERE 조건
- UPDATE FROM = 다른 테이블 값으로 (PG 강점)
- DELETE FROM 테이블 WHERE 조건
- DELETE USING = 다른 테이블 참조해 삭제
- TRUNCATE = 전체 삭제 빠름 (FK 없는 테이블)
- MERGE = SQL 표준 UPSERT (PG 15+)
- 작은 따옴표 = 문자열, 큰 따옴표 = 식별자
- NULL =
IS NULL(= NULL 거짓) - WHERE 누락 = 모든 행 적용 — 가장 무서운 함정
- 운영 = BEGIN → SELECT 검증 → 실행 → ROLLBACK or COMMIT
- BEGIN·COMMIT·ROLLBACK = 트랜잭션 3대 키워드
- JPA save = INSERT 또는 UPDATE 자동
- 직접 SQL 모르면 운영 트러블슈팅 불가
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 3편 — PostgreSQL 아키텍처 (클라이언트·서버·DB·테이블)
- 4편 — psql 첫 접속과 기본 명령
- 5편 — 데이터베이스 만들기 CREATE DATABASE
- 6편 — PostgreSQL 데이터베이스 접속·전환
- 7편 — PostgreSQL 관계형 모델 핵심 개념
다음 글: