
+, -, *, /)와 나머지(%) 연산자 기호를 그대로 사용
-- 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 함수
-- 사용법: 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;INSERT나 REGEXP_REPLACE를 사용해야 한다.-- 방법1) INSERT(원본문자열, 시작위치, 덮어쓸길이, 새글자)
SELECT INSERT('바나나 맛 바나나 우유', 1, 3, '딸기');
-- 방법2) REGEXP_REPLACE(원본, 찾을글자, 바꿀글자, 시작위치, 발생순번)
SELECT REGEXP_REPLACE('바나나 맛 바나나 우유', '바나나', '딸기', 1, 1);REGEXP_REPLACE는 MySQL 8.0 이상이어야 한다.LENGTH, 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');
날짜, 시간함수 사용시 주의할 점
select에서 조회용으로 사용
함수로 컬럼을 변환하면 인덱스에 저장된 값이 아니라 함수 결과를 새로 계산하고 풀 데이터 스캔이 발생할 가능성이 높음
-- 풀 데이터 스캔이 발생할 확률↑↑
WHERE DATE(orer_date) = ‘2026-03-28’
-- 원본 자료형 그대로 검색 -> 매우 빠름!!
WHERE order_date >= ‘2026-03-28 00:00:00’ AND order_date < ‘2026-03-29 00:00:00’
MariaDB에서의 NOW(), SYSDATE()
SELECT NOW(), SLEEP(5), NOW(); -- 결과값 동일
SELECT SYSDATE(), SLEEP(5), SYSDATE(); -- 5초 차이남
STR_TO_DATE 함수, DATE_FORMAT 함수
-- 마당서점이 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)
-와 +를 사용하여 원하는 날짜로부터 이전과 이후를 계산할 수 있음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 함수
-- DBMS 서버에 설정된 현재 날짜와 시간, 요일을 확인하시오.
SELECT SYSDATE(), DATE_FORMAT(SYSDATE(), '%Y/%m/%d %a %h:%i') 'SYSDATE_1';
-- 결과: 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;
NULL 값을 다른 값으로 대치하여 연산하거나 다른 값을 출력하는 함수
IFNULL 함수를 사용하면 NULL 값을 임의의 다른 값으로 변경 가능
IFNULL(속성, 값) -- 속성값이 NULL이면 '값'으로 대치
-- 이름, 전화번호가 포함된 고객 목록을 나타내시오. 단 전화번호가 없는 고객은 '연락처없음'으로 표시하시오.
SELECT NAME '이름', IFNULL(phone, '연락처없음') '전화번호'
FROM customer;
| 특징 | IFNULL | COALESCE |
|---|---|---|
| 인자 개수 | 2개만 가능 (val1, val2) | 무제한 (val1, val2, ..., valN) |
| 표준 여부 | MySQL/MariaDB 전용 | SQL 표준 (모든 DB 지원) |
| 작동 방식 | IFNULL(A, B): A가 NULL이면 B | COALESCE(A, B, C): NULL이 아닌 첫 값 |
| 유연성 | 낮음 (중첩 필요) | 높음 |
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;
CASE
WHEN 조건1 THEN 결과1
WHEN 조건2 THEN 결과2
...
ELSE 기본값
END AS 별칭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;가독성, 클린코드 <<< ⭐응답시간⭐
EXPLAIN 활용✔️ 실무적 예외: 부속질의가 유리할 때
- 필터링 대상이 매우 적을 때: 서브쿼리의 결과값이 단 하나(unique)임이 보장되고, 메인 테이블의 양이 방대할 때는 서브쿼리가 아주 명확한 가이드라인 역할을 함
- 복잡한 집계가 포함될 때: 조인으로 풀면 중복 데이터가 너무 많이 발생하여 계산이 꼬이는 경우, 서브쿼리로 미리 계산한 ‘가상 테이블’을 만드는 것이 유리

보통 데이터를 선택하는 조건 혹은 술어와 같이 사용됨
중첩질의 술어/연산자의 종류


비교 연산자
부속질의: 단순히 쿼리 안에 들어간 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(집합 연산자)
-- 대한민국에 거주하는 고객에게 판매한 도서의 총판매액을 구하시오.
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);
-- 마당서점의 고객별 판매액을 나타내시오(고객이름과 고객별 판매액 출력)
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;
-- Orders 테이블에 각 주문에 맞는 도서이름을 입력하시오.
ALTER TABLE Orders ADD bname VARCHAR(40);
UPDATE Orders
SET bname = (SELECT bookname
FROM Book
WHERE Book.bookid=Orders.bookid);-- 고객번호가 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;WITH cte_name AS (
-- SELECT ...
-- JOIN, GROUP BY 등의 복잡한 로직
)
-- 위에서 정의한 cte_name을 테이블처럼 사용
SELECT ...
FROM cte_name;-- 일라인 뷰 형식
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;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 Step1 AS (SELECT ...),
Step2 AS (SELECT ... FROM Step1) -- 방금 만든 Step1을 사용
SELECT * FROM Step2;
SELECT 함수명() OVER (PARTITION BY 컬럼명 ORDER BY 컬럼명)
FROM 테이블명;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;