[DB] SQL 고급1

이현경·2026년 4월 4일

Database

목록 보기
6/13

1. 내장함수, NULL, 비교문

1-1. SQL 내장 함수

  • SQL 함수의 분류
    • 내장 함수 : DBMS가 제공
    • 사용자 정의 함수 : 사용자가 필요에 따라 직접 만듦
  • SQL 내장 함수
    • 상수나 속성 이름을 입력값으로 받아 단일 값을 결과로 반환함
    • 모든 내장 함수는 사용될 때 유효한 입력값을 받아야 함
    • SELECT 절과 WHERE 절, UPDATE 절 등에서 모두 사용 가능
  • MySQL에서 제공하는 주요 내장 함수

숫자 함수

  • SQL 문에서는 수학의 기본적인 사칙 연산자(+, -, *, /)와 나머지(%) 연산자 기호를 그대로 사용
  • MySQL은 연산자 중 빈도가 높은 것을 내장 함수 형태로 제공
  • 숫자 함수의 종류
  • ROUND 함수
    -- m이 양수일 때
    SET @VALUE := 1.234;
    SELECT ROUND(@VALUE),    -- 1
    	   ROUND(@VALUE, 1), -- 1.2
    	   ROUND(@VALUE, 2); -- 1.23
    
    -- m이 음수일 때
    SET @VALUE := 123456;
    SELECT ROUND(@VALUE, -1), -- 123,460
    	  ROUND(@VALUE, -2), -- 123,500
    	  ROUND(@VALUE, -3); -- 123,000
  • 숫자 함수의 연산
    • 숫자 함수에는직접 숫자를 입력할 수도 있지만 열 이름을 사용할 수도 있음

    • 여러 함수를 복합적으로 사용할 수도 있음

      -- 고객별 평균 주문 금액을 100원 단위로 반올림한 값을 구하시오.
      SELECT custid '고객번호', ROUND(SUM(saleprice)/COUNT(*), -2) '평균금액'
      FROM orders
      GROUP BY custid;

문자 함수

  • 문자 함수의 종류

CONCAT 함수

  • 참고) https://extbrain.tistory.com/52
    -- 사용법: CONCAT(문자열1, 문자열2 [, 문자열3 ...])
    SELECT CONCAT('데베 ','실습 ','중이다','!'); -- 데베 실습 중이다!
    
    -- 컬럼 데이터 합치기
    SELECT CONCAT(name, ' 전번: ', phone) AS '사용자 전번' FROM customer;

LPAD/RPAD 함수

  • 두 번째 인자는 최종적으로 완성될 전체 글자 수를 의미한다.
    SELECT LPAD('중요한 내용!', 10, '*'); -- ***중요한 내용!
    SELECT RPAD('필기중~', 6, '!'); -- 필기중~!!

REPLACE 함수

  • 무조건 전체 변경으로만 동작한다.
    -- 도서 제목에 야구가 포함된 도서를 농구로 변경한 후 도서 목록을 나타내시오.
    SELECT bookid, REPLACE(bookname, '야구', '농구') bookname, publisher, price
    FROM book;
  • 참고) 특정 위치만 변경하고 싶다면 INSERTREGEXP_REPLACE를 사용해야 한다.
    -- 방법1) INSERT(원본문자열, 시작위치, 덮어쓸길이, 새글자)
    SELECT INSERT('바나나 맛 바나나 우유', 1, 3, '딸기'); 
    
    -- 방법2) REGEXP_REPLACE(원본, 찾을글자, 바꿀글자, 시작위치, 발생순번)
    SELECT REGEXP_REPLACE('바나나 맛 바나나 우유', '바나나', '딸기', 1, 1);
    • 결과는 '딸기 맛 바나나 우유'이다.
    • REGEXP_REPLACE는 MySQL 8.0 이상이어야 한다.
    • 지금 알았는데 SQL은 모든 인덱스가 1부터 시작한다고 한다…!!😱

LENGTH, CHAR_LENGTH 함수

  • LENGTH()는 바이트 수를 가져오는 함수 (영어: 1byte, 한글: 3byte)
  • CHAR_LENGTH 함수는 문자의 수를 가져오는 함수 (공백 포함)
    -- 굿스포츠에서 출판한 도서의 제목과 제목의 문자 수, 바이트 수를 나타내시오.
    SELECT bookname '제목', CHAR_LENGTH(bookname) '문자수', LENGTH(bookname) '바이트수'
    FROM book
    WHERE publisher LIKE '굿스포츠';

이름 가리기 실습

  • 문자함수를 이용해서 이름의 첫 글자와 마지막 글자만 남기고 가운데는 *로 표시
    -- 1) 이름이 딱 3자인 경우
    SELECT
    	CONCAT(
    		LEFT(NAME, 1), '*', RIGHT(NAME, 1)
    	) AS masked_name
    FROM customer
    WHERE CHAR_LENGTH(NAME) = 3;
    
    -- 2) 이름이 3자 이상인 경우
    SELECT
    	CONCAT(
    		LEFT(NAME, 1),
    		repeat('*', CHAR_LENGTH(NAME)-2), -- 중간 글자 수만큼 * 붙이기
    		RIGHT(NAME, 1)
    	) AS masked_name
    FROM customer
    WHERE CHAR_LENGTH(NAME) >= 3;
    
    -- 3) 이름이 1자 이상인 경우
    SELECT
    	case
    		when CHAR_LENGTH(NAME) = 2
    			then CONCAT(LEFT(NAME, 1), '*') -- 이름이 2자면 첫 글자 + *
    		when CHAR_LENGTH(NAME) > 2
    			then CONCAT(
    				LEFT(NAME, 1), repeat('*', CHAR_LENGTH(NAME)-2), RIGHT(NAME, 1)
    				)
    		ELSE NAME -- 그 외는 원본 그대로
    	END AS masked_name
    FROM customer;

날짜·시간 함수

  • 날짜와 시간 부분을 나타내는 인수는 ‘format’으로 표기

  • format은 날짜 형식 지정자로 날짜와 시간 부분을 표기하기 위해 특별한 규칙을 가짐

  • 날짜·시간 함수의 종류

  • format의 주요 지정자

    • format 인자는 날짜·시간 함수를 필요에 따라 인자로 사용하기도 함

      SELECT SYSDATE(), DATE_FORMAT(SYSDATE(), '%Y%m%d:%H%i%s');

날짜, 시간함수 사용시 주의할 점

  • DBMS마다 이름과 동작, 의미가 다르다
    • MySQL : NOW(), CURDATE(), DATE_ADD()
    • Oracle : SYSDATE(), ADD_MONTH(), TO_CHAR()
    • PostgresSQL : CURRENT_TIMESTAMP, INTERVAL
  • 타임존(Time Zone) 문제
    • Now()나 CURRENT_TIMESTAMP는 DB 서버의 타임존을 기준으로 반환
    • 서버와 사용자가 다른 지역에 있으면 시간이 어긋날 수 있으므로 필요하다면 CONVERT_TZ()같은 함수로 변환하거나, 애플리케이션에서 타임존을 맞춰야 함
  • 날짜 포맷 출력
    • MySQL: DATE_FORMAT()
    • Oracle/Postgres: TO_CHAR()
  • NULL 처리
    • 날짜 컬럼이 NULL이면 함수 적용 시 에러가 발생함
    • 기본값을 지정 습관 들이기: MySQL - IFNULL(), 표준 SQL - COALESCE()
  • 성능 고려
    • select에서 조회용으로 사용

      • WHERE, JOIN, ORDER BY 같은 검색, 정렬에서는 함수로 속성을 변환하지 않기
    • 함수로 컬럼을 변환하면 인덱스에 저장된 값이 아니라 함수 결과를 새로 계산하고 풀 데이터 스캔이 발생할 가능성이 높음

      -- 풀 데이터 스캔이 발생할 확률↑↑
      WHERE DATE(orer_date) =2026-03-28-- 원본 자료형 그대로 검색 -> 매우 빠름!!
      WHERE order_date >=2026-03-28 00:00:00AND order_date <2026-03-29 00:00:00

MariaDB에서의 NOW(), SYSDATE()

  • NOW()
    • 쿼리 실행 시작 시점의 시간을 반환
    • 같은 쿼리 내에서는 항상 동일한 값으로 유지
  • SYSDATE()
    • 함수 호출 순간의 시스템 시간을 반환
    • 같은 쿼리 내에서도 호출 시점마다 값이 달라짐
SELECT NOW(), SLEEP(5), NOW(); -- 결과값 동일
SELECT SYSDATE(), SLEEP(5), SYSDATE(); -- 5초 차이남

STR_TO_DATE 함수, DATE_FORMAT 함수

  • STR_TO_DATE 함수는 CHAR형으로 저장된 날짜를 DATE형으로 변환함
  • DATE_FORMAT 함수는 STR_TO_DATE 함수와 반대로 날짜형을 문자형으로 변환함
-- 마당서점이 2024년 7월 7일에 주문받은 도서의 주문번호, 주문일, 고객번호, 도서번호를 모두 나타내시오.
-- 단, 주문일은 '%Y-%m-%d' 형태로 표시한다.
SELECT orderid '주문번호', DATE_FORMAT(orderdate, '%Y-%m-%d') '주문일',
	   custid '고객번호', bookid '도서번호'
FROM orders
WHERE orderdate=STR_TO_DATE('20240707', '%Y%m%d');

ADDDATE(date, interval)

  • 지정한 날짜에 일(day) 또는 시간(interval)을 더해 새로운 날짜를 반환하는 함수
  • 날짜형 데이터는 -+를 사용하여 원하는 날짜로부터 이전과 이후를 계산할 수 있음
SELECT ADDDATE('2024-07-01', INTERVAL -5 DAY) BEFORE5, -- 5일 전
	   ADDDATE('2024-07-01', INTERVAL 5 DAY) AFTER5; -- 5일 후

-- 마당서점은 주문일로부터 10일 후에 매출을 확정한다. 각 주문의 확정일자를 구하시오.
SELECT orderid '주문번호', orderdate '주문일',
	   ADDDATE(orderdate, INTERVAL 10 DAY) '확정'
FROM orders;

SYSDATE 함수

  • MySQL 데이터베이스에 설정된 현재 날짜와 시간을 반환하는 함수
-- DBMS 서버에 설정된 현재 날짜와 시간, 요일을 확인하시오.
SELECT SYSDATE(), DATE_FORMAT(SYSDATE(), '%Y/%m/%d %a %h:%i') 'SYSDATE_1';



1-2. NULL 값 처리

  • NULL 값
    • 아직 지정되지 않은 값 → 값을 알 수도 없고 적용할 수도 없다는 뜻
    • NULL 값은 ‘0’, ‘’(빈 문자), ‘ ‘(공백) 등과 다른 특별한 값
    • NULL 값은 비교 연산자로 비교할 수 없음
    • NULL 값의 연산을 수행하면 결과 역시 NULL 값으로 반환됨
  • NULL 값에 대한 연산과 집계 함수
    • ‘NULL+숫자’, ‘NULL+문자열’ 연산 결과는 NULL
    • 집계 함수를 계산할 때 NULL이 포함된 행은 집계에서 빠짐
    • 해당되는 행이 하나도 없을 경우 SUM, AVG 함수의 결과는 NULL이 되고, COUNT 함수의 결과는 0이 됨
-- 결과: NULL
SELECT price+100
FROM book
WHERE bookid=11;

SELECT SUM(price), AVG(price), COUNT(*), COUNT(price)
FROM book;

SELECT SUM(price), AVG(price), COUNT(*)
FROM book
WHERE bookid>22;
  • IFNULL 함수
    • NULL 값을 다른 값으로 대치하여 연산하거나 다른 값을 출력하는 함수

    • IFNULL 함수를 사용하면 NULL 값을 임의의 다른 값으로 변경 가능

      IFNULL(속성,) -- 속성값이 NULL이면 '값'으로 대치
      
      -- 이름, 전화번호가 포함된 고객 목록을 나타내시오. 단 전화번호가 없는 고객은 '연락처없음'으로 표시하시오.
      SELECT NAME '이름', IFNULL(phone, '연락처없음') '전화번호'
      FROM customer;
  • IFNULL vs COALESCE
    특징IFNULLCOALESCE
    인자 개수2개만 가능 (val1, val2)무제한 (val1, val2, ..., valN)
    표준 여부MySQL/MariaDB 전용SQL 표준 (모든 DB 지원)
    작동 방식IFNULL(A, B): A가 NULL이면 BCOALESCE(A, B, C): NULL이 아닌 첫 값
    유연성낮음 (중첩 필요)높음



1-3. 행번호 출력

MySQL에서 변수는 이름 앞에 @ 기호를 붙이며 치환문에는 SET:= 기호를 사용한다

-- 고객 목록에서 고객번호, 이름, 전화번호를 앞의 2명만 나타내시오.
SET @seq:=0;

SELECT (@seq:=@seq+1) '순번', custid, NAME, phone
FROM customer
WHERE @seq < 2;

-- LIMIT 2로 순번 없이 2개의 튜플만 출력
SELECT custid, name, phone
FROM customer
LIMIT 2;



1-4. CASE WHEN

  • 조건에 따라 다른 값을 반환
  • IF-THEN-ELSE와 같으며 집계함수와 함께 쓰거나 출력 컬럼을 가공할 때 유용
  • 형식
    CASE
    	WHEN 조건1 THEN 결과1
    	WHEN 조건2 THEN 결과2
    	...
    	ELSE 기본값
    END AS 별칭
    • WHEN: 조건을 지정
    • THEN: 조건이 참일 때 반환할 값
    • ELSE: 모든 조건이 거짓일 때 반환할 기본값
    • END: CASE 문 종료
  • 단순 조건 : 책 가격 분류
    SELECT bookname,
    	case
    		when price>20000 then '고가'
    		when price BETWEEN 10000 AND 20000 then '중간'
    		ELSE '저가'
    	END AS price_category
    FROM book;
  • 집계함수와 함께
    -- 년별 매출
    SELECT
    	SUM(case when YEAR(orderdate) = 2024 then saleprice ELSE 0 END) AS '2024년 매출',
    	SUM(case when YEAR(orderdate) = 2025 then saleprice ELSE 0 END) AS '2025년 매출',
    	SUM(case when YEAR(orderdate) = 2026 then saleprice ELSE 0 END) AS '2026년 매출'
    FROM orders;
    
    -- 월별 매출
    SELECT
    	MONTH(orderdate) AS MONTH,
    	SUM(case when saleprice >= 20000 then saleprice ELSE 0 END) AS high_sales,
    	SUM(case when saleprice BETWEEN 10000 AND 19999 then saleprice ELSE 0 END) AS mid_sales,
    	SUM(case when saleprice < 10000 then saleprice ELSE 0 END) AS low_sales
    FROM orders
    GROUP BY MONTH(orderdate)
    ORDER BY MONTH;




2. 부속질의

가독성, 클린코드 <<< ⭐응답시간

  • 부속질의
    • 하나의 SQL 문 안에 다른 SQL 문이 중첩된 질의
    • 주로 메일 쿼리의 조건에 따라 서브쿼리의 결과를 가져와서 메인 쿼리에서 사용하는 용도로 활용됨
    • 다른 테이블에서 가져온 데이터로 현재 테이블에 있는 정보를 찾거나 가공하는 등의 작업을 수행할 수 있음
    • 예) 테이블의 관계를 기초로 박지성 고객의 주문 내역을 확인하려면?
      • 조인을 사용: Customer 테이블과 Orders 테이블의 고객번호로 조인한 후 필요한 데이터를 추출
      • 부속질의를 사용: Customer 테이블에서 박지성 고객의 고객번호를 찾고, 찾은 고객번호를 바탕으로 Orders 테이블에서 확인
  • 두 테이블을 연관시킬 때 조인을 선택할지 부속질의를 선택할지 여부는 데이터의 형태와 양에 따라 달라짐
    • EXPLAIN 활용
      • Rows: 쿼리가 훑고 지나간 행의 수
      • Filtered: 실제 결과로 남은 데이터의 비율
      • Extra: Using index(😎) / Using filesort(😱)
    • Spring 프로젝트에서 가독성 좋은, JPA가 생성해주는 메서드 쓰다가 서비스가 다운될 수 있음
      • SQL 로그분석 / EXPLAIN / 인덱싱 전략 / 응답시간 측정이 필요함

✔️ 실무적 예외: 부속질의가 유리할 때

  • 필터링 대상이 매우 적을 때: 서브쿼리의 결과값이 단 하나(unique)임이 보장되고, 메인 테이블의 양이 방대할 때는 서브쿼리가 아주 명확한 가이드라인 역할을 함
  • 복잡한 집계가 포함될 때: 조인으로 풀면 중복 데이터가 너무 많이 발생하여 계산이 꼬이는 경우, 서브쿼리로 미리 계산한 ‘가상 테이블’을 만드는 것이 유리
  • 부속질의의 종류


2-1. 중첩질의 : WHERE 부속질의

  • 보통 데이터를 선택하는 조건 혹은 술어와 같이 사용됨

  • 중첩질의 술어/연산자의 종류

  • 비교 연산자

    • 비교 연산자 사용 시 부속질의가 반드시 단일 행, 단일 열을 반환해야 하며, 아닐 경우 질의를 처리할 수 없음
    • 주질의의 대상 열 값과 부속질의의 결과 값을 비교 연산자에 적용하여, 참인 경우에만 주질의의 해당 열을 출력함
  • 부속질의: 단순히 쿼리 안에 들어간 SELECT로 독립 실행 가능

    -- 평균 주문금액 이하의 주문에 대해서 주문번호와 금액을 나타내시오
    SELECT orderid, saleprice                            
    FROM orders                                          
    WHERE saleprice<=(SELECT AVG(saleprice) FROM orders);
  • 상관질의: 부속질의 중에서도 메인 쿼리의 컬럼을 참조해서, 메인 쿼리의 각 행마다 실행되는 경우

    -- 각 고객의 평균 주문금액보다 큰 금액의 주문 내역에 대해서 주문번호, 고객번호, 금액을 나타내시오.
    SELECT orderid, custid, saleprice                            
    FROM orders od1
    WHERE saleprice > (SELECT AVG(saleprice)
    				FROM orders od2
    				WHERE od1.custid=od2.custid);

IN, NOT IN(집합 연산자)

  • IN 연산자는 주질의의 속성값이 부속질의에서 제공한 결과 집합에 있는지 확인하는 역할을 함
  • 주질의는 WHERE 절에 사용되는 속성값을 부속질의의 결과 집합과 비교해 하나라도 있으면 참이 됨
  • NOT IN 연산자에서는 이와 반대로 값이 존재하지 않으면 참이 됨
-- 대한민국에 거주하는 고객에게 판매한 도서의 총판매액을 구하시오.
SELECT SUM(saleprice) 'total'
FROM orders
WHERE custid IN (SELECT custid
				 FROM customer
				 WHERE address LIKE '%대한민국%');

ALL, SOME/ANY(한정 연산자)

  • 구문 구조: scalar_expression { 비교연산자 } { ALL | SOME | ANY } (부속질의)
-- 3번 고객이 주문한 도서의 최고 금액보다 더 비싼 도서를 구입한 주문의 주문번호와 판매금액을 보이시오.
SELECT orderid, saleprice                                            
FROM orders                                                          
WHERE saleprice> ALL (SELECT saleprice FROM orders WHERE custid='3');

EXISTS, NOT EXISTS(존재 연산자)

  • 데이터의 존재 여부를 확인하는 연산자
  • 구문 구조: WHERE [NOT] EXISTS (부속질의)
-- EXISTS 연산자를 사용하여 대한민국에 거주하는 고객에게 판매한 도서의 총판매액을 구하시오.
SELECT SUM(saleprice) 'total'
FROM orders od
WHERE EXISTS (SELECT *
			  FROM customer cs
			  WHERE address LIKE '%대한민국%' AND cs.custid=od.custid);



2-2. 스칼라 부속질의 : SELECT 부속질의

  • 부속질의의 결과 값을 단일 행, 단일 열의 스칼라 값으로 반환함
  • 만약 결과 값이 다중 행이거나 다중 열이라면 DBMS는 그중 어떠한 행과 열을 출력해야 하는지 알 수 없어 에러를 출력함
  • 결과가 없는 경우에는 NULL 값을 출력함
-- 마당서점의 고객별 판매액을 나타내시오(고객이름과 고객별 판매액 출력)
SELECT (SELECT NAME
		FROM customer cs
		WHERE cs.custid=od.custid) 'name', SUM(saleprice) 'total'
FROM orders od
GROUP BY od.custid;

-- join 방식
SELECT od.custid, cs.name, SUM(od.saleprice) AS total_sales
FROM orders od
JOIN customer cs ON od.custid=cs.custid
GROUP BY od.custid, cs.name
ORDER BY total_sales DESC;
  • 스칼라 부속질의는 SELECT 문과 함께 UPDATE 문에서도 사용할 수 있음
    -- Orders 테이블에 각 주문에 맞는 도서이름을 입력하시오.
    ALTER TABLE Orders ADD bname VARCHAR(40);
    UPDATE Orders
    SET bname = (SELECT bookname
    			FROM Book
    			WHERE Book.bookid=Orders.bookid);



2-3. 인라인 뷰 : FROM 부속질의

  • 뷰: 기존 테이블로부터 일시적으로 만들어진 가상 테이블
  • -- 고객번호가 2 이하인 고객의 판매액을 나타내시오 (고객이름과 고객별 판매액 출력)
    SELECT cs.name, SUM(od.saleprice) 'total'
    FROM (SELECT custid, name
    			FROM Customer
    			WHERE custid<=2) cs,
    			Orders od
    WHERE cs.custid=od.custid
    GROUP BY cs.name;
    • 가상 테이블을 만들고 od와 조인한 방식
    • cs와 od를 동등조인한 후 고객번호≤2인 고객만 출력하는 형태보다 처리성능을 높일 수 있다
    • 중복 데이터를 제거하는 방법보다 필요한 데이터를 먼저 뽑아쓰는 방법이 효율적이다.



2-4. CTE(Common Table Expression, WITH)

  • 복잡한 SQL 쿼리 내에서 일시적인 결과 집합(임시 테이블)을 정의하여, 가독성을 높이고 쿼리를 구조화
  • 메인 쿼리에서 일반 테이블처럼 재사용하거나, 재귀 쿼리 구현에 활용됨
  • 쿼리 실행 시에만 존재하며, 데이터베이스에 영구적으로 저장되지 않음
  • 구문 형식
    WITH cte_name AS (
    	-- SELECT ...
    	-- JOIN, GROUP BY 등의 복잡한 로직
    )
    
    -- 위에서 정의한 cte_name을 테이블처럼 사용
    SELECT ...
    FROM cte_name;
  • 인라인 뷰 → CTE 변환
    -- 일라인 뷰 형식
    SELECT sub.custid, sub.name, sub.bookname, sub.saleprice
    FROM (
    	SELECT o.custid, c.name, b.bookname, o.saleprice
    	FROM orders o
    	JOIN customer c ON o.custid=c.custid
    	JOIN book b ON o.bookid=b.bookid) sub
    WHERE sub.saleprice > 10000;
    
    -- CTE 형식
    WITH order_details AS (
    	SELECT o.custid, c.name, b.bookname, o.saleprice
    	FROM orders o
    	JOIN customer c ON o.custid=c.custid
    	JOIN book b ON o.bookid=b.bookid
    )
    
    SELECT custid, name, bookname, saleprice
    FROM order_details
    WHERE salerprice > 10000;
  • CTE로 변환 후 다른 조건이나 분석도 가능
    WITH sales_summary AS (
    	SELECT od.custid, cs.name, SUM(od.saleprice) AS total_sales
    	FROM orders od
    	JOIN customer cs ON od.custid=cs.custid
    	GROUP BY od.custid, cs.name
    )
    -- 1. 매출별로 내림차순하여 보기
    SELECT custid, NAME, total_sales
    FROM sales_summary
    ORDER BY total_sales DESC;
    
    -- 2. 평균 매출보다 높은 고객 조회
    SELECT custid, NAME, total_sales
    FROM sales_summary
    WHERE total_sales > (
    	SELECT AVG(total_sales)
    	FROM sales_summary
    )
    ORDER BY total_sales DESC;
  • 주의) WITH CTE명 AS (SELECT …); 는 하나의 거대한 문장이다
    • 얼핏보면 WITH 절이 하나의 문장같지만 괄호 뒤 세미콜론이 없다
    • 따라서 WITH절 뒤에 오는 세미콜론이 끝나면 가상 테이블은 바로 사라진다!
  • 연속 사용 가능
    • WITH 절 안에서 쉽표를 쓰면 가상 테이블을 여러 개 연달아 만들 수 있으며, 앞에서 만든 가상 테이블을 그다음 가상 테이블이 가져다 쓸 수도 있다

      WITH Step1 AS (SELECT ...),
           Step2 AS (SELECT ... FROM Step1) -- 방금 만든 Step1을 사용
      SELECT * FROM Step2;



2-5. 윈도우 함수(Window Functions)

  • SQL 윈도우 함수
    • 테이블의 행과 행 간의 관계를 정의하여 데이터를 윈도우로 그룹화하여 사용하는 함수
    • 각 행에 대해 집계나 순위 계산 결과를 추가하여 사용
    • GROUP BY와 달리 행의 개수를 유지하면서 그룹 내 계산 결과를 각 행에 표시
    • 복잡한 조인 없이 행 간 계산, 순위 매기기, 누적합계 등을 수행할 때 유용
    • OVER 절과 함께 사용되어 PARTITION BY와 ORDER BY로 범위와 순서를 지정
  • 구문 형식
    SELECT 함수명() OVER (PARTITION BY 컬럼명 ORDER BY 컬럼명)
    FROM 테이블명;
    • PARTITION BY: 계산을 수행할 그룹을 나눔
    • ORDER BY: 그 그룹 안에서 계산을 수행할 순서를 정함
  • 주요 윈도우 함수 유형
    • 순위 함수(Ranking): ROW_NUMBER(), RANK(), DENSE_RANK()
    • 집계 함수(Aggregate): SUM(), AVG(), COUNT(), MAX(), MIN()
    • 분석/값 함수(Value): LEAD(), LAG(), FIRST_VALUE(), LAST_VALUE()
  • 출판사별 도서의 매출순위를 구하고 출판사별, 매출 순위별 구하기
    SELECT b.publisher, b.bookname, 
    SUM(o.saleprice) AS total_sales,            
    	-- GROUP BY와 비슷하지만, 행을 줄이지 않고 원본행을 유지하며 그룹별 계산 결과를 붙임.
    	RANK() OVER (PARTITION BY b.publisher ORDER BY SUM(o.saleprice) DESC) AS rank_in_publisher
    FROM orders o  JOIN book b ON o.bookid = b.bookid
    GROUP BY b.publisher, b.bookname
    ORDER BY b.publisher, rank_in_publisher;  
  • 실행 순서: JOIN → GROUP BY → 집계 → 윈도우 함수 → SELECT → ORDER BY
profile
커피 한 잔의 여유를 아는 품격있는 여자

0개의 댓글