> ## 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 GROUP BY

> GROUP BY가 MySQL 행을 그룹으로 나누고 그룹마다 1개의 행을 반환하는 방법, 여러 열로 그룹화하는 방법, 그리고 ERROR 1055가 말해 주는 것입니다.

> [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 →

`GROUP BY`가 없는 집계 함수는 테이블 전체를 1개의 그룹으로 취급하고 1개의 행을 반환합니다. `GROUP BY`는 행을 먼저 그룹으로 나누므로, 그룹마다 1개의 행을 얻습니다. "영화가 몇 편인가"가 "등급마다 영화가 몇 편인가"가 되는 방법입니다.

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

## GROUP BY의 동작 방식

```sql theme={null}
SELECT GROUPING_COLUMN, AGGREGATE_FUNCTION(COLUMN_NAME)
FROM TABLE_NAME
GROUP BY GROUPING_COLUMN;
```

`GROUPING_COLUMN`을 값이 그룹을 정의하는 열로, `TABLE_NAME`을 테이블로, `AGGREGATE_FUNCTION(COLUMN_NAME)`을 각 그룹에 대해 계산하고 싶은 것으로 바꾸세요.

서버는 행을 그룹화 열의 서로 다른 값마다 1개씩의 바구니로 분류하고, 각 바구니에 대해 따로 집계를 실행합니다. 결과는 서로 다른 값의 수만큼의 행을 가집니다.

`GROUP BY`는 `WHERE` 뒤, `ORDER BY` 앞에 오며, 이것은 결과가 정의되는 순서이기도 합니다: 행을 필터링하고, 살아남은 것을 그룹화한 다음, 그룹을 정렬합니다.

## 예제

### 값마다 한 행

```sql theme={null}
SELECT rating, COUNT(*) AS films
FROM film
GROUP BY rating
ORDER BY rating;
```

```text theme={null}
+--------+-------+
| rating | films |
+--------+-------+
| G      |   178 |
| PG     |   194 |
| PG-13  |   223 |
| R      |   195 |
| NC-17  |   210 |
+--------+-------+
5 rows in set (0.00 sec)
```

`rating`에 5개의 서로 다른 값이 있으므로 5개의 그룹이고, 5개의 개수를 더하면 1,000이 됩니다. 행은 알파벳순이 아니라 `ENUM`의 선언 순서로 나오며, [SELECT DISTINCT](https://villagesql.com/docs/ko/tutorial/select-distinct.md) 레슨이 그것을 설명합니다.

### 2개의 열로 그룹화

2개의 열을 지정하면, 그룹은 각 열의 값이 아니라 서로 다른 짝입니다.

```sql theme={null}
SELECT rating, rental_rate, COUNT(*) AS films
FROM film
GROUP BY rating, rental_rate
ORDER BY rating, rental_rate;
```

```text theme={null}
+--------+-------------+-------+
| rating | rental_rate | films |
+--------+-------------+-------+
| G      |        0.99 |    64 |
| G      |        2.99 |    59 |
| G      |        4.99 |    55 |
| PG     |        0.99 |    62 |
| PG     |        2.99 |    64 |
| PG     |        4.99 |    68 |
...
+--------+-------------+-------+
15 rows in set (0.01 sec)
```

5개의 등급 곱하기 3개의 요금은 15개의 그룹입니다. 이것은 `SELECT DISTINCT rating, rental_rate`가 반환하는 것과 같은 형태에, 각 행에 개수가 붙은 것입니다.

### 그룹화 전에 행 필터링하기

`WHERE`가 먼저 실행되므로, 개수는 그것을 통과한 행에 대한 것입니다.

```sql theme={null}
SELECT rating, COUNT(*) AS films
FROM film
WHERE length > 120
GROUP BY rating
ORDER BY rating;
```

```text theme={null}
+--------+-------+
| rating | films |
+--------+-------+
| G      |    72 |
| PG     |    82 |
| PG-13  |   118 |
| R      |    90 |
| NC-17  |    95 |
+--------+-------+
5 rows in set (0.00 sec)
```

여전히 5개의 그룹이고, 모두 더 작아졌습니다. 그룹은 `WHERE`가 그 그룹의 행을 모두 제거할 때만 완전히 사라집니다.

### 계산한 것으로 그룹화

그룹화 열이 반드시 열일 필요는 없습니다. 어떤 표현식이든 되고, 별칭으로 이름을 붙여 그것으로 그룹화할 수 있습니다.

```sql theme={null}
SELECT LEFT(title, 1) AS initial, COUNT(*) AS films
FROM film
GROUP BY initial
ORDER BY initial
LIMIT 5;
```

```text theme={null}
+---------+-------+
| initial | films |
+---------+-------+
| A       |    46 |
| B       |    63 |
| C       |    92 |
| D       |    65 |
| E       |    32 |
+---------+-------+
5 rows in set (0.00 sec)
```

`GROUP BY`는 select 목록의 별칭을 받아들이지만, `WHERE`는 그렇지 않습니다. 보고서가 월별로 그룹화되는 방법이 이것입니다: 월을 계산하고, 그것으로 그룹화합니다.

### 조인 위에서 그룹화

그룹화는 행이 2개 이상의 테이블에서 올 때 가장 유용합니다. 이 쿼리는 각 고객의 결제를 셉니다.

```sql theme={null}
SELECT c.last_name, COUNT(*) AS payments
FROM customer AS c
INNER JOIN payment AS p ON p.customer_id = c.customer_id
GROUP BY c.customer_id, c.last_name
ORDER BY payments DESC, c.last_name
LIMIT 5;
```

```text theme={null}
+-----------+----------+
| last_name | payments |
+-----------+----------+
| HUNT      |       46 |
| SEAL      |       45 |
| DEAN      |       42 |
| SHAW      |       42 |
| SANDERS   |       41 |
+-----------+----------+
5 rows in set (0.01 sec)
```

조인이 먼저 실행되어 결제마다 1개의 행을 만들고, 그다음 그룹화가 그 행들을 고객별로 합칩니다. `last_name`과 함께 `customer_id`도 `GROUP BY`에 있는데, 2명의 고객이 같은 성을 가질 수 있고 그들을 따로 세고 싶기 때문입니다.

## 선택한 모든 열은 그룹화되거나 집계되어야 합니다

그룹은 많은 행입니다. 그룹화 열도 아니고 집계 함수 안에도 없는 열을 요청하는 것은 그 많은 행 중 어느 것을 보여 줄지 서버에 묻는 것이고, 서버에는 답이 없습니다. 기본적으로 켜져 있는 `only_full_group_by` 설정이 서버가 추측하는 대신 그렇다고 말하게 만드는 것입니다.

```sql theme={null}
SELECT rating, title FROM film GROUP BY rating;
```

```text theme={null}
ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'sakila.film.title' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
```

`ERROR 1055`는 해결할 수 없었던 열의 이름을 말해 주는데, 여기서는 `title`입니다. 빠져나가는 방법은 3가지이고, 어느 것이 맞는지는 의도에 따라 다릅니다:

* 더 세밀한 그룹을 원했다면, 그 열을 `GROUP BY`에 추가하세요.
* 아무 값 1개면 된다면, `MIN(title)` 같은 집계 함수로 감싸세요.
* 필요 없었다면, select 목록에서 빼세요.

`only_full_group_by`를 끄면 같은 쿼리가 실행되고, 서버가 고른 플랜에 따라 임의의 제목을 반환합니다. [MySQL GROUP BY 성능](https://villagesql.com/docs/ko/guides/group-by-having.md) 가이드가 그 설정과, 일부러 임의의 행을 요청하는 방법인 `ANY_VALUE()`를 다룹니다.

## GROUP BY는 순서를 약속하지 않습니다

그룹은 서버가 편리하다고 본 순서로 돌아옵니다. 여기의 모든 예제가 `ORDER BY`를 쓰는 것은 그것이 순서를 고정하는 유일한 방법이기 때문이며, [LIMIT](https://villagesql.com/docs/ko/tutorial/limit.md)에서와 같습니다.

## 요약

* 전체 1개 대신 서로 다른 값마다 1개의 결과 행을 얻으려면 `GROUP BY`를 사용하세요.
* 여러 열을 지정하면 서로 다른 조합마다 그룹이 생긴다고 예상하세요.
* `WHERE`가 그룹화가 일어나기 전에 행을 필터링한다고 예상하세요.
* `ERROR 1055`는 그룹화되지도 집계되지도 않은 열로 읽으세요.
* 그룹의 순서가 중요하면 `ORDER BY`를 추가하세요.

## 함께 보기

* [집계 함수](https://villagesql.com/docs/ko/tutorial/aggregate-functions.md) — 그룹마다 계산할 것
* [HAVING](https://villagesql.com/docs/ko/tutorial/having.md) — 그룹이 만들어진 뒤에 그룹 필터링하기
* [WITH ROLLUP](https://villagesql.com/docs/ko/tutorial/with-rollup.md) — 그룹 아래에 합계 행 추가하기
* [MySQL GROUP BY 성능](https://villagesql.com/docs/ko/guides/group-by-having.md) — 이것이 만들 수 있는 임시 테이블


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