SQL JOIN · WHERE · HAVING · GROUP BY — 백엔드 기초 쿼리 감각 잡기

SQL 입문에서 가장 자주 헷갈리는 JOIN, WHERE, HAVING, GROUP BY를 한 글에 묶어 정리한다. 쿼리를 외우기보다 결과를 예측하는 감각을 만드는 것이 목표다.

Seobway · · 13분

이 시리즈 구성

포스트 내용
로드맵 인덱스 → 01~19 전체 학습 경로
02-1. Node.js · Bun · Deno → 서버 JavaScript 런타임 비교
02-2. HTTP 메서드와 상태 코드 → REST API의 기본 언어
02-3. Hono로 REST API 시작하기 → 경량 서버 프레임워크 실습
02-4. SQL JOIN · WHERE · HAVING · GROUP BY → 쿼리 결과 예측과 집계

왜 SQL은 결과를 예측하는 연습이 중요한가

SQL은 문법만 외우면 금방 막힌다.

정말 중요한 것은 "이 쿼리를 실행하면 표가 어떻게 바뀔까?"를 머릿속으로 그릴 수 있는지다. 그래서 이 글은 SELECT, JOIN, WHERE, HAVING, GROUP BY결과 중심으로 설명한다.


예제 테이블

-- users
id | name
---+-------
1  | Alice
2  | Bob
3  | Chris

-- orders
id | user_id | amount
---+---------+--------
1  | 1       | 10000
2  | 1       | 15000
3  | 2       | 7000

INNER JOIN vs LEFT JOIN

INNER JOIN

양쪽에 매칭되는 행만 남긴다.

SELECT u.name, o.amount
FROM users u
INNER JOIN orders o ON u.id = o.user_id;

결과:

Alice | 10000
Alice | 15000
Bob   | 7000

Chris는 주문이 없으므로 빠진다.

LEFT JOIN

왼쪽 테이블은 모두 남기고, 오른쪽에서 매칭이 없으면 NULL을 채운다.

SELECT u.name, o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;

결과:

Alice | 10000
Alice | 15000
Bob   | 7000
Chris | NULL
%% desc: INNER JOIN은 교집합, LEFT JOIN은 왼쪽 기준 전체를 남긴다
flowchart LR
  I["INNER JOIN\n매칭되는 행만"] --> R["결과"]
  L["LEFT JOIN\n왼쪽 행은 모두 유지"] --> R

WHERE는 언제 쓰는가

WHERE그룹핑하기 전 행(row)을 걸러내는 조건이다.

SELECT *
FROM orders
WHERE amount >= 10000;

이 쿼리는 개별 주문 행 중에서 금액이 10000 이상인 행만 남긴다.


GROUP BY와 집계 함수

사용자별 총 주문 금액을 보고 싶다면 GROUP BY를 쓴다.

SELECT user_id, SUM(amount) AS total_amount
FROM orders
GROUP BY user_id;

결과:

1 | 25000
2 | 7000

대표적인 집계 함수는 다음과 같다.


HAVING은 왜 필요한가

HAVING그룹핑한 결과에 조건을 거는 것이다.

SELECT user_id, SUM(amount) AS total_amount
FROM orders
GROUP BY user_id
HAVING SUM(amount) >= 10000;

결과:

1 | 25000

WHEREHAVING의 차이는 다음으로 보면 된다.


JOIN과 GROUP BY를 같이 쓰면 어떻게 읽나

SELECT u.name, COUNT(o.id) AS order_count, SUM(o.amount) AS total_amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name;

이 쿼리는 다음 순서로 읽으면 좋다.

  1. users를 기준으로 본다
  2. ordersLEFT JOIN한다
  3. 사용자별로 묶는다
  4. 주문 개수와 총액을 계산한다

결과는 대략 이렇게 된다.

Alice | 2 | 25000
Bob   | 1 | 7000
Chris | 0 | NULL

초반에 꼭 해 볼 연습


마치며

백엔드 기초에서 SQL이 어려운 이유는 문법이 많아서가 아니라, 행과 그룹이 어떻게 변하는지 머릿속에서 추적해야 하기 때문이다.

그래서 JOIN, WHERE, HAVING, GROUP BY를 묶어서 이해하면 훨씬 빨리 감이 온다.

조금 더 깊게 보기

SQL은 표를 변형하는 언어다

SQL을 어렵게 느끼는 이유는 코드를 위에서 아래로 실행한다고 생각하기 때문이다. SQL은 절차형 코드라기보다 표를 선언적으로 변형하는 언어다.

JOIN을 머릿속에서 그리는 법

JOIN은 두 표를 옆으로 붙이는 일이다. INNER JOIN은 양쪽에 짝이 있는 행만 남기고, LEFT JOIN은 왼쪽 표의 행을 보존한다. 실무에서 LEFT JOIN은 "주문이 없는 사용자도 보여줘" 같은 요구사항에서 자주 등장한다.

개발자 인사이트

ORM을 쓰더라도 SQL 결과를 예측하는 힘은 필수다. ORM 코드는 결국 SQL로 변환되고, 성능 문제도 SQL에서 드러난다. N+1 문제, 느린 JOIN, 잘못된 GROUP BY는 ORM을 잘 써도 피할 수 없다.


참고

  1. [1] PostgreSQL Docs — SELECT
  2. [2] PostgreSQL Docs — Table Expressions and JOINs
  3. [3] PostgreSQL Docs — Tutorial: Queries

관련 글