데이터베이스 공부를 시작했는데 JOIN이 너무 어렵나요? 여러 테이블에 흩어진 데이터를 하나로 합쳐야 하는데 막막하신가요? 실제로 개발자 설문조사에 따르면 SQL 초보자의 73%가 JOIN 개념에서 가장 많이 막힌다고 답했습니다. 이 글에서 INNER JOIN을 쉽고 재미있게, 그림과 실습 예제로 완벽하게 마스터해보겠습니다!
JOIN이란 무엇인가?
JOIN은 말 그대로 합친다는 뜻입니다. 여러 개의 테이블을 하나로 합쳐서 필요한 정보를 한 번에 가져오는 SQL 문법입니다.
왜 JOIN이 필요한가?
현실 세계를 생각해보세요. 학교 시스템을 만든다면:
학생 테이블
| `학생ID | 이름 | 학년 |
|---|---|---|
| 1 | 김철수 | 3 |
| 2 | 이영희 | 2 |
| 3 | 박민수 | 3` |
수강 테이블
| `수강ID | 학생ID | 과목명 |
|---|---|---|
| 101 | 1 | 수학 |
| 102 | 1 | 영어 |
| 103 | 2 | 국어` |
만약 “각 학생이 어떤 과목을 수강하는지” 보고 싶다면? 두 테이블을 합쳐야 합니다!
이렇게 데이터를 여러 테이블로 나누는 이유는:
- 데이터 중복 방지
- 저장 공간 절약
- 데이터 관리 용이
- 업데이트 효율성
하지만 실제 업무에서는 여러 테이블의 데이터를 함께 봐야 하는 경우가 대부분입니다. 바로 이때 JOIN이 필요합니다!
INNER JOIN이란?
INNER JOIN은 두 테이블을 조인할 때, 두 테이블에 모두 지정한 열의 데이터가 있어야 합니다. 쉽게 말해, 양쪽 테이블에 모두 존재하는 데이터만 가져옵니다.
벤 다이어그램으로 이해하기
테이블 A 테이블 B ○ ○ \\ / \\ / \\ ●●● / ← 이 겹치는 부분만! \\_____/
INNER JOIN은 두 원이 겹치는 가운데 부분만 가져옵니다.
일상 생활 비유
상황: 동창회 명단 만들기
- 1반 명단: 김철수, 이영희, 박민수, 최지우
- 2반 명단: 이영희, 박민수, 정다은, 강호동
INNER JOIN은 “두 반 모두에 있는 사람“만 선택: → 결과: 이영희, 박민수
INNER JOIN 기본 문법
가장 기본적인 형태
sql
SELECT 열_이름 FROM 테이블1 INNER JOIN 테이블2 ON 테이블1.공통열 = 테이블2.공통열;
문법 요소 설명
요소 의미 필수 여부 SELECT 가져올 열 지정 필수 ✅FROM첫 번째(왼쪽) 테이블 필수 ✅INNER JOIN두 번째(오른쪽) 테이블 필수 ✅ON합칠 조건 (어떤 열 기준)필수 ✅
INNER 생략 가능
sql
*- 이 두 문장은 완전히 같습니다!* SELECT * FROM A INNER JOIN B ON A.id = B.id; SELECT * FROM A JOIN B ON A.id = B.id;
INNER는 생략 가능하며, 그냥 JOIN이라고 쓰면 자동으로 INNER JOIN입니다.
실습 예제로 배우기
예제 1: 학생과 성적 테이블
students 테이블
sql
`CREATE TABLE students ( student_id INT PRIMARY KEY, name VARCHAR(50), age INT );
INSERT INTO students VALUES (1, ‘김철수’, 20), (2, ‘이영희’, 22), (3, ‘박민수’, 21), (4, ‘최지우’, 19);`
student_id name age 1 김철수 20 2 이영희 22 3 박민수 21 4 최지우 19
scores 테이블
sql
`CREATE TABLE scores ( score_id INT PRIMARY KEY, student_id INT, subject VARCHAR(50), score INT );
INSERT INTO scores VALUES (101, 1, ‘수학’, 85), (102, 1, ‘영어’, 90), (103, 2, ‘수학’, 95), (104, 3, ‘영어’, 88);`
score_id student_id subject score 101 1 수학 85 102 1 영어 90 103 2 수학 95 104 3 영어 88
INNER JOIN 실행
sql
SELECT students.name, students.age, scores.subject, scores.score FROM students INNER JOIN scores ON students.student_id = scores.student_id;
결과:
nameagesubjectscore김철수20수학85김철수20영어90이영희22수학95박민수21영어88
주목! 최지우(student_id=4)는 결과에 없습니다. 왜냐하면 scores 테이블에 최지우의 성적이 없기 때문입니다. 이것이 바로 INNER JOIN의 특징입니다!
테이블 별칭(Alias) 사용하기
테이블 이름이 길면 매번 쓰기 귀찮습니다. 별칭을 사용하면 훨씬 간결해집니다!
별칭 없이 (번거로움)
sql
SELECT students.name, students.age, scores.subject, scores.score FROM students INNER JOIN scores ON students.student_id = scores.student_id;
별칭 사용 (간결함)
sql
SELECT s.name, s.age, sc.subject, sc.score FROM students AS s INNER JOIN scores AS sc ON s.student_id = sc.student_id;
팁: AS는 생략 가능합니다!
sql
FROM students s INNER JOIN scores sc
실전 예제 모음
예제 2: 쇼핑몰 주문 내역
customers (고객) 테이블
sql
customer_id | name | email *-----------|---------|------------------* 1 | 홍길동 | hong@email.com 2 | 김영수 | kim@email.com 3 | 이순신 | lee@email.com
orders (주문) 테이블
sql
order_id | customer_id | product | price *---------|------------|--------------|-------* 101 | 1 | 노트북 | 1500000 102 | 1 | 마우스 | 30000 103 | 2 | 키보드 | 80000
쿼리: 각 주문에 고객 정보 포함
sql
SELECT o.order_id, c.name AS customer_name, c.email, o.product, o.price FROM orders o INNER JOIN customers c ON o.customer_id = c.customer_id;
결과:
| `order_id | customer_name | product | price | |
|---|---|---|---|---|
| 101 | 홍길동 | hong@email.com | 노트북 | 1500000 |
| 102 | 홍길동 | hong@email.com | 마우스 | 30000 |
| 103 | 김영수 | kim@email.com | 키보드 | 80000` |
이순신은 주문 내역이 없어서 결과에 나타나지 않습니다!
예제 3: 직원과 부서
employees (직원) 테이블
sql
emp_id | emp_name | dept_id | salary *-------|----------|---------|--------* 1 | 박과장 | 10 | 5000000 2 | 김대리 | 20 | 4000000 3 | 이사원 | 10 | 3000000 4 | 최인턴 | NULL | 2000000
departments (부서) 테이블
sql
dept_id | dept_name *--------|----------* 10 | 개발팀 20 | 영업팀 30 | 마케팅팀
쿼리: 직원과 소속 부서
sql
SELECT e.emp_name, d.dept_name, e.salary FROM employees e INNER JOIN departments d ON e.dept_id = d.dept_id;
결과:
| `emp_name | dept_name | salary |
|---|---|---|
| 박과장 | 개발팀 | 5000000 |
| 김대리 | 영업팀 | 4000000 |
| 이사원 | 개발팀 | 3000000` |
주의사항:
- 최인턴(dept_id가 NULL)은 결과에 없음
- 마케팅팀(직원이 없음)도 결과에 없음
WHERE 절과 함께 사용하기
INNER JOIN 후 추가 조건으로 필터링할 수 있습니다.
예제: 특정 과목만 조회
sql
SELECT s.name, sc.subject, sc.score FROM students s INNER JOIN scores sc ON s.student_id = sc.student_id WHERE sc.subject = '수학';
결과:
| `name | subject | score |
|---|---|---|
| 김철수 | 수학 | 85 |
| 이영희 | 수학 | 95` |
예제: 점수가 90점 이상인 학생
sql
SELECT s.name, sc.subject, sc.score FROM students s INNER JOIN scores sc ON s.student_id = sc.student_id WHERE sc.score >= 90;
결과:
| `name | subject | score |
|---|---|---|
| 김철수 | 영어 | 90 |
| 이영희 | 수학 | 95` |
여러 테이블 JOIN하기
3개 이상의 테이블도 JOIN할 수 있습니다!
예제: 학생 – 수강 – 교수 테이블
students 테이블
sql
student_id | name *-----------|------* 1 | 김철수 2 | 이영희
enrollments 테이블
sql
enrollment_id | student_id | course_id *--------------|-----------|----------* 101 | 1 | 201 102 | 2 | 202
courses 테이블
sql
course_id | course_name | professor *----------|-------------|----------* 201 | 데이터베이스 | 홍교수 202 | 알고리즘 | 김교수
쿼리: 학생별 수강 과목과 교수
sql
SELECT s.name AS student_name, c.course_name, c.professor FROM students s INNER JOIN enrollments e ON s.student_id = e.student_id INNER JOIN courses c ON e.course_id = c.course_id;
결과:
| `student_name | course_name | professor |
|---|---|---|
| 김철수 | 데이터베이스 | 홍교수 |
| 이영희 | 알고리즘 | 김교수` |
순서 설명:
- students와 enrollments를 student_id로 연결
- 그 결과와 courses를 course_id로 연결
- 세 테이블의 정보가 합쳐진 결과 출력
GROUP BY와 집계 함수 활용
INNER JOIN과 GROUP BY를 함께 사용하면 강력한 분석이 가능합니다.
예제: 학생별 평균 점수
sql
SELECT s.name, AVG(sc.score) AS avg_score, COUNT(sc.score_id) AS subject_count FROM students s INNER JOIN scores sc ON s.student_id = sc.student_id GROUP BY s.student_id, s.name;
결과:
| `name | avg_score | subject_count |
|---|---|---|
| 김철수 | 87.5 | 2 |
| 이영희 | 95.0 | 1 |
| 박민수 | 88.0 | 1` |
예제: 부서별 평균 연봉
sql
SELECT d.dept_name, COUNT(e.emp_id) AS employee_count, AVG(e.salary) AS avg_salary, MAX(e.salary) AS max_salary FROM departments d INNER JOIN employees e ON d.dept_id = e.dept_id GROUP BY d.dept_id, d.dept_name;
결과:
| `dept_name | employee_count | avg_salary | max_salary |
|---|---|---|---|
| 개발팀 | 2 | 4000000 | 5000000 |
| 영업팀 | 1 | 4000000 | 4000000` |
INNER JOIN vs LEFT JOIN 비교
INNER JOIN
sql
SELECT s.name, sc.subject, sc.score FROM students s INNER JOIN scores sc ON s.student_id = sc.student_id;
결과: 성적이 있는 학생만 (김철수, 이영희, 박민수)
LEFT JOIN
sql
SELECT s.name, sc.subject, sc.score FROM students s LEFT JOIN scores sc ON s.student_id = sc.student_id;
결과: 모든 학생 포함, 성적 없으면 NULL (김철수, 이영희, 박민수, 최지우)
비교표
구분INNER JOINLEFT JOIN의미양쪽 모두 있는 것만왼쪽 테이블 전체 + 매칭되는 오른쪽결과 개수적음많음 (왼쪽 기준)NULL 포함없음가능사용 시기완전히 매칭되는 데이터만한쪽 테이블 기준 전체 조회
언제 무엇을 사용할까?
INNER JOIN 사용:
- 주문한 고객 목록만 보고 싶을 때
- 성적이 입력된 학생만 확인할 때
- 실제로 연결된 데이터만 필요할 때
LEFT JOIN 사용:
- 주문 안 한 고객도 포함해야 할 때
- 성적이 없는 학생도 명단에 포함할 때
- 왼쪽 테이블의 모든 레코드가 필요할 때
자주하는 실수와 해결법
실수 1: 중복 열 이름
sql
*- ❌ 에러 발생!* SELECT student_id, name, score FROM students INNER JOIN scores ON students.student_id = scores.student_id;
문제: student_id가 두 테이블에 모두 있어서 애매합니다.
해결:
sql
*- ✅ 올바른 방법* SELECT students.student_id, students.name, scores.score FROM students INNER JOIN scores ON students.student_id = scores.student_id;
실수 2: ON 조건 빠뜨림
sql
*- ❌ 에러 발생!* SELECT * FROM students INNER JOIN scores;
문제: ON 조건이 없으면 카테시안 곱(모든 조합)이 생성됩니다.
해결:
sql
*- ✅ 올바른 방법* SELECT * FROM students INNER JOIN scores ON students.student_id = scores.student_id;
실수 3: 잘못된 JOIN 순서
sql
*- 비효율적* SELECT * FROM big_table *- 1억 건* INNER JOIN small_table *- 100건* ON big_table.id = small_table.id;
최적화 팁:
- 작은 테이블을 먼저 쓰는 것이 좋습니다 (대부분의 DB는 자동 최적화)
- 인덱스를 JOIN 조건 열에 생성
- EXPLAIN으로 실행 계획 확인
실수 4: WHERE vs ON 혼동
sql
*- 다른 결과!- 방법 1: ON에 조건* SELECT * FROM students s INNER JOIN scores sc ON s.student_id = sc.student_id AND sc.score >= 90; *- 방법 2: WHERE에 조건* SELECT * FROM students s INNER JOIN scores sc ON s.student_id = sc.student_id WHERE sc.score >= 90;
INNER JOIN에서는 결과가 같지만:
- LEFT JOIN에서는 결과가 다름!
- ON: JOIN 전에 필터링
- WHERE: JOIN 후에 필터링
실전 프로젝트 예제
프로젝트: 쇼핑몰 매출 분석
테이블 구조
sql
*- 고객 테이블* customers (customer_id, name, email, join_date) *- 주문 테이블* orders (order_id, customer_id, order_date, total_amount) *- 주문 상세 테이블* order_details (detail_id, order_id, product_id, quantity, price) *- 상품 테이블* products (product_id, product_name, category, stock)
쿼리 1: 고객별 총 구매액
sql
SELECT c.name, c.email, COUNT(o.order_id) AS order_count, SUM(o.total_amount) AS total_purchase FROM customers c INNER JOIN orders o ON c.customer_id = o.customer_id GROUP BY c.customer_id, c.name, c.email ORDER BY total_purchase DESC;
쿼리 2: 카테고리별 판매 현황
sql
SELECT p.category, COUNT(DISTINCT od.order_id) AS order_count, SUM(od.quantity) AS total_quantity, SUM(od.quantity * od.price) AS total_sales FROM products p INNER JOIN order_details od ON p.product_id = od.product_id INNER JOIN orders o ON od.order_id = o.order_id WHERE o.order_date >= '2025-01-01' GROUP BY p.category ORDER BY total_sales DESC;
쿼리 3: 월별 베스트셀러
sql
SELECT DATE_FORMAT(o.order_date, '%Y-%m') AS month, p.product_name, SUM(od.quantity) AS total_sold, SUM(od.quantity * od.price) AS revenue FROM orders o INNER JOIN order_details od ON o.order_id = od.order_id INNER JOIN products p ON od.product_id = p.product_id GROUP BY month, p.product_id, p.product_name ORDER BY month DESC, total_sold DESC;
성능 최적화 팁
1. 인덱스 생성
sql
*- JOIN에 사용되는 열에 인덱스 생성* CREATE INDEX idx_student_id ON scores(student_id); CREATE INDEX idx_customer_id ON orders(customer_id);
효과:
- 검색 속도 10배~1000배 향상
- 대용량 테이블 JOIN 시 필수
2. 필요한 열만 SELECT
sql
*- ❌ 비효율적* SELECT * FROM students s INNER JOIN scores sc ON s.student_id = sc.student_id; *- ✅ 효율적* SELECT s.name, sc.score FROM students s INNER JOIN scores sc ON s.student_id = sc.student_id;
3. EXPLAIN으로 실행 계획 확인
sql
EXPLAIN SELECT s.name, sc.score FROM students s INNER JOIN scores sc ON s.student_id = sc.student_id;
확인 사항:
- type: ALL이 아닌 ref, eq_ref이 좋음
- rows: 스캔하는 행 수 (적을수록 좋음)
- Extra: Using index가 나오면 최적
4. JOIN 순서 최적화
sql
*- 작은 테이블을 먼저* FROM small_table s INNER JOIN medium_table m ON s.id = m.id INNER JOIN large_table l ON m.id = l.id
데이터베이스별 차이점
MySQL
sql
*- 표준 문법* SELECT * FROM A INNER JOIN B ON A.id = B.id; *- MySQL 특수 문법 (권장하지 않음)* SELECT * FROM A, B WHERE A.id = B.id;
PostgreSQL
sql
*- USING 절 사용 가능 (열 이름이 같을 때)* SELECT * FROM students INNER JOIN scores USING (student_id);
SQL Server
sql
*- 테이블 힌트 사용 가능* SELECT * FROM students s WITH (NOLOCK) INNER JOIN scores sc ON s.student_id = sc.student_id;
Oracle
sql
*- 오라클 전용 문법 (구식, 권장 안 함)* SELECT * FROM students s, scores sc WHERE s.student_id = sc.student_id; *- 표준 ANSI 문법 (권장)* SELECT * FROM students s INNER JOIN scores sc ON s.student_id = sc.student_id;
연습 문제
문제 1: 기본 INNER JOIN
테이블:
authors (author_id, name, country) books (book_id, title, author_id, price)
질문: 각 책의 제목과 저자 이름을 출력하세요.
<details> <summary>정답 보기</summary>
SELECT
b.title,
a.name AS author_name
FROM books b
INNER JOIN authors a
ON b.author_id = a.author_id;
</details>
문제 2: 집계 함수 활용
질문: 저자별로 쓴 책의 개수와 총 판매가를 계산하세요.
<details> <summary>정답 보기</summary>
SELECT
a.name,
COUNT(b.book_id) AS book_count,
SUM(b.price) AS total_value
FROM authors a
INNER JOIN books b
ON a.author_id = b.author_id
GROUP BY a.author_id, a.name;
</details>
문제 3: 3개 테이블 JOIN
추가 테이블:
sales (sale_id, book_id, quantity, sale_date)
질문: 2025년에 판매된 책의 저자명, 책 제목, 판매 수량을 출력하세요.
<details> <summary>정답 보기</summary>
SELECT
a.name AS author_name,
b.title,
s.quantity,
s.sale_date
FROM authors a
INNER JOIN books b ON a.author_id = b.author_id
INNER JOIN sales s ON b.book_id = s.book_id
WHERE YEAR(s.sale_date) = 2025;
</details>
자주 묻는 질문 (FAQ)
Q1. INNER JOIN과 WHERE를 같이 쓰면 어떻게 되나요?
A: 완벽하게 가능하며, 실무에서 매우 자주 사용됩니다!
sql
SELECT s.name, sc.score FROM students s INNER JOIN scores sc ON s.student_id = sc.student_id WHERE sc.score >= 90 AND s.age >= 20;
실행 순서:
- FROM – 테이블 선택
- INNER JOIN – 테이블 결합
- WHERE – 조건 필터링
- SELECT – 열 선택
Q2. JOIN과 INNER JOIN의 차이는?
A: 완전히 같습니다! JOIN은 기본적으로 INNER JOIN입니다.
sql
*- 이 두 문장은 동일* SELECT * FROM A JOIN B ON A.id = B.id; SELECT * FROM A INNER JOIN B ON A.id = B.id;
Q3. ON과 WHERE의 차이는?
A: INNER JOIN에서는 결과가 같지만, OUTER JOIN에서는 다릅니다.
sql
*- INNER JOIN에서는 같은 결과* SELECT * FROM A INNER JOIN B ON A.id = B.id AND B.value > 10; SELECT * FROM A INNER JOIN B ON A.id = B.id WHERE B.value > 10; *- LEFT JOIN에서는 다른 결과!* SELECT * FROM A LEFT JOIN B ON A.id = B.id AND B.value > 10; *- A의 모든 행 포함* SELECT * FROM A LEFT JOIN B ON A.id = B.id WHERE B.value > 10; *- B.value > 10인 행만*
Q4. 같은 테이블을 두 번 JOIN할 수 있나요?
A: 네! SELF JOIN이라고 하며, 별칭을 다르게 주면 됩니다.
sql
*- 직원과 그 직원의 상사 정보* SELECT e1.name AS employee, e2.name AS manager FROM employees e1 INNER JOIN employees e2 ON e1.manager_id = e2.emp_id;
Q5. JOIN이 너무 느린데 어떻게 하나요?
A: 다음 방법을 시도하세요:
- 인덱스 생성
sql
CREATE INDEX idx_join_column ON table_name(join_column);
- EXPLAIN으로 분석
sql
EXPLAIN SELECT ...
- 불필요한 열 제거
sql
*- SELECT * 대신 필요한 열만* SELECT id, name, score FROM ...
- 서브쿼리를 JOIN으로 변경
sql
*- 느림* SELECT * FROM A WHERE id IN (SELECT id FROM B); *- 빠름* SELECT A.* FROM A INNER JOIN B ON A.id = B.id;
- 통계 정보 업데이트
sql
*- MySQL* ANALYZE TABLE table_name; *- PostgreSQL* VACUUM ANALYZE table_name;
Q6. NULL 값이 있으면 INNER JOIN이 어떻게 되나요?
A: NULL 값은 매칭되지 않아 결과에서 제외됩니다.
sql
*- employees 테이블* emp_id | name | dept_id *------|--------|--------* 1 | 김철수 | 10 2 | 이영희 | NULL 3 | 박민수 | 20 *- departments 테이블* dept_id | dept_name *-------|----------* 10 | 개발팀 20 | 영업팀 *- INNER JOIN 결과* SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id = d.dept_id; *- 결과: 이영희는 제외됨 (dept_id가 NULL)* name | dept_name *------|----------* 김철수 | 개발팀 박민수 | 영업팀
NULL을 포함하려면 LEFT JOIN 사용:
sql
SELECT e.name, COALESCE(d.dept_name, '미배치') AS dept_name FROM employees e LEFT JOIN departments d ON e.dept_id = d.dept_id;
Q7. 여러 조건으로 JOIN할 수 있나요?
A: 네! AND로 여러 조건을 연결할 수 있습니다.
sql
SELECT * FROM orders o INNER JOIN order_details od ON o.order_id = od.order_id AND o.order_date = od.detail_date AND o.customer_id = od.customer_id;
복합 키 JOIN:
sql
*- 두 열의 조합이 키인 경우* SELECT * FROM table1 t1 INNER JOIN table2 t2 ON t1.key1 = t2.key1 AND t1.key2 = t2.key2;
실무 팁과 베스트 프랙티스
1. 명확한 별칭 사용
sql
*- ❌ 나쁜 예* SELECT * FROM students a INNER JOIN scores b ON a.student_id = b.student_id; *- ✅ 좋은 예* SELECT * FROM students s INNER JOIN scores sc ON s.student_id = sc.student_id;
규칙:
- 테이블 이름의 의미 있는 축약어 사용
- 일관성 유지 (같은 프로젝트에서는 같은 약어)
- 단순히 a, b, c는 피하기
2. JOIN 조건은 ON에, 필터링은 WHERE에
sql
*- ✅ 명확한 구조* SELECT s.name, sc.score FROM students s INNER JOIN scores sc ON s.student_id = sc.student_id *- JOIN 조건* WHERE sc.score >= 90 *- 필터링 조건* AND s.age >= 20;
3. 복잡한 JOIN은 CTE 사용
sql
*- WITH 절로 가독성 향상* WITH student_scores AS ( SELECT s.student_id, s.name, AVG(sc.score) AS avg_score FROM students s INNER JOIN scores sc ON s.student_id = sc.student_id GROUP BY s.student_id, s.name ) SELECT * FROM student_scores WHERE avg_score >= 85;
4. 포매팅으로 가독성 높이기
sql
*- ✅ 읽기 쉬운 포맷* SELECT s.student_id, s.name, s.age, sc.subject, sc.score, sc.exam_date FROM students s INNER JOIN scores sc ON s.student_id = sc.student_id WHERE sc.score >= 80 AND sc.exam_date >= '2025-01-01' ORDER BY s.name, sc.subject;
5. 주석으로 의도 명확히
sql
*- 2025년 상반기 우수 학생 목록 추출- 기준: 평균 85점 이상, 2과목 이상 수강* SELECT s.name, COUNT(sc.score_id) AS subject_count, AVG(sc.score) AS avg_score FROM students s INNER JOIN scores sc ON s.student_id = sc.student_id WHERE sc.exam_date BETWEEN '2025-01-01' AND '2025-06-30' GROUP BY s.student_id, s.name HAVING AVG(sc.score) >= 85 AND COUNT(sc.score_id) >= 2;
실전 시나리오별 쿼리 모음
시나리오 1: 전자상거래 – 미배송 주문 조회
sql
*- 주문은 했지만 배송 정보가 없는 주문 찾기* SELECT o.order_id, c.name AS customer_name, o.order_date, o.total_amount FROM orders o INNER JOIN customers c ON o.customer_id = c.customer_id LEFT JOIN shipments s ON o.order_id = s.order_id WHERE s.shipment_id IS NULL ORDER BY o.order_date DESC;
시나리오 2: 인사 시스템 – 부서별 급여 현황
sql
*- 부서별 평균 급여, 최고 급여, 직원 수* SELECT d.dept_name, COUNT(e.emp_id) AS emp_count, AVG(e.salary) AS avg_salary, MAX(e.salary) AS max_salary, MIN(e.salary) AS min_salary, SUM(e.salary) AS total_salary FROM departments d INNER JOIN employees e ON d.dept_id = e.dept_id GROUP BY d.dept_id, d.dept_name HAVING AVG(e.salary) >= 4000000 ORDER BY avg_salary DESC;
시나리오 3: 교육 시스템 – 수강생 출석률
sql
*- 과목별 학생 출석률 계산* SELECT c.course_name, s.name AS student_name, COUNT(a.attendance_id) AS attended_days, c.total_classes, ROUND(COUNT(a.attendance_id) * 100.0 / c.total_classes, 2) AS attendance_rate FROM courses c INNER JOIN enrollments e ON c.course_id = e.course_id INNER JOIN students s ON e.student_id = s.student_id LEFT JOIN attendance a ON e.enrollment_id = a.enrollment_id AND a.status = 'present' GROUP BY c.course_id, c.course_name, s.student_id, s.name, c.total_classes HAVING attendance_rate < 80 ORDER BY attendance_rate;
시나리오 4: 재고 관리 – 재고 부족 상품
sql
*- 판매량이 많은데 재고가 부족한 상품* SELECT p.product_name, p.stock AS current_stock, SUM(od.quantity) AS total_sold, CASE WHEN p.stock < SUM(od.quantity) * 0.2 THEN '긴급 발주' WHEN p.stock < SUM(od.quantity) * 0.5 THEN '발주 필요' ELSE '정상' END AS stock_status FROM products p INNER JOIN order_details od ON p.product_id = od.product_id INNER JOIN orders o ON od.order_id = o.order_id WHERE o.order_date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) GROUP BY p.product_id, p.product_name, p.stock HAVING p.stock < SUM(od.quantity) * 0.5 ORDER BY stock_status, total_sold DESC;
학습 로드맵
1단계: 기초 다지기 (1주차)
학습 목표:
- SELECT, FROM, WHERE 완벽 이해
- 기본 INNER JOIN 문법 숙지
- 2개 테이블 JOIN 연습
실습:
sql
*- 매일 10개씩 다양한 패턴 연습* SELECT * FROM A INNER JOIN B ON A.id = B.id; SELECT A.*, B.name FROM A INNER JOIN B ON A.id = B.id; SELECT * FROM A INNER JOIN B ON A.id = B.id WHERE A.status = 'active';
2단계: 응용 배우기 (2주차)
학습 목표:
- 3개 이상 테이블 JOIN
- GROUP BY와 집계 함수
- ORDER BY, LIMIT 활용
실습:
sql
*- 복잡한 쿼리 작성 연습* SELECT category, COUNT(*) as count, AVG(price) as avg_price FROM products p INNER JOIN order_details od ON p.product_id = od.product_id GROUP BY category ORDER BY count DESC LIMIT 10;
3단계: 실전 프로젝트 (3-4주차)
학습 목표:
- 실제 비즈니스 문제 해결
- 성능 최적화
- 복잡한 쿼리 작성
프로젝트 아이디어:
- 학사 관리 시스템 분석
- 쇼핑몰 매출 대시보드
- 직원 급여 분석 시스템
- 도서관 대출 통계
4단계: 마스터 되기 (지속)
학습 목표:
- 서브쿼리 vs JOIN 선택
- 인덱스 설계
- 쿼리 튜닝
고급 주제:
- 윈도우 함수와 JOIN
- 재귀 쿼리
- 파티셔닝과 JOIN
온라인 연습 사이트
1. SQL Fiddle
- URL: http://sqlfiddle.com/
- 특징: 브라우저에서 바로 SQL 연습
- 장점: 설치 불필요, 다양한 DB 지원
2. DB Fiddle
- URL: https://www.db-fiddle.com/
- 특징: 깔끔한 UI, 빠른 속도
- 장점: PostgreSQL, MySQL, SQLite 지원
3. LeetCode SQL
- URL: https://leetcode.com/problemset/database/
- 특징: 난이도별 문제
- 장점: 면접 준비에 최적
4. HackerRank SQL
- URL: https://www.hackerrank.com/domains/sql
- 특징: 게임처럼 재미있게
- 장점: 점진적 난이도 상승
5. SQLZoo
- URL: https://sqlzoo.net/
- 특징: 단계별 튜토리얼
- 장점: 초보자 친화적
INNER JOIN 체크리스트
쿼리 작성 전
- [ ] 어떤 테이블들을 합칠지 명확히 파악
- [ ] 두 테이블의 공통 키(연결 고리) 확인
- [ ] 필요한 열(컬럼) 목록 정리
- [ ] 필터링 조건(WHERE) 정의
쿼리 작성 중
- [ ] FROM에 첫 번째 테이블 작성
- [ ] INNER JOIN으로 두 번째 테이블 연결
- [ ] ON에 올바른 조인 조건 작성
- [ ] 별칭(alias) 사용으로 가독성 향상
- [ ] 중복 열 이름은 테이블명.열명으로 명시
쿼리 작성 후
- [ ] 실행해서 결과 확인
- [ ] 예상한 행 개수와 일치하는지 확인
- [ ] NULL 값 처리 확인
- [ ] EXPLAIN으로 성능 점검
- [ ] 인덱스 사용 여부 확인
추천 도서 및 자료
📚 입문서
1. SQL 첫걸음
- 초보자 최적화
- 그림으로 쉽게 설명
- 한국어 번역 우수
2. 모두의 SQL
- 실습 위주 구성
- 예제 코드 풍부
- 무료 온라인 버전 제공
📚 중급서
3. SQL 레벨업
- 실무 쿼리 최적화
- 성능 튜닝 기법
- 케이스 스터디 풍부
4. SQL Cookbook
- 다양한 문제 해결법
- 실전 레시피 수록
- 참고서로 활용
🎥 온라인 강의
1. 인프런 – SQL 기초부터 실무까지
- 한국어 강의
- 실습 환경 제공
- Q&A 활발
2. Udemy – Complete SQL Bootcamp
- 영어 강의 (한글 자막)
- 프로젝트 포함
- 평생 소장
3. 유튜브 – 생활코딩 Database
- 무료 강의
- 기초 개념 설명
- 한국어
📝 블로그 및 커뮤니티
1. 개발자 커뮤니티
- OKKY
- 인프런 질문 게시판
- Stack Overflow (한국어판)
2. 기술 블로그
- 우아한형제들 기술 블로그
- 카카오 기술 블로그
- NHN Cloud Meetup
마치며
INNER JOIN은 SQL의 가장 기본이면서도 강력한 기능입니다. 처음에는 어렵게 느껴질 수 있지만, 꾸준히 연습하면 누구나 마스터할 수 있습니다.
핵심 요약 5가지
✅ INNER JOIN은 양쪽 테이블 모두에 있는 데이터만 가져옵니다 ✅ ON 절로 두 테이블을 연결하는 조건을 명시합니다 ✅ 별칭(alias)을 사용하면 코드가 깔끔해집니다 ✅ WHERE 절로 추가 필터링을 할 수 있습니다 ✅ 인덱스를 활용하면 성능이 크게 향상됩니다
학습 팁
1. 매일 10분씩 연습하기
- 하루에 5개씩 다양한 JOIN 쿼리 작성
- 실제 데이터로 실습
- 에러 메시지 분석하는 습관
2. 실제 프로젝트에 적용하기
- 학교 과제나 사이드 프로젝트에서 활용
- 간단한 게시판이라도 직접 DB 설계
- JOIN이 필요한 상황 만들어보기
3. 다른 사람 코드 읽기
- GitHub의 오픈소스 프로젝트 SQL
- Stack Overflow의 질문과 답변
- 회사 선배 개발자의 쿼리
4. 개념을 설명하기
- 친구나 동료에게 가르쳐보기
- 블로그에 정리하기
- 스터디 그룹에서 발표하기
다음 단계
INNER JOIN을 마스터했다면:
- LEFT JOIN, RIGHT JOIN 배우기
- OUTER JOIN 이해하기
- SELF JOIN 활용하기
- 서브쿼리 최적화하기
- 윈도우 함수와 JOIN 결합하기
여러분은 할 수 있습니다!
SQL은 데이터 시대의 필수 기술입니다. INNER JOIN은 그 시작점입니다. 이 글을 읽은 여러분은 이미 기초를 다졌습니다. 이제 꾸준히 연습하고 실전에 적용하면서 SQL 마스터로 성장하세요!
기억하세요:
- 처음부터 완벽할 필요 없습니다
- 에러는 배움의 기회입니다
- 작은 성공을 축하하세요
- 커뮤니티에서 도움을 구하세요
관련 유용한 자료
이 글이 도움되셨나요?
SQL INNER JOIN으로 여러분의 데이터 분석 능력을 한 단계 업그레이드하세요!
댓글로 여러분의 경험을 공유해주세요:
- INNER JOIN을 처음 배울 때 어려웠던 점은?
- 실무에서 가장 자주 사용하는 JOIN 패턴은?
- 이 글에서 가장 유용했던 부분은?
- 다음에 다뤄줬으면 하는 SQL 주제는?
SQL 학습자들과 함께 성장하세요! 이 글을 주변 개발자, 데이터 분석가, DB 관리자들과 공유하여 더 많은 사람들이 SQL을 쉽게 배울 수 있도록 도와주세요.
연습 문제 정답이 궁금하거나 막히는 부분이 있다면 댓글로 질문해주세요!
#SQL #INNERJOIN #데이터베이스 #MySQL #PostgreSQL #초보자가이드 #코딩공부 #데이터분석 #백엔드개발 #프로그래밍기초 #JOIN문법 #SQL튜토리얼