SQL INNER JOIN 완벽 가이드 – 초보자도 10분이면 이해한다! 2025

데이터베이스 공부를 시작했는데 JOIN이 너무 어렵나요? 여러 테이블에 흩어진 데이터를 하나로 합쳐야 하는데 막막하신가요? 실제로 개발자 설문조사에 따르면 SQL 초보자의 73%가 JOIN 개념에서 가장 많이 막힌다고 답했습니다. 이 글에서 INNER JOIN을 쉽고 재미있게, 그림과 실습 예제로 완벽하게 마스터해보겠습니다!


Table of Contents

JOIN이란 무엇인가?

JOIN은 말 그대로 합친다는 뜻입니다. 여러 개의 테이블을 하나로 합쳐서 필요한 정보를 한 번에 가져오는 SQL 문법입니다.


왜 JOIN이 필요한가?

현실 세계를 생각해보세요. 학교 시스템을 만든다면:

학생 테이블

`학생ID이름학년
1김철수3
2이영희2
3박민수3`

수강 테이블

`수강ID학생ID과목명
1011수학
1021영어
1032국어`

만약 “각 학생이 어떤 과목을 수강하는지” 보고 싶다면? 두 테이블을 합쳐야 합니다!

이렇게 데이터를 여러 테이블로 나누는 이유는:

  • 데이터 중복 방지
  • 저장 공간 절약
  • 데이터 관리 용이
  • 업데이트 효율성

하지만 실제 업무에서는 여러 테이블의 데이터를 함께 봐야 하는 경우가 대부분입니다. 바로 이때 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_idcustomer_nameemailproductprice
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_namedept_namesalary
박과장개발팀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 = '수학';

결과:

`namesubjectscore
김철수수학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;

결과:

`namesubjectscore
김철수영어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_namecourse_nameprofessor
김철수데이터베이스홍교수
이영희알고리즘김교수`

순서 설명:

  1. students와 enrollments를 student_id로 연결
  2. 그 결과와 courses를 course_id로 연결
  3. 세 테이블의 정보가 합쳐진 결과 출력

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;

결과:

`nameavg_scoresubject_count
김철수87.52
이영희95.01
박민수88.01`

예제: 부서별 평균 연봉

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_nameemployee_countavg_salarymax_salary
개발팀240000005000000
영업팀140000004000000`

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;

실행 순서:

  1. FROM – 테이블 선택
  2. INNER JOIN – 테이블 결합
  3. WHERE – 조건 필터링
  4. 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: 다음 방법을 시도하세요:

  1. 인덱스 생성

sql

CREATE INDEX idx_join_column ON table_name(join_column);

  1. EXPLAIN으로 분석

sql

EXPLAIN SELECT ...

  1. 불필요한 열 제거

sql

  • *- SELECT * 대신 필요한 열만* SELECT id, name, score FROM ...
  1. 서브쿼리를 JOIN으로 변경

sql

  • *- 느림* SELECT * FROM A WHERE id IN (SELECT id FROM B); *- 빠름* SELECT A.* FROM A INNER JOIN B ON A.id = B.id;
  1. 통계 정보 업데이트

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주차)

학습 목표:

  • 실제 비즈니스 문제 해결
  • 성능 최적화
  • 복잡한 쿼리 작성

프로젝트 아이디어:

  1. 학사 관리 시스템 분석
  2. 쇼핑몰 매출 대시보드
  3. 직원 급여 분석 시스템
  4. 도서관 대출 통계

4단계: 마스터 되기 (지속)

학습 목표:

  • 서브쿼리 vs JOIN 선택
  • 인덱스 설계
  • 쿼리 튜닝

고급 주제:

  • 윈도우 함수와 JOIN
  • 재귀 쿼리
  • 파티셔닝과 JOIN

온라인 연습 사이트

1. SQL Fiddle

  • URL: http://sqlfiddle.com/
  • 특징: 브라우저에서 바로 SQL 연습
  • 장점: 설치 불필요, 다양한 DB 지원

2. DB Fiddle

3. LeetCode SQL

4. HackerRank SQL

5. SQLZoo

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을 마스터했다면:

  1. LEFT JOIN, RIGHT JOIN 배우기
  2. OUTER JOIN 이해하기
  3. SELF JOIN 활용하기
  4. 서브쿼리 최적화하기
  5. 윈도우 함수와 JOIN 결합하기

여러분은 할 수 있습니다!

SQL은 데이터 시대의 필수 기술입니다. INNER JOIN은 그 시작점입니다. 이 글을 읽은 여러분은 이미 기초를 다졌습니다. 이제 꾸준히 연습하고 실전에 적용하면서 SQL 마스터로 성장하세요!

기억하세요:

  • 처음부터 완벽할 필요 없습니다
  • 에러는 배움의 기회입니다
  • 작은 성공을 축하하세요
  • 커뮤니티에서 도움을 구하세요

관련 유용한 자료


이 글이 도움되셨나요?

SQL INNER JOIN으로 여러분의 데이터 분석 능력을 한 단계 업그레이드하세요!

댓글로 여러분의 경험을 공유해주세요:

  • INNER JOIN을 처음 배울 때 어려웠던 점은?
  • 실무에서 가장 자주 사용하는 JOIN 패턴은?
  • 이 글에서 가장 유용했던 부분은?
  • 다음에 다뤄줬으면 하는 SQL 주제는?

SQL 학습자들과 함께 성장하세요! 이 글을 주변 개발자, 데이터 분석가, DB 관리자들과 공유하여 더 많은 사람들이 SQL을 쉽게 배울 수 있도록 도와주세요.

연습 문제 정답이 궁금하거나 막히는 부분이 있다면 댓글로 질문해주세요!

#SQL #INNERJOIN #데이터베이스 #MySQL #PostgreSQL #초보자가이드 #코딩공부 #데이터분석 #백엔드개발 #프로그래밍기초 #JOIN문법 #SQL튜토리얼

댓글 남기기