조회와 정렬

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의 작동 원리

SELECT DISTINCT customer_id, product_id는 데이터베이스에게 이렇게 요청하는 것과 같습니다.

"customer_idproduct_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