소프트웨어 기술(스타트업 위주)

sql

MySQL 윈도우 함수 자주 사용하는 목록

MySQL에서 자주 사용되는 윈도우 함수들을 간단한 설명과 함께 표로 정리한 문서입니다. 실무에서 자주 등장하는 패턴 위주로 구성되어 있습니다.

함수설명사용 예시
ROW_NUMBER()그룹 내에서 각 행에 순위를 부여 (중복 순위 없음)부서별 연봉 순위 매기기
RANK()공동 순위 허용 (동점자 건너뜀)대회 점수 순위에서 공동 순위 부여
SUM()누적 합계 계산유저별 주문 금액 누적 합계
AVG()누적 평균 계산유저별 평균 점수 계산

윈도우 함수 기본 구조

<윈도우 함수>() OVER (
  PARTITION BY <그룹 기준 컬럼>
  ORDER BY <정렬 기준 컬럼>
)
  • PARTITION BY: 그룹을 나누는 기준 (옵션)
  • ORDER BY: 각 그룹 안에서 정렬 기준

참고

  • 윈도우 함수는 WHERE이 아니라 SELECT 절에서 사용됨
  • GROUP BY 없이도 집계 가능한 장점이 있음
  • 서브쿼리 또는 CTE와 함께 사용 시 유용함

1. 현업에서 자주 사용하는 SQL 쿼리 문제

  1. 기본 SELECT + WHERE
    문제: 특정 고객의 정보를 조회하라.
SELECT *  
FROM Customers  
WHERE customer_id = 123;
  1. 집계 함수 (SUM, COUNT, AVG, MAX, MIN)
    문제: 각 고객이 지금까지 주문한 총 금액을 구하라.
SELECT customer_id, SUM(order_amount) AS total_spent  
FROM Orders  
GROUP BY customer_id;
  1. GROUP BY + HAVING
    문제: 1회 이상 주문한 고객만 조회하라.
SELECT customer_id, COUNT(*) AS order_count  
FROM Orders  
GROUP BY customer_id  
HAVING COUNT(*) > 0;
  1. 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;
  1. NULL 처리
    문제: 이메일이 없는 고객을 조회하라.
SELECT *  
FROM Customers  
WHERE email IS NULL;
  1. 날짜 관련

자주 사용되는 날짜, 문자 관련 함수 정리

함수설명예시결과 예시
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');
  1. 서브쿼리
    문제: 평균 주문 금액보다 큰 주문만 조회하라.
SELECT *  
FROM Orders  
WHERE order_amount > (
  SELECT AVG(order_amount) FROM Orders
);
  1. UNION
    문제: 회원과 비회원의 문의사항을 모두 조회하라.
SELECT user_id, question FROM MemberQuestions  
UNION  
SELECT guest_id, question FROM GuestQuestions;

user_id 와 guest_id가 다르면 안되는거 아니냐고 생각할 수도 있는데요, 달라도 상관없습니다. 칼럼 갯수와 데이터 타입이 같으면 됩니다. 그리고 첫번째 조회한 select 의 column user_id를 기준으로 데이터가 조회됩니다.

  1. 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
);
  1. 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;
  1. 사용자별 최근 주문 내역 (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
);
  1. 월별 매출 집계 (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;
  1. 누적 매출과 월별 순위 (윈도우 함수)
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');
  1. 다중 조건 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;
  1. 최근 일주일 내 활동한 유저 목록 (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
);
  1. 조건에 따라 다른 통계 계산 (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;
  1. 카테고리별 매출 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;
  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;
  1. 윈도우 함수 (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;
  1. 복합 조건 + 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
);
  1. 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;
  1. 특정 유저가 특정 상품에 리뷰를 작성했는지 여부 확인
    문제: 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;
Previous
데이터베이스(SQL)