SQL 기초 · SELECT/INSERT/UPDATE/DELETE · JOIN
4가지 CRUD 키워드 + INNER/LEFT JOIN — DB 대화의 알파벳.
Supabase·Prisma·Drizzle이 SQL을 가려도, 에러·성능 문제 생기면 생 SQL을 읽어야 한다. JOIN 종류를 모르면 "왜 사용자가 빠졌지?" 사고를 못 푼다.
SQL은 '데이터에 던지는 4가지 질문'. SELECT(조회) · INSERT(추가) · UPDATE(수정) · DELETE(삭제). 표준 SQL은 대부분 DB에서 공통(미세 차이 존재).
-- SELECT 기본
SELECT id, name FROM users WHERE age >= 20 ORDER BY name ASC LIMIT 10;
-- 집계 (GROUP BY + HAVING)
SELECT country, COUNT(*) AS n
FROM users
WHERE signup_at > '2024-01-01'
GROUP BY country
HAVING COUNT(*) >= 100
ORDER BY n DESC;
-- INSERT
INSERT INTO users (email, name) VALUES ('kim@example.com', '성훈');
-- UPDATE (WHERE 꼭!)
UPDATE users SET role = 'admin' WHERE id = 42;
-- ⚠️ WHERE 빼먹으면 모든 행이 바뀐다
-- DELETE
DELETE FROM posts WHERE created_at < NOW() - INTERVAL '1 year';
-- UPSERT (Postgres)
INSERT INTO users (email, name) VALUES ('k@e.com', '김')
ON CONFLICT (email) DO UPDATE SET name = EXCLUDED.name;"WHERE 없는 UPDATE/DELETE"는 전설의 실수 1번. 반드시 트랜잭션으로 감싸고 먼저 SELECT로 영향 행 수를 확인하자.
JOIN은 여러 테이블을 한 결과로 합치기. 관계형 DB의 진짜 힘. 핵심 네 가지만 알면 된다.
| JOIN 종류 | 뜻 | 교집합·합집합 |
|---|---|---|
| INNER JOIN | 양쪽에 매칭되는 행만 | 교집합 |
| LEFT JOIN | 왼쪽 전부 + 오른쪽 매칭 (없으면 NULL) | 왼쪽 포함 |
| RIGHT JOIN | 오른쪽 전부 + 왼쪽 매칭 | 오른쪽 포함 (드물게 사용) |
| FULL OUTER JOIN | 양쪽 전부 (안 맞으면 NULL) | 합집합 |
-- 예시 테이블
-- users: (id, name) posts: (id, user_id, title)
-- 1 김, 2 이, 3 박 10 (user 1), 11 (user 2), 12 (user 99)
-- INNER JOIN — 양쪽 매칭만 (user 3 박, post 12가 사라짐)
SELECT u.name, p.title
FROM users u
INNER JOIN posts p ON u.id = p.user_id;
-- 김 | 제목A
-- 이 | 제목B
-- LEFT JOIN — 모든 사용자 + 글 (글 없으면 NULL)
SELECT u.name, p.title
FROM users u
LEFT JOIN posts p ON u.id = p.user_id;
-- 김 | 제목A
-- 이 | 제목B
-- 박 | NULL ← user 3 유지, post 없음
-- "글 없는 사용자만 찾기" 이디엄
SELECT u.name
FROM users u
LEFT JOIN posts p ON u.id = p.user_id
WHERE p.id IS NULL;
-- 박"LEFT JOIN + WHERE IS NULL"은 "관계가 없는 것"을 찾는 고전 패턴. 초보가 모르면 결과가 이상하다.
N+1 쿼리 문제 — ORM이 만드는 단골 함정. `users.map(u => db.posts.where(userId: u.id))`처럼 유저마다 쿼리 1번 = 1+N. JOIN 하나로 끝낼 걸 수백 번 왕복. Claude가 만든 코드에 루프 안 DB 호출이 있으면 JOIN으로 묶자.
-- 실전: 서브쿼리와 CTE (Common Table Expression)
-- CTE가 가독성·디버깅 측면에서 우위
WITH active_users AS (
SELECT id FROM users WHERE last_login > NOW() - INTERVAL '30 days'
)
SELECT u.name, COUNT(p.id) AS post_count
FROM users u
JOIN active_users a ON u.id = a.id
LEFT JOIN posts p ON p.user_id = u.id
GROUP BY u.id, u.name
ORDER BY post_count DESC
LIMIT 10;
-- 윈도우 함수 — "사용자별 순위" 같은 고급 분석
SELECT
name,
score,
RANK() OVER (PARTITION BY country ORDER BY score DESC) AS country_rank
FROM users;CTE·윈도우 함수는 Postgres·MySQL 8+·Supabase 모두 지원. Claude가 복잡 쿼리 짤 때 자주 등장 — 읽을 줄 알아야 한다.
양쪽 테이블에 모두 매칭되는 행만 결합하는 JOIN은?
글이 한 개도 없는 사용자의 이름을 모두 조회하는 SQL을 작성하시오.
-- users(id, name), posts(id, user_id, title)
-- posts.user_id 는 users.id 참조`UPDATE users SET role='admin'` 처럼 WHERE를 빼먹으면 에러가 난다.