개발자공부일기
SQL 기본 문법 본문
딱히 정리한적도 없기도하고 오랜만에 sql문제를 풀려하니 기억이 바로바로 안나서 재활겸 정리해봤습니다.
작성 순서
SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT/FETCH
논리적 실행 순서(DB가 처리하는 순서)
- FROM (JOIN 포함)
- WHERE
- GROUP BY
- HAVING
- SELECT
- ORDER BY
- LIMIT/FETCH
예시 테이블
USERS
| user_id | name | country | joined |
| 1 | Min | KR | 2021-03-10 |
| 2 | Jae | KR | 2022-10-05 |
| 3 | Alex | US | 2022-10-20 |
PRODUCTS
| product_id | product_name | category | unit_price |
| 10 | Tshirt | top | 20000 |
| 20 | Sneakers | shoes | 80000 |
| 30 | Cap | acc | 15000 |
ONLINE_SALE
| sale_id | user_id | product_id | created_date | qty | status |
| 1001 | 1 | 10 | 2022-10-01 | 1 | PAID |
| 1002 | 1 | 10 | 2022-10-02 | 1 | PAID |
| 1003 | 1 | 20 | 2022-11-01 | 1 | CANCELLED |
| 1004 | 2 | 10 | 2022-10-03 | 2 | PAID |
| 1005 | 2 | 30 | 2022-10-04 | 1 | PAID |
| 1006 | 2 | 30 | 2022-12-01 | 1 | PAID |
| 1007 | 3 | 10 | 2022-10-05 | 1 | PAID |
SELECT (무엇을 보여줄지)
SELECT는 결과 테이블의 “열(column) 구성”을 결정합니다.
기본 형태
SELECT sale_id, status
FROM ONLINE_SALE;
실행 결과
| sale_id | status |
| 1001 | PAID |
| 1002 | PAID |
| 1003 | CANCELLED |
| 1004 | PAID |
| 1005 | PAID |
| 1006 | PAID |
| 1007 | PAID |
별칭(alias) 붙이기
- 컬럼/표현식에 이름을 붙여 결과를 읽기 쉽게 만듭니다.(AS 생략가능)
SELECT sale_id id, created_date AS ordered_at
FROM ONLINE_SALE;
실행 결과
| id | ordered_at |
| 1001 | 2022-10-01 |
| 1002 | 2022-10-02 |
| 1003 | 2022-11-01 |
| 1004 | 2022-10-03 |
| 1005 | 2022-10-04 |
| 1006 | 2022-12-01 |
| 1007 | 2022-10-05 |
표현식/계산 컬럼
- 단가 * 수량, 날짜 가공, 문자열 합치기 같은 “계산된 열”을 만들 수 있습니다.
SELECT sale_id, qty, qty * 1000 AS dummy_amount
FROM ONLINE_SALE;
실행 결과
| sale_id | qty | dummy_amount |
| 1001 | 1 | 1000 |
| 1002 | 1 | 1000 |
| 1003 | 1 | 1000 |
| 1004 | 2 | 2000 |
| 1005 | 1 | 1000 |
| 1006 | 1 | 1000 |
| 1007 | 1 | 1000 |
DISTINCT (중복 제거)
- 결과에서 중복 행을 제거합니다.
SELECT DISTINCT user_id
FROM ONLINE_SALE;
실행 결과
| user_id |
| 1 |
| 2 |
| 3 |
주의
- DISTINCT는 “SELECT 결과의 전체 행” 기준으로 중복 제거합니다.
- GROUP BY와는 목적이 다릅니다(중복 제거 vs 집계).
FROM (어떤 데이터에서 가져올지)
FROM은 단순히 “테이블을 적는 곳”이 아니라, DB 관점에서 “작업 대상 행 집합을 만드는 단계”입니다.
실제 존재하는 테이블이 아니어도 된다는 얘기입니다. 예를 들면 JOIN으로 생성된 테이블이 있겠네요.
단일 테이블
SELECT *
FROM USERS;
실행 결과
| user_id | name | country | joined |
| 1 | Min | KR | 2021-03-10 |
| 2 | Jae | KR | 2022-10-05 |
| 3 | Alex | US | 2022-10-20 |
테이블 별칭(alias)
- JOIN에서 거의 필수로 쓰입니다.
SELECT u.user_id, u.name
FROM USERS u;
실행 결과
| user_id | name |
| 1 | Min |
| 2 | Jae |
| 3 | Alex |
JOIN은 FROM 단계에서 수행됩니다
- FROM에서 테이블을 붙여서 “더 큰 행 집합”을 만들고, 그 결과에 WHERE/GROUP BY를 적용합니다.
INNER JOIN (매칭되는 것만)
SELECT s.sale_id, u.name, p.product_name, s.qty
FROM ONLINE_SALE s
JOIN USERS u ON s.user_id = u.user_id
JOIN PRODUCTS p ON s.product_id = p.product_id;
실행 결과
| sale_id | name | product_name | qty |
| 1001 | Min | Tshirt | 1 |
| 1002 | Min | Tshirt | 1 |
| 1003 | Min | Sneakers | 1 |
| 1004 | Jae | Tshirt | 2 |
| 1005 | Jae | Cap | 1 |
| 1006 | Jae | Cap | 1 |
| 1007 | Alex | Tshirt | 1 |
INNER JOIN외에도 다양한 JOIN이 있지만 다음에 따로 알아보도록 하고 기본적인것만 살펴봅시다.
FROM 서브쿼리(인라인 뷰)
- FROM에서 “집계 결과”를 임시 테이블처럼 만들고, 바깥 쿼리에서 다시 필터링/정렬합니다.
SELECT x.user_id, x.cnt
FROM (
SELECT user_id, COUNT(*) AS cnt
FROM ONLINE_SALE
GROUP BY user_id
) x
WHERE x.cnt >= 2;
실행 결과
| user_id | cnt |
| 1 | 3 |
| 2 | 3 |
WHERE (행 필터링)
WHERE는 “GROUP BY 이전”에 개별 행을 걸러냅니다.
같다
SELECT sale_id, status
FROM ONLINE_SALE
WHERE status = 'PAID';
실행 결과
| sale_id | status |
| 1001 | PAID |
| 1002 | PAID |
| 1004 | PAID |
| 1005 | PAID |
| 1006 | PAID |
| 1007 | PAID |
다르다
SELECT sale_id, status
FROM ONLINE_SALE
WHERE status <> 'CANCELLED';
실행 결과
(가상데이터에서 CANCELLED는 1003 한 건뿐이라, 위 결과와 동일하게 나옵니다.)
| sale_id | status |
| 1001 | PAID |
| 1002 | PAID |
| 1004 | PAID |
| 1005 | PAID |
| 1006 | PAID |
| 1007 | PAID |
범위(BETWEEN)
SELECT *
FROM USERS
WHERE user_id BETWEEN 1 AND 2;
실행 결과
| user_id | name | country | joined |
| 1 | Min | KR | 2021-03-10 |
| 2 | Jae | KR | 2022-10-05 |
BETWEEN은 양 끝을 포함합니다.
포함(IN) / 제외(NOT IN)
SELECT *
FROM PRODUCTS
WHERE category IN ('top', 'acc');
실행 결과
| product_id | product_name | category | unit_price |
| 10 | Tshirt | top | 20000 |
| 30 | Cap | acc | 15000 |
NULL 체크
NULL은 = 비교가 안 됩니다.
- WHERE col IS NULL
- WHERE col IS NOT NULL
(현재 가상데이터에는 NULL 값이 없으므로, 예시로 status IS NULL을 실행하면 0행이 나옵니다.)
SELECT sale_id, status
FROM ONLINE_SALE
WHERE status IS NULL;
실행 결과: 0 rows
GROUP BY + 집계함수
GROUP BY는 “같은 값(또는 조합)”을 묶어서 집계할 때 씁니다.
6.1 사용자별 구매 건수
SELECT user_id, COUNT(*) AS cnt
FROM ONLINE_SALE
GROUP BY user_id;
실행 결과
(이 쿼리는 CANCELLED도 포함한 “주문 행 수” 기준입니다.)
| user_id | cnt |
| 1 | 3 |
| 2 | 3 |
| 3 | 1 |
두 컬럼 조합으로 그룹화
SELECT user_id, product_id, COUNT(*) AS dup_count
FROM ONLINE_SALE
GROUP BY user_id, product_id;
실행 결과
| user_id | product_id | dup_count |
| 1 | 10 | 2 |
| 1 | 20 | 1 |
| 2 | 10 | 1 |
| 2 | 30 | 2 |
| 3 | 10 | 1 |
HAVING (그룹 필터링)
HAVING은 그룹화 이후 집계 결과에 조건을 겁니다.
“중복 2개 이상”은 WHERE가 아니라 HAVING입니다.
SELECT user_id, product_id, COUNT(*) AS dup_count
FROM ONLINE_SALE
GROUP BY user_id, product_id
HAVING COUNT(*) >= 2;
실행 결과
| user_id | product_id | dup_count |
| 1 | 10 | 2 |
| 2 | 30 | 2 |
ORDER BY (정렬)
정렬 우선순위를 여러 개 줄 수 있습니다.
ASC(default):오름차순 / DESC:내림차순
GROUP BY에서 보여드린 예시에 ORDER BY만 추가했습니다.
SELECT user_id, product_id, COUNT(*) AS dup_count
FROM ONLINE_SALE
GROUP BY user_id, product_id
ORDER BY user_id ASC, product_id DESC;
실행 결과
(user_id 오름차순, 같은 user_id 안에서는 product_id 내림차순)
| user_id | product_id | dup_count |
| 1 | 20 | 1 |
| 1 | 10 | 2 |
| 2 | 30 | 2 |
| 2 | 10 | 1 |
| 3 | 10 | 1 |
LIMIT/FETCH (상위 N개)
- MySQL: LIMIT
- 표준 SQL/일부 DB: FETCH FIRST N ROWS ONLY
SELECT *
FROM ONLINE_SALE
ORDER BY created_date
LIMIT 3;
실행 결과
(created_date 오름차순 기준 상위 3행)
| sale_id | user_id | product_id | created_date | qty | status |
| 1001 | 1 | 10 | 2022-10-01 | 1 | PAID |
| 1002 | 1 | 10 | 2022-10-02 | 1 | PAID |
| 1004 | 2 | 10 | 2022-10-03 | 2 | PAID |
서브쿼리(쿼리 안의 쿼리)
11.1 WHERE + IN
SELECT user_id, name
FROM USERS
WHERE user_id IN (
SELECT user_id
FROM ONLINE_SALE
GROUP BY user_id
HAVING COUNT(*) >= 3
);
실행 결과
(ONLINE_SALE에서 user_id별 행 수가 3 이상인 사용자는 1, 2)
| user_id | name |
| 1 | Min |
| 2 | Jae |
WHERE + EXISTS
SELECT u.user_id, u.name
FROM USERS u
WHERE EXISTS (
SELECT 1
FROM ONLINE_SALE s
WHERE s.user_id = u.user_id
AND s.status = 'CANCELLED'
);
실행 결과
(CANCELLED 주문이 있는 사용자는 user_id=1)
| user_id | name |
| 1 | Min |
FROM 서브쿼리(인라인 뷰)
SELECT x.user_id, x.cnt
FROM (
SELECT user_id, COUNT(*) AS cnt
FROM ONLINE_SALE
GROUP BY user_id
) x
WHERE x.cnt >= 2
ORDER BY x.user_id;
실행 결과
(cnt >= 2인 user_id는 1, 2)
| user_id | cnt |
| 1 | 3 |
| 2 | 3 |
'코딩테스트 > SQL문제 풀기' 카테고리의 다른 글
| 문제 풀어보기 (0) | 2024.12.06 |
|---|---|
| SQL 문법 문제(기초) Step10 (0) | 2024.10.25 |
| SQL 문법 문제(기초) Step9 (0) | 2024.10.24 |
| SQL 문법 문제(기초) Step8 (0) | 2024.10.22 |
| SQL 문법 문제(기초) Step7 (0) | 2024.10.21 |