mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2026-10-28 16:18:23 +08:00
395 lines
15 KiB
Plaintext
395 lines
15 KiB
Plaintext
---
|
|
description: 'ARRAY JOIN 절에 대한 문서'
|
|
sidebarTitle: 'ARRAY JOIN'
|
|
slug: /sql-reference/statements/select/array-join
|
|
title: 'ARRAY JOIN'
|
|
doc_type: 'reference'
|
|
---
|
|
|
|
{/*AUTOGENERATED_START*/}
|
|
|
|
배열 컬럼을 포함하는 테이블에서는 원래 컬럼의 각 배열 요소마다 하나의 행을 갖는 새 테이블을 만들고, 다른 컬럼의 값은 복제하는 작업이 흔히 사용됩니다. 이것이 `ARRAY JOIN` 절이 수행하는 가장 기본적인 형태입니다.
|
|
|
|
이 이름은 배열 또는 중첩 데이터 구조에 대해 `JOIN`을 수행하는 것으로 볼 수 있다는 데서 유래했습니다. 의도는 [arrayJoin](/ko/reference/functions/regular-functions/array-join) 함수와 비슷하지만, 절의 기능이 더 넓습니다.
|
|
|
|
<Note>
|
|
PostgreSQL의 `FROM unnest(...)`, `CROSS JOIN UNNEST(...)`, `LATERAL unnest(...)` 형태의 테이블 소스 구문은 지원되지 않습니다. 대신 `ARRAY JOIN`을 사용하십시오. `unnest` 이름(버전 26.5부터)은 테이블 함수가 아니라 `arrayJoin`의 함수 호출 별칭입니다(`SELECT unnest(arr)`). 상관 서브쿼리(correlated subquery)를 대상으로 하는 `JOIN LATERAL <subquery>`는 별도로 Experimental 기능으로 지원됩니다. 자세한 내용은 [`JOIN`](/ko/reference/statements/select/join#lateral-join)을 참고하십시오.
|
|
</Note>
|
|
|
|
구문:
|
|
|
|
```sql
|
|
SELECT <expr_list>
|
|
FROM <left_subquery>
|
|
[LEFT] ARRAY JOIN <array>
|
|
[WHERE|PREWHERE <expr>]
|
|
...
|
|
```
|
|
|
|
지원되는 `ARRAY JOIN` 유형은 다음과 같습니다:
|
|
|
|
* `ARRAY JOIN` - 기본적으로 빈 배열은 `JOIN` 결과에 포함되지 않습니다.
|
|
* `LEFT ARRAY JOIN` - `JOIN` 결과에는 빈 배열을 가진 행도 포함됩니다. 빈 배열의 값은 배열 요소 타입의 기본값(일반적으로 0, 빈 문자열 또는 NULL)으로 설정됩니다.
|
|
|
|
## 기본 ARRAY JOIN 예시 {#basic-array-join-examples}
|
|
|
|
### ARRAY JOIN 및 LEFT ARRAY JOIN {#array-join-left-array-join-examples}
|
|
|
|
아래 예시는 `ARRAY JOIN` 및 `LEFT ARRAY JOIN` 절의 사용 방법을 보여줍니다. [Array](/ko/reference/data-types/array) 타입의 컬럼이 있는 테이블을 생성하고 여기에 값을 삽입해 보겠습니다:
|
|
|
|
```sql
|
|
CREATE TABLE arrays_test
|
|
(
|
|
s String,
|
|
arr Array(UInt8)
|
|
) ENGINE = Memory;
|
|
|
|
INSERT INTO arrays_test
|
|
VALUES ('Hello', [1,2]), ('World', [3,4,5]), ('Goodbye', []);
|
|
```
|
|
|
|
```response
|
|
┌─s───────────┬─arr─────┐
|
|
│ Hello │ [1,2] │
|
|
│ World │ [3,4,5] │
|
|
│ Goodbye │ [] │
|
|
└─────────────┴─────────┘
|
|
```
|
|
|
|
다음 예시에서는 `ARRAY JOIN` 절을 사용합니다:
|
|
|
|
```sql
|
|
SELECT s, arr
|
|
FROM arrays_test
|
|
ARRAY JOIN arr;
|
|
```
|
|
|
|
```response
|
|
┌─s─────┬─arr─┐
|
|
│ Hello │ 1 │
|
|
│ Hello │ 2 │
|
|
│ World │ 3 │
|
|
│ World │ 4 │
|
|
│ World │ 5 │
|
|
└───────┴─────┘
|
|
```
|
|
|
|
다음 예시에서는 `LEFT ARRAY JOIN` 절을 사용합니다:
|
|
|
|
```sql
|
|
SELECT s, arr
|
|
FROM arrays_test
|
|
LEFT ARRAY JOIN arr;
|
|
```
|
|
|
|
```response
|
|
┌─s───────────┬─arr─┐
|
|
│ Hello │ 1 │
|
|
│ Hello │ 2 │
|
|
│ World │ 3 │
|
|
│ World │ 4 │
|
|
│ World │ 5 │
|
|
│ Goodbye │ 0 │
|
|
└─────────────┴─────┘
|
|
```
|
|
|
|
### ARRAY JOIN 및 arrayEnumerate 함수 {#array-join-arrayEnumerate}
|
|
|
|
이 함수는 일반적으로 `ARRAY JOIN`과 함께 사용됩니다. `ARRAY JOIN`을 적용한 후 각 배열별로 항목을 한 번만 집계할 수 있습니다. 예시:
|
|
|
|
```sql
|
|
SELECT
|
|
count() AS Reaches,
|
|
countIf(num = 1) AS Hits
|
|
FROM test.hits
|
|
ARRAY JOIN
|
|
GoalsReached,
|
|
arrayEnumerate(GoalsReached) AS num
|
|
WHERE CounterID = 160656
|
|
LIMIT 10
|
|
```
|
|
|
|
```text
|
|
┌─Reaches─┬──Hits─┐
|
|
│ 95606 │ 31406 │
|
|
└─────────┴───────┘
|
|
```
|
|
|
|
이 예시에서 Reaches는 전환 수(`ARRAY JOIN` 적용 후 받은 문자열 수)를 의미하고, Hits는 페이지뷰 수(`ARRAY JOIN` 적용 전의 문자열 수)를 의미합니다. 이 사례에서는 더 간단한 방법으로도 동일한 결과를 얻을 수 있습니다:
|
|
|
|
```sql
|
|
SELECT
|
|
sum(length(GoalsReached)) AS Reaches,
|
|
count() AS Hits
|
|
FROM test.hits
|
|
WHERE (CounterID = 160656) AND notEmpty(GoalsReached)
|
|
```
|
|
|
|
```text
|
|
┌─Reaches─┬──Hits─┐
|
|
│ 95606 │ 31406 │
|
|
└─────────┴───────┘
|
|
```
|
|
|
|
### ARRAY JOIN 및 arrayEnumerateUniq {#array_join_arrayEnumerateUniq}
|
|
|
|
이 함수는 `ARRAY JOIN`을 사용해 배열 요소를 집계할 때 유용합니다.
|
|
|
|
이 예시에서는 각 목표 ID별로 전환 수(중첩된 Goals 데이터 구조의 각 요소는 달성된 목표이며, 이를 전환이라고 합니다)와 세션 수를 계산합니다. `ARRAY JOIN`이 없다면 세션 수는 sum(Sign)으로 계산했을 것입니다. 하지만 이 경우에는 중첩된 Goals 구조 때문에 행 수가 늘어났으므로, 이후 각 세션을 한 번씩만 집계하기 위해 `arrayEnumerateUniq(Goals.ID)` 함수 값에 조건을 적용합니다.
|
|
|
|
```sql
|
|
SELECT
|
|
Goals.ID AS GoalID,
|
|
sum(Sign) AS Reaches,
|
|
sumIf(Sign, num = 1) AS Visits
|
|
FROM test.visits
|
|
ARRAY JOIN
|
|
Goals,
|
|
arrayEnumerateUniq(Goals.ID) AS num
|
|
WHERE CounterID = 160656
|
|
GROUP BY GoalID
|
|
ORDER BY Reaches DESC
|
|
LIMIT 10
|
|
```
|
|
|
|
```text
|
|
┌──GoalID─┬─Reaches─┬─Visits─┐
|
|
│ 53225 │ 3214 │ 1097 │
|
|
│ 2825062 │ 3188 │ 1097 │
|
|
│ 56600 │ 2803 │ 488 │
|
|
│ 1989037 │ 2401 │ 365 │
|
|
│ 2830064 │ 2396 │ 910 │
|
|
│ 1113562 │ 2372 │ 373 │
|
|
│ 3270895 │ 2262 │ 812 │
|
|
│ 1084657 │ 2262 │ 345 │
|
|
│ 56599 │ 2260 │ 799 │
|
|
│ 3271094 │ 2256 │ 812 │
|
|
└─────────┴─────────┴────────┘
|
|
```
|
|
|
|
## 별칭 사용 {#using-aliases}
|
|
|
|
`ARRAY JOIN` 절에서 배열에 별칭을 지정할 수 있습니다. 이 경우 배열 항목은 이 별칭으로 참조할 수 있지만, 배열 자체는 원래 이름으로 참조합니다. 예시:
|
|
|
|
```sql
|
|
SELECT s, arr, a
|
|
FROM arrays_test
|
|
ARRAY JOIN arr AS a;
|
|
```
|
|
|
|
```response
|
|
┌─s─────┬─arr─────┬─a─┐
|
|
│ Hello │ [1,2] │ 1 │
|
|
│ Hello │ [1,2] │ 2 │
|
|
│ World │ [3,4,5] │ 3 │
|
|
│ World │ [3,4,5] │ 4 │
|
|
│ World │ [3,4,5] │ 5 │
|
|
└───────┴─────────┴───┘
|
|
```
|
|
|
|
별칭을 사용하면 외부 배열을 사용해 `ARRAY JOIN`을 수행할 수 있습니다. 예를 들면 다음과 같습니다:
|
|
|
|
```sql
|
|
SELECT s, arr_external
|
|
FROM arrays_test
|
|
ARRAY JOIN [1, 2, 3] AS arr_external;
|
|
```
|
|
|
|
```response
|
|
┌─s───────────┬─arr_external─┐
|
|
│ Hello │ 1 │
|
|
│ Hello │ 2 │
|
|
│ Hello │ 3 │
|
|
│ World │ 1 │
|
|
│ World │ 2 │
|
|
│ World │ 3 │
|
|
│ Goodbye │ 1 │
|
|
│ Goodbye │ 2 │
|
|
│ Goodbye │ 3 │
|
|
└─────────────┴──────────────┘
|
|
```
|
|
|
|
여러 배열은 `ARRAY JOIN` 절에서 쉼표로 구분할 수 있습니다. 이 경우 `JOIN`은 이들에 대해 동시에 수행됩니다(카테시안 곱이 아니라 직접합입니다). 기본적으로 모든 배열의 크기는 같아야 한다는 점에 유의하십시오. 예시:
|
|
|
|
```sql
|
|
SELECT s, arr, a, num, mapped
|
|
FROM arrays_test
|
|
ARRAY JOIN arr AS a, arrayEnumerate(arr) AS num, arrayMap(x -> x + 1, arr) AS mapped;
|
|
```
|
|
|
|
```response
|
|
┌─s─────┬─arr─────┬─a─┬─num─┬─mapped─┐
|
|
│ Hello │ [1,2] │ 1 │ 1 │ 2 │
|
|
│ Hello │ [1,2] │ 2 │ 2 │ 3 │
|
|
│ World │ [3,4,5] │ 3 │ 1 │ 4 │
|
|
│ World │ [3,4,5] │ 4 │ 2 │ 5 │
|
|
│ World │ [3,4,5] │ 5 │ 3 │ 6 │
|
|
└───────┴─────────┴───┴─────┴────────┘
|
|
```
|
|
|
|
다음 예시에서는 [arrayEnumerate](/ko/reference/functions/regular-functions/array-functions#arrayEnumerate) 함수를 사용합니다:
|
|
|
|
```sql
|
|
SELECT s, arr, a, num, arrayEnumerate(arr)
|
|
FROM arrays_test
|
|
ARRAY JOIN arr AS a, arrayEnumerate(arr) AS num;
|
|
```
|
|
|
|
```response
|
|
┌─s─────┬─arr─────┬─a─┬─num─┬─arrayEnumerate(arr)─┐
|
|
│ Hello │ [1,2] │ 1 │ 1 │ [1,2] │
|
|
│ Hello │ [1,2] │ 2 │ 2 │ [1,2] │
|
|
│ World │ [3,4,5] │ 3 │ 1 │ [1,2,3] │
|
|
│ World │ [3,4,5] │ 4 │ 2 │ [1,2,3] │
|
|
│ World │ [3,4,5] │ 5 │ 3 │ [1,2,3] │
|
|
└───────┴─────────┴───┴─────┴─────────────────────┘
|
|
```
|
|
|
|
크기가 서로 다른 여러 배열도 `SETTINGS enable_unaligned_array_join = 1`을 사용하면 조인할 수 있습니다. 예시:
|
|
|
|
```sql
|
|
SELECT s, arr, a, b
|
|
FROM arrays_test ARRAY JOIN arr AS a, [['a','b'],['c']] AS b
|
|
SETTINGS enable_unaligned_array_join = 1;
|
|
```
|
|
|
|
```response
|
|
┌─s───────┬─arr─────┬─a─┬─b─────────┐
|
|
│ Hello │ [1,2] │ 1 │ ['a','b'] │
|
|
│ Hello │ [1,2] │ 2 │ ['c'] │
|
|
│ World │ [3,4,5] │ 3 │ ['a','b'] │
|
|
│ World │ [3,4,5] │ 4 │ ['c'] │
|
|
│ World │ [3,4,5] │ 5 │ [] │
|
|
│ Goodbye │ [] │ 0 │ ['a','b'] │
|
|
│ Goodbye │ [] │ 0 │ ['c'] │
|
|
└─────────┴─────────┴───┴───────────┘
|
|
```
|
|
|
|
## 중첩 데이터 구조의 ARRAY JOIN {#array-join-with-nested-data-structure}
|
|
|
|
`ARRAY JOIN`은 [중첩 데이터 구조](/ko/reference/data-types/nested-data-structures/index)에서도 사용할 수 있습니다:
|
|
|
|
```sql
|
|
CREATE TABLE nested_test
|
|
(
|
|
s String,
|
|
nest Nested(
|
|
x UInt8,
|
|
y UInt32)
|
|
) ENGINE = Memory;
|
|
|
|
INSERT INTO nested_test
|
|
VALUES ('Hello', [1,2], [10,20]), ('World', [3,4,5], [30,40,50]), ('Goodbye', [], []);
|
|
```
|
|
|
|
```response
|
|
┌─s───────┬─nest.x──┬─nest.y─────┐
|
|
│ Hello │ [1,2] │ [10,20] │
|
|
│ World │ [3,4,5] │ [30,40,50] │
|
|
│ Goodbye │ [] │ [] │
|
|
└─────────┴─────────┴────────────┘
|
|
```
|
|
|
|
```sql
|
|
SELECT s, `nest.x`, `nest.y`
|
|
FROM nested_test
|
|
ARRAY JOIN nest;
|
|
```
|
|
|
|
```response
|
|
┌─s─────┬─nest.x─┬─nest.y─┐
|
|
│ Hello │ 1 │ 10 │
|
|
│ Hello │ 2 │ 20 │
|
|
│ World │ 3 │ 30 │
|
|
│ World │ 4 │ 40 │
|
|
│ World │ 5 │ 50 │
|
|
└───────┴────────┴────────┘
|
|
```
|
|
|
|
`ARRAY JOIN`에서 중첩 데이터 구조의 이름을 지정하면, 해당 구조를 이루는 모든 배열 요소에 `ARRAY JOIN`을 적용한 것과 같은 의미입니다. 아래에 예시를 나열합니다:
|
|
|
|
```sql
|
|
SELECT s, `nest.x`, `nest.y`
|
|
FROM nested_test
|
|
ARRAY JOIN `nest.x`, `nest.y`;
|
|
```
|
|
|
|
```response
|
|
┌─s─────┬─nest.x─┬─nest.y─┐
|
|
│ Hello │ 1 │ 10 │
|
|
│ Hello │ 2 │ 20 │
|
|
│ World │ 3 │ 30 │
|
|
│ World │ 4 │ 40 │
|
|
│ World │ 5 │ 50 │
|
|
└───────┴────────┴────────┘
|
|
```
|
|
|
|
이와 같은 변형도 가능합니다:
|
|
|
|
```sql
|
|
SELECT s, `nest.x`, `nest.y`
|
|
FROM nested_test
|
|
ARRAY JOIN `nest.x`;
|
|
```
|
|
|
|
```response
|
|
┌─s─────┬─nest.x─┬─nest.y─────┐
|
|
│ Hello │ 1 │ [10,20] │
|
|
│ Hello │ 2 │ [10,20] │
|
|
│ World │ 3 │ [30,40,50] │
|
|
│ World │ 4 │ [30,40,50] │
|
|
│ World │ 5 │ [30,40,50] │
|
|
└───────┴────────┴────────────┘
|
|
```
|
|
|
|
중첩 데이터 구조에는 별칭을 사용할 수 있으며, 이를 통해 `JOIN` 결과 또는 소스 배열을 선택할 수 있습니다. 예시:
|
|
|
|
```sql
|
|
SELECT s, `n.x`, `n.y`, `nest.x`, `nest.y`
|
|
FROM nested_test
|
|
ARRAY JOIN nest AS n;
|
|
```
|
|
|
|
```response
|
|
┌─s─────┬─n.x─┬─n.y─┬─nest.x──┬─nest.y─────┐
|
|
│ Hello │ 1 │ 10 │ [1,2] │ [10,20] │
|
|
│ Hello │ 2 │ 20 │ [1,2] │ [10,20] │
|
|
│ World │ 3 │ 30 │ [3,4,5] │ [30,40,50] │
|
|
│ World │ 4 │ 40 │ [3,4,5] │ [30,40,50] │
|
|
│ World │ 5 │ 50 │ [3,4,5] │ [30,40,50] │
|
|
└───────┴─────┴─────┴─────────┴────────────┘
|
|
```
|
|
|
|
[arrayEnumerate](/ko/reference/functions/regular-functions/array-functions#arrayEnumerate) 함수 사용 예시:
|
|
|
|
```sql
|
|
SELECT s, `n.x`, `n.y`, `nest.x`, `nest.y`, num
|
|
FROM nested_test
|
|
ARRAY JOIN nest AS n, arrayEnumerate(`nest.x`) AS num;
|
|
```
|
|
|
|
```response
|
|
┌─s─────┬─n.x─┬─n.y─┬─nest.x──┬─nest.y─────┬─num─┐
|
|
│ Hello │ 1 │ 10 │ [1,2] │ [10,20] │ 1 │
|
|
│ Hello │ 2 │ 20 │ [1,2] │ [10,20] │ 2 │
|
|
│ World │ 3 │ 30 │ [3,4,5] │ [30,40,50] │ 1 │
|
|
│ World │ 4 │ 40 │ [3,4,5] │ [30,40,50] │ 2 │
|
|
│ World │ 5 │ 50 │ [3,4,5] │ [30,40,50] │ 3 │
|
|
└───────┴─────┴─────┴─────────┴────────────┴─────┘
|
|
```
|
|
|
|
## 구현 세부 사항 {#implementation-details}
|
|
|
|
`ARRAY JOIN`을 실행할 때 쿼리 실행 순서는 최적화됩니다. 쿼리에서는 `ARRAY JOIN`을 항상 [WHERE](/ko/reference/statements/select/where)/[PREWHERE](/ko/reference/statements/select/prewhere) 절보다 앞에 지정해야 하지만, `ARRAY JOIN`의 결과를 필터링에 사용하지 않는다면 기술적으로는 어느 쪽을 먼저 수행해도 됩니다. 처리 순서는 쿼리 옵티마이저가 제어합니다.
|
|
|
|
### 쇼트서킷 함수 평가와의 비호환성 {#incompatibility-with-short-circuit-function-evaluation}
|
|
|
|
[쇼트서킷 함수 평가](/ko/reference/settings/session-settings/short-circuit-function-evaluation#short_circuit_function_evaluation)는 `if`, `multiIf`, `and`, `or`와 같은 특정 함수에서 복잡한 표현식의 실행을 최적화하는 기능입니다. 이 기능은 이러한 함수를 실행하는 동안 0으로 나누기와 같은 잠재적인 예외가 발생하지 않도록 합니다.
|
|
|
|
`arrayJoin`은 항상 실행되며 쇼트서킷 함수 평가를 지원하지 않습니다. 이는 `arrayJoin`이 쿼리 분석 및 실행 과정에서 다른 모든 함수와 별도로 처리되는 고유한 함수이고, 쇼트서킷 함수 실행과는 호환되지 않는 추가 로직이 필요하기 때문입니다. 또한 결과의 행 수는 `arrayJoin`의 결과에 따라 달라지므로, `arrayJoin`의 지연 실행을 구현하는 것은 지나치게 복잡하고 비용도 많이 듭니다.
|
|
|
|
## 관련 콘텐츠 {#related-content}
|
|
|
|
* 블로그: [ClickHouse에서 시계열 데이터 다루기](https://clickhouse.com/blog/working-with-time-series-data-and-functions-ClickHouse)
|
|
|
|
{/*AUTOGENERATED_END*/} |