0. SQL 기본 구조와 실행 순서
SQL 쿼리의 기본 구조는 다음과 같이 이루어진다.
SELECT 컬럼/표현식
FROM 테이블명 AS t
WHERE 조건
GROUP BY 그룹핑 기준
HAVING 그룹핑 이후 조건
ORDER BY 정렬 기준
LIMIT 개수 [OFFSET 시작위치]
SQL 실행 순서
코드는 위에서 아래로 보이지만 실제로는 아래 순서로 실행된다.
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT
이 실행 순서를 이해하면 WHERE/HAVING 차이, 그룹 연산, 정렬 오류 등을 쉽게 해결할 수 있다.
1. SELECT / FROM / WHERE
1-1. 컬럼 선택 및 별칭(AS)
SELECT
e.emp_no AS 직원번호,
e.salary * 12 AS 연봉
FROM employees e;
- AS는 생략 가능 (e.emp_no 직원번호)
- select 목록의 연산 결과에도 별칭을 붙일 수 있음
1-2. WHERE 조건식
SELECT *
FROM employees
WHERE salary >= 3000 -- 비교 연산자: =, !=, <>, >, <, >=, <=
AND dept_no = 'D001' -- 논리 연산자: AND, OR, NOT
AND hire_date BETWEEN '2020-01-01' AND '2020-12-31'
AND job IN ('DEV', 'QA'); -- IN / NOT IN
- BETWEEN a AND b : a 이상, b 이하
- IN (...) / NOT IN (...) : 여러 값 비교 시 유용
- 문자열, 날짜 비교 모두 가능
2. ORDER BY와 LIMIT
2-1. ORDER BY
SELECT name, age, score
FROM students
ORDER BY score DESC, age ASC;
- 여러 컬럼 조합 정렬 가능
- ASC는 생략 가능
2-2. LIMIT / OFFSET
SELECT *
FROM students
ORDER BY score DESC
LIMIT 5; -- 상위 5개
SELECT *
FROM students
ORDER BY score DESC
LIMIT 5 OFFSET 5; -- 6~10번째
3. 집계 함수 + GROUP BY + HAVING
3-1. 집계 함수
자주 쓰는 집계 함수:
- COUNT(*), COUNT(col)
- SUM(col), AVG(col)
- MIN(col), MAX(col)
SELECT
dept_no,
COUNT(*) AS 인원수,
AVG(salary) AS 평균연봉
FROM employees
GROUP BY dept_no;
💡GROUP BY 시 주의
- 표준 SQL에서는 SELECT 문에 다음 두 종류의 컬럼만 포함해야 한다.
- 그룹 기준 컬럼 (GROUP BY에 있는 것)집계 함수로 감싼 컬럼
(MySQL은 기본 설정에서 예외를 허용하기도 함)
3-2. HAVING vs WHERE
- WHERE : 그룹핑 이전의 행 필터링
- HAVING : 그룹핑 이후의 집계 결과 필터링
SELECT dept_no, COUNT(*) AS 인원수
FROM employees
WHERE hire_date >= '2020-01-01'
GROUP BY dept_no
HAVING COUNT(*) >= 5;
4. JOIN (조인)
4-1. INNER JOIN
SELECT
o.order_id,
c.name AS customer_name
FROM orders o
JOIN customers c ON o.customer_id = c.id;
- 두 테이블 모두에서 조건이 만족하는 행만 반환 (교집합)
4-2. LEFT JOIN
SELECT
c.id,
c.name,
o.order_id
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id;
- 왼쪽(customers)은 모든 행 유지
- 오른쪽(orders) 데이터가가 없으면 NULL
- “주문 이력이 없는 고객도 포함” 같은 문제에서 자주 사용
4-3. 자기 자신 조인 (Self Join)
SELECT
e1.emp_no,
e1.name AS 직원,
e2.name AS 매니저
FROM employees e1
JOIN employees e2 ON e1.manager_id = e2.emp_no;
5. 서브쿼리 (Subquery)
5-1. SELECT 안에서 쓰는 스칼라 서브쿼리
SELECT
name,
salary,
(SELECT AVG(salary) FROM employees) AS 평균연봉
FROM employees;
모든 행에 대해 같은 값(전체 평균)을 붙이고 싶을 때.
5-2. WHERE 절에서 IN
SELECT *
FROM employees
WHERE dept_no IN (
SELECT dept_no
FROM departments
WHERE location = 'SEOUL'
);
서울에 있는 부서에 속한 직원
5-3. FROM 절 서브쿼리 (인라인 뷰)
SELECT dept_no, avg_salary
FROM (
SELECT dept_no, AVG(salary) AS avg_salary
FROM employees
GROUP BY dept_no
) AS t
WHERE avg_salary >= 3000;
한 번 집계한 결과를 다시 필터링할 때 자주 사용한다
6. NULL 처리
6-1. NULL 비교
WHERE col IS NULL
WHERE col IS NOT NULL
- col = NULL → 불가능
- 반드시 IS (NOT) NULL
6-2. IFNULL / COALESCE
SELECT IFNULL(phone, '없음') AS phone FROM customers;
SELECT COALESCE(phone, '없음') AS phone FROM customers;
- IFNULL(expr, alt) : expr이 NULL이면 alt 반환
- COALESCE(a, b, c, ...) : 왼쪽부터 첫 NULL 아닌 첫 값 반환 (표준 함수)
SELECT
dept_no,
IFNULL(SUM(bonus), 0) AS 총보너스
FROM employees
GROUP BY dept_no;
- 위와 같이 집계에서 자주 사용한다
7. 문자열 관련 함수
SELECT
CONCAT(first_name, ' ', last_name) AS full_name,
SUBSTRING(email, 1, 5) AS prefix,
LEFT(phone, 3) AS 국번,
RIGHT(phone, 4) AS 끝4자리,
LENGTH(name) AS 바이트길이,
CHAR_LENGTH(name) AS 글자수,
UPPER(name), -- 대문자
LOWER(name) -- 소문자
FROM customers;
7-1. LIKE 패턴 매칭
WHERE name LIKE '김%' -- 김으로 시작
WHERE name LIKE '%민' -- 민으로 끝남
WHERE name LIKE '%철수%' -- 철수 포함
WHERE email LIKE '%@gmail.com'
- % : 0글자 이상
- _ : 정확히 1글자
8. 날짜/시간 함수
SELECT
order_id,
order_date,
YEAR(order_date) AS 연도,
MONTH(order_date) AS 월,
DAY(order_date) AS 일,
DATE_FORMAT(order_date, '%Y-%m-%d') AS 날짜포맷,
DATE_ADD(order_date, INTERVAL 7 DAY) AS 일주일후,
DATEDIFF(NOW(), order_date) AS 경과일수
FROM orders;
-- 특정 연/월 필터
WHERE YEAR(order_date) = 2024
AND MONTH(order_date) = 11;
-- 최근 3개월
WHERE order_date >= DATE_SUB(CURDATE(), INTERVAL 3 MONTH);
- 위와 같이 자주 사용한다
9. 수치 함수
SELECT
price,
ROUND(price, 0) AS 반올림,
FLOOR(price) AS 내림,
CEIL(price) AS 올림
FROM products;
-- 백분율
SELECT
category,
ROUND(100.0 * sales / total_sales, 2) AS 비율
FROM ...;
- 위와 같이 정수/백분율 계산 문제에서 사용한다
10. CASE WHEN (조건 분기)
SELECT
name,
score,
CASE
WHEN score >= 90 THEN 'A'
WHEN score >= 80 THEN 'B'
WHEN score >= 70 THEN 'C'
ELSE 'D'
END AS grade
FROM students;
- 조건에 따라 값을 변환해야 할 때
- 구간(연령대, 점수대, 금액대) 분류에서 필수
11. DISTINCT / UNION
11-1. DISTINCT
SELECT DISTINCT dept_no
FROM employees;
- 중복 제거
11-2. UNION vs UNION ALL
SELECT name FROM students
UNION
SELECT name FROM teachers;
- UNION : 합치면서 중복 제거
- UNION ALL : 중복 유지
12. 윈도우 함수
12-1. 기본 형태
SELECT
emp_no,
dept_no,
salary,
ROW_NUMBER() OVER (PARTITION BY dept_no ORDER BY salary DESC) AS rn,
RANK() OVER (PARTITION BY dept_no ORDER BY salary DESC) AS rnk,
DENSE_RANK() OVER (PARTITION BY dept_no ORDER BY salary DESC) AS drnk,
SUM(salary) OVER (PARTITION BY dept_no) AS dept_total
FROM employees;
- PARTITION BY : 그룹 나누는 기준 (GROUP BY와 비슷한 개념)
- ORDER BY : 그룹 안에서 순서를 정함
- ROW_NUMBER() : 동일 값이어도 고유 번호
- RANK() : 동일한 값은 같은 순위, 다음 순위 건너뜀
- DENSE_RANK() : 동일 순위 후 다음 순위를 건너뛰지 않음
12-2. 활용 예시
1. 부서별 연봉 1등 직원만 뽑기
SELECT *
FROM (
SELECT
emp_no,
dept_no,
salary,
ROW_NUMBER() OVER (PARTITION BY dept_no ORDER BY salary DESC) AS rn
FROM employees
) t
WHERE rn = 1;
2. 누적 합
SELECT
order_date,
amount,
SUM(amount) OVER (ORDER BY order_date) AS 누적금액
FROM orders;
13. SQL 패턴별 정리
- 단순 조회/정렬
- SELECT, WHERE, ORDER BY, LIMIT, LIKE
- 집계/그룹 문제
- GROUP BY, COUNT/SUM/AVG, HAVING, DISTINCT
- 두 개 이상 테이블 연관 문제
- INNER JOIN, LEFT JOIN, ON 조건, 다중 조인
- 특정 조건을 만족하는 그룹만
- HAVING COUNT(*) >= N, MAX/MIN, 서브쿼리
- 날짜 기준 필터 & 포맷팅
- DATE_FORMAT, YEAR/MONTH, BETWEEN, DATE_ADD/SUB
- 구간/등급/라벨링
- CASE WHEN, BETWEEN, 나이/점수 구간화
- “n등”, “가장 많이/적게 주문한 ~”
- 윈도우 함수 (ROW_NUMBER/RANK)
- 또는 서브쿼리로 WHERE col = (SELECT MAX(col) ...)
'DB' 카테고리의 다른 글
| [Database/DBMS] 트랜잭션과 ACID (3) | 2025.10.11 |
|---|---|
| [Database] 데이터베이스 기본 개념 (1) | 2025.10.10 |