소프트웨어 기술(스타트업 위주)
sql
MySQL 윈도우 함수 자주 사용하는 목록
MySQL에서 자주 사용되는 윈도우 함수들을 간단한 설명과 함께 표로 정리한 문서입니다. 실무에서 자주 등장하는 패턴 위주로 구성되어 있습니다.
함수 | 설명 | 사용 예시 |
---|---|---|
ROW_NUMBER() | 그룹 내에서 각 행에 순위를 부여 (중복 순위 없음) | 부서별 연봉 순위 매기기 |
RANK() | 공동 순위 허용 (동점자 건너뜀) | 대회 점수 순위에서 공동 순위 부여 |
SUM() | 누적 합계 계산 | 유저별 주문 금액 누적 합계 |
AVG() | 누적 평균 계산 | 유저별 평균 점수 계산 |
윈도우 함수 기본 구조
<윈도우 함수>() OVER (
PARTITION BY <그룹 기준 컬럼>
ORDER BY <정렬 기준 컬럼>
)
PARTITION BY
: 그룹을 나누는 기준 (옵션)ORDER BY
: 각 그룹 안에서 정렬 기준
참고
- 윈도우 함수는
WHERE
이 아니라SELECT
절에서 사용됨 GROUP BY
없이도 집계 가능한 장점이 있음- 서브쿼리 또는 CTE와 함께 사용 시 유용함
1. 현업에서 자주 사용하는 SQL 쿼리 문제
- 기본 SELECT + WHERE
문제: 특정 고객의 정보를 조회하라.
SELECT *
FROM Customers
WHERE customer_id = 123;
- 집계 함수 (SUM, COUNT, AVG, MAX, MIN)
문제: 각 고객이 지금까지 주문한 총 금액을 구하라.
SELECT customer_id, SUM(order_amount) AS total_spent
FROM Orders
GROUP BY customer_id;
- GROUP BY + HAVING
문제: 1회 이상 주문한 고객만 조회하라.
SELECT customer_id, COUNT(*) AS order_count
FROM Orders
GROUP BY customer_id
HAVING COUNT(*) > 0;
- JOIN (INNER, LEFT, etc.)
join is null 문제 예시 추가
문제: 주문 정보와 함께 고객 이름을 조회하라.
SELECT o.order_id, c.customer_name, o.order_amount
FROM Orders o
JOIN Customers c ON o.customer_id = c.customer_id;
- NULL 처리
문제: 이메일이 없는 고객을 조회하라.
SELECT *
FROM Customers
WHERE email IS NULL;
- 날짜 관련
자주 사용되는 날짜, 문자 관련 함수 정리
함수 | 설명 | 예시 | 결과 예시 |
---|---|---|---|
CURDATE() | 오늘 날짜 (시간 제외) | CURDATE() | 2025-04-10 |
NOW() | 현재 날짜 + 시간 | NOW() | 2025-04-10 14:23:10 |
DATE() | 날짜만 추출 | DATE(NOW()) | 2025-04-10 |
YEAR(date) | 연도 추출 | YEAR('2025-04-10') | 2025 |
MONTH(date) | 월 추출 | MONTH('2025-04-10') | 4 |
DAY(date) | 일 추출 | DAY('2025-04-10') | 10 |
DATE_ADD(date, INTERVAL n DAY) | 날짜 더하기 | DATE_ADD('2025-04-10', INTERVAL 5 DAY) | 2025-04-15 |
DATE_SUB(date, INTERVAL n MONTH) | 날짜 빼기 | DATE_SUB('2025-04-10', INTERVAL 1 MONTH) | 2025-03-10 |
DATE_FORMAT(date, format) | 날짜 포맷 변경 | DATE_FORMAT(NOW(), '%Y-%m-%d') | 2025-04-10 |
함수 | 설명 | 예시 | 결과 예시 |
---|---|---|---|
CONCAT(str1, str2, ...) | 문자열 합치기 | CONCAT('Hello', ' ', 'World') | Hello World |
CONCAT_WS(separator, str1, str2, ...) | 구분자 넣어서 문자열 합치기 | CONCAT_WS('-', '2025', '04', '10') | 2025-04-10 |
UPPER(str) | 대문자로 변환 | UPPER('abc') | ABC |
LOWER(str) | 소문자로 변환 | LOWER('ABC') | abc |
LEFT(str, len) | 왼쪽에서 지정 길이만큼 추출 | LEFT('abcdef', 3) | abc |
RIGHT(str, len) | 오른쪽에서 지정 길이만큼 추출 | RIGHT('abcdef', 2) | ef |
SUBSTRING(str, start, length) | 특정 위치부터 부분 문자열 추출 | SUBSTRING('abcdef', 2, 3) | bcd |
CHAR_LENGTH(str) | 문자열 길이 (문자 수 기준) | CHAR_LENGTH('한글abc') | 6 |
LENGTH(str) | 문자열 길이 (바이트 수 기준) | LENGTH('한글abc') | 8 (UTF-8 기준) |
TRIM(str) | 앞뒤 공백 제거 | TRIM(' hello ') | hello |
LTRIM(str) | 왼쪽 공백 제거 | LTRIM(' hello') | hello |
RTRIM(str) | 오른쪽 공백 제거 | RTRIM('hello ') | hello |
REPLACE(str, from_str, to_str) | 문자열 치환 | REPLACE('a-b-c', '-', '+') | a+b+c |
INSTR(str, substr) | 부분 문자열 위치 반환 | INSTR('abcdef', 'cd') | 3 |
LOCATE(substr, str) | 부분 문자열 위치 반환 (INSTR 과 동일) | LOCATE('cd', 'abcdef') | 3 |
REVERSE(str) | 문자열 뒤집기 | REVERSE('abc') | cba |
REPEAT(str, count) | 문자열 반복 | REPEAT('ha', 3) | hahaha |
문제: 이번 달에 등록한 고객을 조회하라.
SELECT *
FROM Customers
WHERE registration_date >= DATE_FORMAT(CURDATE(), '%Y-%m-01');
- 서브쿼리
문제: 평균 주문 금액보다 큰 주문만 조회하라.
SELECT *
FROM Orders
WHERE order_amount > (
SELECT AVG(order_amount) FROM Orders
);
- UNION
문제: 회원과 비회원의 문의사항을 모두 조회하라.
SELECT user_id, question FROM MemberQuestions
UNION
SELECT guest_id, question FROM GuestQuestions;
user_id 와 guest_id가 다르면 안되는거 아니냐고 생각할 수도 있는데요, 달라도 상관없습니다. 칼럼 갯수와 데이터 타입이 같으면 됩니다. 그리고 첫번째 조회한 select 의 column user_id를 기준으로 데이터가 조회됩니다.
- IN / NOT IN
문제: VIP 고객만 주문내역 조회
SELECT *
FROM Orders
WHERE customer_id IN (
SELECT customer_id FROM Customers WHERE is_vip = TRUE
);
문제: 주문한 적 없는 고객을 조회
SELECT customer_id, name
FROM Customers
WHERE customer_id NOT IN (
SELECT customer_id FROM Orders
);
- CASE WHEN 문제: 주문 금액에 따라 등급을 표시
SELECT order_id,
CASE
WHEN order_amount >= 100000 THEN 'High'
WHEN order_amount >= 50000 THEN 'Medium'
ELSE 'Low'
END AS grade
FROM Orders;
- 사용자별 최근 주문 내역 (JOIN + 서브쿼리)
SELECT u.id AS user_id, u.name, o.id AS order_id, o.order_date
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.order_date = (
SELECT MAX(o2.order_date)
FROM orders o2
WHERE o2.user_id = u.id
);
- 월별 매출 집계 (GROUP BY + 날짜 함수)
SELECT DATE_FORMAT(order_date, '%Y-%M') AS month,
SUM(total_amount) AS monthly_sales
FROM orders
GROUP BY DATE_FORMAT(order_date, '%Y-%m')
ORDER BY month;
- 누적 매출과 월별 순위 (윈도우 함수)
SELECT
customer_id,
DATE_FORMAT(order_date, '%Y-%m') AS month,
SUM(total_amount) AS monthly_total,
SUM(SUM(total_amount)) OVER (
PARTITION BY customer_id
ORDER BY DATE_FORMAT(order_date, '%Y-%m')
) AS cumulative_total,
RANK() OVER (
PARTITION BY customer_id
ORDER BY SUM(total_amount) DESC
) AS monthly_rank
FROM orders
GROUP BY customer_id, DATE_FORMAT(order_date, '%Y-%m');
- 다중 조건 CASE WHEN으로 상태 처리
SELECT order_id,
CASE
WHEN status = 'shipped' AND delivery_date < CURRENT_DATE THEN '배송 완료'
WHEN status = 'shipped' THEN '배송 중'
WHEN status = 'pending' THEN '대기 중'
ELSE '기타'
END AS order_status
FROM orders;
- 최근 일주일 내 활동한 유저 목록 (EXISTS + 서브쿼리) not exists 추가
SELECT id, name
FROM users u
WHERE EXISTS (
SELECT 1
FROM user_logs l
WHERE l.user_id = u.id
AND l.action_time >= CURDATE() - INTERVAL 7 DAY
);
- 조건에 따라 다른 통계 계산 (CASE + 집계함수) 각 부서(department_id)별로 전체 직원 수, 남자 수(M), **여자 수(F)**를 집계해주세요
SELECT department_id,
COUNT(*) AS total,
SUM(CASE WHEN gender = 'M' THEN 1 ELSE 0 END) AS male_count,
SUM(CASE WHEN gender = 'F' THEN 1 ELSE 0 END) AS female_count
FROM employees
GROUP BY department_id;
- 카테고리별 매출 TOP 1 상품 (ROW_NUMBER)
SELECT *
FROM (
SELECT category_id, product_id, SUM(amount) AS total_sales,
ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY SUM(amount) DESC) AS rn
FROM sales
GROUP BY category_id, product_id
) ranked
WHERE rn = 1;
- 다중 테이블 JOIN + 집계 + 정렬
문제: 고객 이름과 그들이 가장 최근에 주문한 주문일과 금액을 조회하라.
SELECT c.customer_name, o.order_date, o.order_amount
FROM Customers c
JOIN Orders o ON c.customer_id = o.customer_id
WHERE o.order_date = (
SELECT MAX(o2.order_date)
FROM Orders o2
WHERE o2.customer_id = c.customer_id
)
ORDER BY o.order_date DESC;
- 윈도우 함수 (RANK, ROW_NUMBER) 문제: 고객별 주문 금액 상위 1~3건을 조회하라.
SELECT * FROM (
SELECT customer_id, order_id, order_amount,
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_amount DESC) AS rn
FROM Orders
) ranked_orders
WHERE rn <= 3;
- 복합 조건 + EXISTS 문제: 적어도 한 번 100만원 이상 주문한 고객 조회 조인으로 해서 where 절로 필터링 걸어도 되는데요, 단지 조건 존재 여부만 확인하고 싶을 때 사용합니다.
SELECT *
FROM Customers c
WHERE EXISTS (
SELECT 1
FROM Orders o
WHERE o.customer_id = c.customer_id
AND o.order_amount >= 1000000
);
- CTE (Common Table Expression) 사용
문제: 주문 수가 많은 상위 5명의 고객 조회
WITH OrderCounts AS (
SELECT customer_id, COUNT(*) AS order_count
FROM Orders
GROUP BY customer_id
)
SELECT c.customer_name, oc.order_count
FROM OrderCounts oc
JOIN Customers c ON c.customer_id = oc.customer_id
ORDER BY oc.order_count DESC
LIMIT 5;
- 특정 유저가 특정 상품에 리뷰를 작성했는지 여부 확인
문제: user_id와 product_id를 기준으로 해당 유저가 리뷰를 작성했는지 확인하라.
SELECT CASE
WHEN EXISTS (
SELECT 1
FROM Reviews
WHERE user_id = 101 AND product_id = 202
) THEN '작성함'
ELSE '미작성'
END AS review_status;