> ## Documentation Index
> Fetch the complete documentation index at: https://villagesql.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# MySQL INNER JOIN

> MySQL INNER JOIN 절이 ON과 USING으로 2개 이상의 테이블에서 행을 어떻게 일치시키는지, 그리고 행 하나가 여러 행과 일치하면 왜 행 수가 늘어나는지 설명합니다.

> [card] [VillageSQL is a drop-in replacement for MySQL with extensions.](https://villagesql.com/docs/en/8.4/stable/install-server.md)
>
> All examples on this page work on VillageSQL. Install Now →

`INNER JOIN`은 일치가 성공할 때만 행을 남깁니다. 한 테이블의 행이 다른 테이블의 행을 가리키고 둘 다 원할 때 쓰는 조인입니다.

시작하기 전에 `mysql -u root -p sakila`로 클라이언트를 여세요.

## INNER JOIN의 동작 방식

```sql theme={null}
SELECT COLUMN_LIST
FROM LEFT_TABLE
INNER JOIN RIGHT_TABLE ON LEFT_TABLE.COLUMN_NAME = RIGHT_TABLE.COLUMN_NAME;
```

`COLUMN_LIST`를 원하는 열로, `LEFT_TABLE`을 시작하는 테이블로, `RIGHT_TABLE`을 추가 열을 가져올 테이블로, 각 `COLUMN_NAME`을 그쪽에서 둘을 연결하는 열로 바꾸세요.

`ON` 절은 어느 행끼리 짝인지 말합니다. MySQL은 왼쪽 테이블의 각 행을 가져와서, 그 조건이 성립하는 오른쪽 테이블의 행을 찾고, 일치마다 결합된 행 1개를 반환합니다. 아무것도 찾지 못한 행은 어느 쪽이든 버려집니다. [LEFT JOIN](https://villagesql.com/docs/ko/tutorial/left-join.md)이 그 행을 대신 남기는 절입니다.

## 예제

### 테이블 2개 조인

`film.language_id`는 `language.language_id`를 가리킵니다. 그 쌍을 일치시키면 제목 옆에 언어 이름을 출력할 수 있습니다.

```sql theme={null}
SELECT f.title, l.name
FROM film AS f
INNER JOIN language AS l ON f.language_id = l.language_id
ORDER BY f.title;
```

```text theme={null}
+-----------------------------+---------+
| title                       | name    |
+-----------------------------+---------+
| ACADEMY DINOSAUR            | English |
| ACE GOLDFINGER              | English |
| ADAPTATION HOLES            | English |
...
+-----------------------------+---------+
1000 rows in set (0.00 sec)
```

1,000개의 행으로, `film` 하나만 읽었을 때와 같은 수입니다. 모든 영화가 정확히 1개의 언어와 일치했는데, 조인 열이 단일 행을 가리킬 때 얻는 결과입니다.

### 외래 키를 따라 조인

고객은 도로명 주소를 별도의 테이블에 보관합니다.

```sql theme={null}
SELECT c.first_name, c.last_name, a.address
FROM customer AS c
INNER JOIN address AS a ON c.address_id = a.address_id
ORDER BY c.last_name, c.first_name;
```

```text theme={null}
+-------------+--------------+----------------------------------------+
| first_name  | last_name    | address                                |
+-------------+--------------+----------------------------------------+
| RAFAEL      | ABNEY        | 48 Maracaíbo Place                     |
| NATHANIEL   | ADAM         | 786 Matsue Way                         |
| KATHLEEN    | ADAMS        | 334 Munger (Monghyr) Lane              |
...
+-------------+--------------+----------------------------------------+
599 rows in set (0.00 sec)
```

599개의 행으로, 고객당 1개입니다.

### 테이블 3개 조인

테이블이 하나 더 늘 때마다 `INNER JOIN` 절을 하나씩 추가하세요. Sakila는 위치를 `address`, `city`, `country`로 나누어 두므로, 도시 이름에 닿으려면 2번 건너가야 합니다.

```sql theme={null}
SELECT c.first_name, c.last_name, ci.city
FROM customer AS c
INNER JOIN address AS a ON c.address_id = a.address_id
INNER JOIN city AS ci ON a.city_id = ci.city_id
ORDER BY c.last_name, c.first_name;
```

```text theme={null}
+-------------+--------------+----------------------------+
| first_name  | last_name    | city                       |
+-------------+--------------+----------------------------+
| RAFAEL      | ABNEY        | Talavera                   |
| NATHANIEL   | ADAM         | Joliet                     |
| KATHLEEN    | ADAMS        | Arak                       |
...
+-------------+--------------+----------------------------+
599 rows in set (0.00 sec)
```

여전히 599개의 행입니다. 각 조인이 단일 행과 일치했으므로 수가 움직이지 않았습니다.

### USING으로 줄여 쓰기

2개의 열이 같은 이름을 가질 때, `USING`이 `ON`을 대신하고 이름을 한 번만 말합니다.

```sql theme={null}
SELECT f.title, l.name
FROM film AS f
INNER JOIN language AS l USING (language_id)
ORDER BY f.title;
```

```text theme={null}
+-----------------------------+---------+
| title                       | name    |
+-----------------------------+---------+
| ACADEMY DINOSAUR            | English |
| ACE GOLDFINGER              | English |
| ADAPTATION HOLES            | English |
...
+-----------------------------+---------+
1000 rows in set (0.00 sec)
```

예제 1과 같은 1,000개의 행입니다. `USING`은 양쪽의 이름이 일치할 때만 동작하므로, 언제나 적용되는 형태는 `ON`입니다.

## 여러 행과 일치하면 행이 곱해집니다

조인은 일치당 1개의 행을 반환하므로, 여러 행과 일치하는 행은 여러 번 돌아옵니다. 각 영화에는 여러 배우가 있습니다.

```sql theme={null}
SELECT f.title, fa.actor_id
FROM film AS f
INNER JOIN film_actor AS fa ON f.film_id = fa.film_id
ORDER BY f.title, fa.actor_id;
```

```text theme={null}
+-----------------------------+----------+
| title                       | actor_id |
+-----------------------------+----------+
| ACADEMY DINOSAUR            |        1 |
| ACADEMY DINOSAUR            |       10 |
| ACADEMY DINOSAUR            |       20 |
...
+-----------------------------+----------+
5462 rows in set (0.00 sec)
```

1,000개의 영화에서 5,462개의 행이 나왔고, 각 제목이 배우마다 한 번씩 반복됩니다. 이것이 조인이 예상보다 많은 행을 반환하는 흔한 이유이고, 조인 위에서 계산한 합계가 너무 크게 나올 수 있는 이유입니다.

## 사람들이 걸려 넘어지는 2가지

**일치하지 않은 행은 비어 있는 것이 아니라 사라집니다.** `film` 대신 `language`에서 시작해도 1,005개가 아니라 여전히 1,000개의 행을 얻습니다: Sakila의 나머지 5개 언어는 어떤 영화와도 일치하지 않아 빠집니다. 내부 조인은 일치한 것을 보고하지, 빠진 것은 결코 보고하지 않습니다. [LEFT JOIN](https://villagesql.com/docs/ko/tutorial/left-join.md)이 그 반대 질문을 묻는 방법입니다.

**`ON`이 빠져도 오류가 아닙니다.** MySQL은 `ON`이 없는 `INNER JOIN`을 두 테이블의 모든 조합으로 읽으므로, 오타가 오류 대신 1,000 곱하기 6개의 행을 반환합니다. [CROSS JOIN](https://villagesql.com/docs/ko/tutorial/cross-join.md)이 의도한 경우와 실수인 경우 모두를 다룹니다.

## 요약

* 테이블 간에 일치하는 행을 결합하려면 `INNER JOIN`과 `ON`을 사용하세요.
* 테이블이 하나 더 늘 때마다 `INNER JOIN` 절을 1개 추가하세요.
* 내부 조인은 일치만 보고하므로, 일치하지 않은 행은 사라집니다.
* 행 1개가 여러 행과 일치하면 행 수가 늘어납니다.
* `USING`은 열이 양쪽에서 같은 이름일 때만 사용하세요.

## 함께 보기

* [조인의 동작 방식](https://villagesql.com/docs/ko/tutorial/joins.md) — 이 조인과 다른 종류의 차이
* [LEFT JOIN](https://villagesql.com/docs/ko/tutorial/left-join.md) — 이 조인이 버리는 행 남기기
* [MySQL JOIN 성능과 흔한 실수](https://villagesql.com/docs/ko/guides/joins.md) — 조인에 인덱스 만들기, 그리고 잘못 나오는 행 수
* [MySQL의 외래 키](https://villagesql.com/docs/ko/guides/foreign-keys.md) — 이 조인들이 따라가는 관계


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.