조회와 정렬
SELECT 열이름
FROM 테이블이름
WHERE 조건;

더 복잡한 조건
AND : 양쪽의 조건이 모두 참(True)일 때 최종적으로 참이 된다. '그리고' , '~이면서'의 의미
OR : 양쪽의 조건 중 하나라도 참(true)이면 최종적으로 참이 된다. '또는', '~이거나'의 의미
NOT : 주어진 조건을 부정한다. (뒤에서 알아볼 IN , LIKE, BETWEEN, NOT NULL 등과 함께 사용된다.)
BETWEEN,IN,LIKE, NOT LIKE , IS NULL
LIKE랑 와일드카드 , 언더스코어 같이 활용
ORDER BY 다중 정렬
products 테이블을 재고수량 내림차순, 가격 오름차순으로 정렬하기
SELECT * FROM products ORDER BY stock_quantity DESC, price ASC;
LIMIT 조회 결과 개수를 제한
ㅣㅣLIMIT 는 ORDER BY 뒤에 위치하며, 조회할 결과의 최대 개수를 제한하는 역할을 한다.
SELECT 열이름
FROM 테이블이름
WHERE 조건
ORDER BY 정렬기준
LIMIT 개수;
products 테이블에서 가장 비싼 상품 2개만 조회하기
SELECT * FROM products ORDER BY pirce DESC LIMIT 2;
실무의 핵심, 페이징 (Pagination) 처리 [문제 상황]
SELECT * FROM products ORDER BY product_id ASC LIMIT 4, 2;
네, LIMIT ?, ?
(또는 OFFSET LIMIT
) 방식의 페이지네이션은 중간에 데이터가 추가되면 '페이지 중복' 또는 '데이터 누락' 문제가 발생합니다.
이 문제의 가장 확실한 해결책은 **'커서 기반 페이징(Cursor-based Pagination)'**을 사용하는 것입니다.
## 무엇이 문제인가? (OFFSET 방식의 한계)
LIMIT 4, 2
는 앞에서 4개를 건너뛰고 그다음 2개를 가져오라는 의미입니다. **'순서'**를 기준으로 동작하기 때문에 문제가 발생합니다.
1. 첫 페이지 조회
사용자가 LIMIT 0, 2로 1페이지를 봅니다.
[1, 2]
2. 새 데이터 추가
사용자가 페이지를 보는 동안 product_id가 0인 새 상품이 추가됩니다.
전체 목록: [0, 1, 2, 3, 4, 5, 6]
3. 다음 페이지 조회
사용자가 LIMIT 2, 2로 다음 페이지를 요청합니다. DB는 새로운 목록에서 2개를 건너뛰므로, **[2, 3]**을 반환합니다.
🔴 결과:
중복 발생:
2
번 상품이 1페이지와 2페이지에 모두 나타납니다.데이터 누락:
1
번 상품은 어디에서도 볼 수 없게 됩니다.
## 해결책: 커서 기반 페이징
커서 기반 페이징은 '순서'가 아닌 **'특정 데이터의 위치'**를 기준으로 동작하여 이 문제를 해결합니다.
1. 첫 페이지 조회
사용자가 1페이지를 보고, 클라이언트는 마지막 상품인 **product_id=2**를 '커서'로 기억합니다.
2. 새 데이터 추가
product_id=0인 상품이 추가됩니다.
3. 다음 페이지 조회
클라이언트는 "2개를 건너뛰어 줘"가 아니라 "product_id가 2보다 큰 상품 2개를 줘" 라고 요청합니다.
SQL
SELECT * FROM products
WHERE product_id > 2 -- 마지막으로 봤던 커서(2)보다 큰 것
ORDER BY product_id ASC
LIMIT 2;
🟢 결과:
DB는 새 상품(id=0)의 존재와 상관없이, 정확히 product_id=2 다음인 **[3, 4]**를 반환합니다. 데이터 중복이나 누락이 발생하지 않습니다. 이 방식의 성능을 보장하려면 product_id에 반드시 인덱스가 있어야 합니다.
DISTINCT 중복 제거
SELECT DISTINCT customer_id from orders;
SELECT
절에는 DISTINCT
키워드를 한 번만 사용할 수 있기 때문입니다. DISTINCT
는 특정 컬럼이 아닌, 조회되는 행(Row) 전체에 적용되어 중복을 제거하는 역할을 합니다.
## DISTINCT
의 작동 원리
DISTINCT
의 작동 원리SELECT DISTINCT customer_id, product_id
는 데이터베이스에게 이렇게 요청하는 것과 같습니다.
"
customer_id
와product_id
, 이 두 값의 조합이 고유한 모든 행을 찾아줘."
데이터베이스는 (customer_id, product_id)
를 하나의 묶음(Tuple)으로 보고, 이 묶음의 중복을 제거합니다. 각 컬럼에 개별적으로 DISTINCT
를 적용하는 기능은 SQL 표준에 없습니다.
## 올바른 문법
따라서 DISTINCT
를 한 번만 사용하여 아래와 같이 작성해야 합니다.
SQL
SELECT DISTINCT customer_id, product_id
FROM orders
ORDER BY customer_id, product_id;
이렇게 하면 orders
테이블에서 고객 한 명이 특정 상품을 여러 번 주문했더라도, (고객 ID, 상품 ID)
조합은 결과에 단 한 번만 나타나게 됩니다.
NULL
데이터베이스에서 '값이 없음'을 나타내는 특별한 상태를 NULL이라고 한다. NULL은 숫자 0이나 빈 문자열 (' ') 과는 완전히 다른 개념이다. 말 그대로 '알 수 없는 값', '존재하지 않는 값'이다. 여기서 아주 중요한 함정이 있다. 상품 설명이 비어있는 상품을 찾기 위해 WHERE description = NULL 이라고 쓰면 될까? 절대 안된다. NULL은 특정 값이 아니기 때문에 등호(=)로 비교할 수 없다.
이는 SQL에서 NULL 이 '값이 없는 상태'를 의미하는 특별한 존재이기 때문이다. NULL은 0도 아니고, 공백 문자열(' ')도 아니다.
MYSQL은 NULL을 가장 작은 값으로 취급한다.
ORDER BY column ASC : NULL 값이 가장 먼저 나온다.
"상품 설명을 내림차순으로 정렬하되, 설명이 없는 상품( NULL )은 빨리 확인할 수 있게 맨 앞으로 보내주세요."

select product_id, name, description, description IS NULL from products
order by description IS NULL desc, description desc;
SELECT 절 안에서 데이터 가공
SELECT name, price, stock_quantity, price * stock_quantity FROM products
+,-,/,*,% 등 활용 가능
문자열
CONCAT() 함수로 문자열 합치기

CONCAT(string1, string2, ...)
CONCAT에는 붙이고 싶은 값을 , (쉼표)로 구분해서 넣으면 된다.
select concat_ws(' - ', name, email ,address) as customer_deatils from customers;

UPPER() / LOWER()
예시 이메일을 모두 대문자로 출력하기
SELECT email, UPPER(email) AS upper_email FROM customers;
LENGTH(), CHAR_LENGTH()
LENGTH() : 문자열의 길이를 바이트 단위로 변환한다.
CHAR_LENGTH() : 글자 수를 반환한다.

NULL 값 처리함수 IFNULL() 과 COALESCE()
IFNULL(표현식1, 표현식2)
IFNULL() 함수는 두 개의 인자를 받는다.
표현식1 : NULL인지 아닌지 검사할 컬럼이나 값.
표현식2 : 표현식1이 NULL일 경우 대신 반환할 값.
만약 표현식1이 NULL이 아니면 표현식1의 값을 그대로 반환하고, NULL이면 표현식2의 값을 반환한다.
SELECT
name,
IFNULL(description, '상품 설명 없음') AS description
FROM
products;
COALESCE() 함수는 IFNULL() 보다 조금 더 유연하다. 괄호 안에 여러 개의 인자를 전달할 수 있으며, 왼쪽부터 차례대로 확인해서 처음으로 NULL이 아닌 값을 반환한다. 모든 인자가 NULL이면 결국 NULL을 반환한다.
SELECT
name,
COALESCE(description, '상품 설명 없음') AS description
FROM
products;
이 쿼리는 IFNULL과 결과가 똑같다.
COALESCE() 가 유용한 상황?
COALESCE(short_description, long_description, '설명 없음')
이 상황에서 짧은 설명은 없고 긴 설명은 있으면 긴 설명을 쓴다.
긴 설명도 없으면 '설명 없음'이 된다.
이런 식으로 여러 대안을 순서대로 제시할 수 있다는 점에서 COALESCE() 가 IFNULL()보다 더 확장성이 좋다.
문자열 함수
UPPER(string)
문자열을 모두 대문자로 변환
SELECT UPPER('hello world');
→ HELLO WORLD
LOWER(string)
문자열을 모두 소문자로 변환
SELECT LOWER('Hello World');
→ hello world
SUBSTRING(...)
문자열의 일부를 추출
SELECT SUBSTRING('안녕하세요', 1, 2);
→ 안녕
TRIM(...)
문자열의 양쪽 공백을 제거
SELECT TRIM(' hello ');
→ hello
POSITION(...)
문자열 내 특정 문자의 위치를 찾음
SELECT POSITION('l' IN 'hello');
→ 3
CHAR_LENGTH(string)
문자열의 길이를 반환
SELECT CHAR_LENGTH('hello');
→ 5
숫자 함수
ABS(number)
숫자의 절댓값을 반환
SELECT ABS(-10);
→ 10
MOD(dividend, divisor)
나눗셈의 나머지를 반환
SELECT MOD(10, 3);
→ 1
ROUND(number, decimals)
숫자를 반올림
SELECT ROUND(123.456, 2);
→ 123.46
CEILING(number)
숫자보다 크거나 같은 가장 작은 정수
SELECT CEILING(10.1);
→ 11
FLOOR(number)
숫자보다 작거나 같은 가장 큰 정수
SELECT FLOOR(10.9);
→ 10
SQRT(number)
숫자의 제곱근을 반환
SELECT SQRT(9);
→ 3
POWER(base, exponent)
거듭제곱 값을 계산
SELECT POWER(2, 3);
→ 8
날짜 및 시간 함수
CURRENT_DATE
현재 날짜를 반환
SELECT CURRENT_DATE;
→ 2025-09-05
CURRENT_TIME
현재 시간을 반환
SELECT CURRENT_TIME;
→ 11:08:21
CURRENT_TIMESTAMP
현재 날짜와 시간(타임스탬프)을 반환
SELECT CURRENT_TIMESTAMP;
→ 2025-09-05 11:08:21
EXTRACT(field FROM source)
날짜에서 특정 필드(년, 월 등)를 추출
SELECT EXTRACT(YEAR FROM '2025-09-05');
→ 2025
조건 및 변환 함수
CAST(expression AS datatype)
데이터 타입을 변환
SELECT CAST('123' AS INT);
→ 123
(숫자)
COALESCE(value1, ...)
첫 번째로 NULL
이 아닌 값을 반환
SELECT COALESCE(NULL, 'A', 'B');
→ A
NULLIF(expr1, expr2)
두 값이 같으면 NULL
을 반환
SELECT NULLIF(10, 10);
→ NULL
집계 함수
AVG(expression)
평균값을 반환
SELECT AVG(price) FROM products;
COUNT(expression)
NULL
이 아닌 행의 수를 반환
SELECT COUNT(email) FROM users;
COUNT(*)
전체 행 수를 반환
SELECT COUNT(*) FROM users;
MAX(expression)
최댓값을 반환
SELECT MAX(price) FROM products;
MIN(expression)
최솟값을 반환
SELECT MIN(price) FROM products;
SUM(expression)
합계를 반환
SELECT SUM(sales) FROM orders;
DATE_FORMAT(date, format)
날짜를 지정된 format 형식의 문자열로 변환
Last updated