ClickHouse/docs/ja/sql-reference/statements/select/join.md
2024-11-18 11:58:58 +09:00

526 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
slug: /ja/sql-reference/statements/select/join
sidebar_label: Join Table
---
# JOIN句
**JOIN**は、共通の値を使用して複数のテーブルの**カラム**を組み合わせることで、新しいテーブルを生成します。これは、SQLをサポートするデータベースで一般的な操作であり、[関係代数](https://en.wikipedia.org/wiki/Relational_algebra#Joins_and_join-like_operators)の結合に対応します。1つのテーブル内での結合の特別なケースは、「自己結合」と呼ばれることがよくあります。
**構文**
``` sql
SELECT <expr_list>
FROM <left_table>
[GLOBAL] [INNER|LEFT|RIGHT|FULL|CROSS] [OUTER|SEMI|ANTI|ANY|ALL|ASOF] JOIN <right_table>
(ON <expr_list>)|(USING <column_list>) ...
```
`ON`句からの式と`USING`句からの**カラム**は「結合キー」と呼ばれます。特に指定がない限り、結合は一致する「結合キー」を持つ**行**からの[直積](https://en.wikipedia.org/wiki/Cartesian_product)を生成し、元のテーブルよりもはるかに多くの**行**を持つ結果を生成する可能性があります。
## 関連コンテンツ
- ブログ: [ClickHouse: A Blazingly Fast DBMS with Full SQL Join Support - Part 1](https://clickhouse.com/blog/clickhouse-fully-supports-joins)
- ブログ: [ClickHouse: A Blazingly Fast DBMS with Full SQL Join Support - Under the Hood - Part 2](https://clickhouse.com/blog/clickhouse-fully-supports-joins-hash-joins-part2)
- ブログ: [ClickHouse: A Blazingly Fast DBMS with Full SQL Join Support - Under the Hood - Part 3](https://clickhouse.com/blog/clickhouse-fully-supports-joins-full-sort-partial-merge-part3)
- ブログ: [ClickHouse: A Blazingly Fast DBMS with Full SQL Join Support - Under the Hood - Part 4](https://clickhouse.com/blog/clickhouse-fully-supports-joins-direct-join-part4)
## サポートされるJOINの種類
全ての標準[SQL JOIN](https://en.wikipedia.org/wiki/Join_(SQL))タイプがサポートされています:
- `INNER JOIN`: 一致する**行**のみが返されます。
- `LEFT OUTER JOIN`: 左テーブルからの非一致行も一致した**行**と共に返されます。
- `RIGHT OUTER JOIN`: 右テーブルからの非一致行も一致した**行**と共に返されます。
- `FULL OUTER JOIN`: 両テーブルからの非一致行も一致した**行**と共に返されます。
- `CROSS JOIN`: テーブル全体の直積を生成し、「結合キー」は**指定しません**。
`JOIN`でタイプが指定されていない場合、`INNER`が暗黙に適用されます。キーワード`OUTER`は省略可能です。`CROSS JOIN`の代替構文として[FROM句](../../../sql-reference/statements/select/from.md)で複数のテーブルをカンマで区切って指定することもできます。
ClickHouseで利用可能な追加の結合タイプ:
- `LEFT SEMI JOIN`と`RIGHT SEMI JOIN`: 「結合キー」のホワイトリストを生成し、直積を生成しません。
- `LEFT ANTI JOIN`と`RIGHT ANTI JOIN`: 「結合キー」のブラックリストを生成し、直積を生成しません。
- `LEFT ANY JOIN`, `RIGHT ANY JOIN`と`INNER ANY JOIN`: 標準的な`JOIN`タイプの直積を部分的(`LEFT`と`RIGHT`の反対側)または完全(`INNER`と`FULL`)に無効にします。
- `ASOF JOIN`と`LEFT ASOF JOIN`: 厳密でない一致でシーケンスを結合します。`ASOF JOIN`の使用法については以下で説明します。
- `PASTE JOIN`: 2つのテーブルを水平方向に結合します。
:::note
`join_algorithm`が`partial_merge`に設定されている場合、`RIGHT JOIN`と`FULL JOIN`は`ALL`厳密性(`SEMI`, `ANTI`, `ANY`, `ASOF`はサポートされていません)のみサポートされます。
:::
## 設定
デフォルトの結合タイプは[join_default_strictness](../../../operations/settings/settings.md#join_default_strictness)設定を使用してオーバーライドすることができます。
`ANY JOIN`操作に対するClickHouseサーバーの動作は[any_join_distinct_right_table_keys](../../../operations/settings/settings.md#any_join_distinct_right_table_keys)設定に依存します。
**参照**
- [join_algorithm](../../../operations/settings/settings.md#join_algorithm)
- [join_any_take_last_row](../../../operations/settings/settings.md#join_any_take_last_row)
- [join_use_nulls](../../../operations/settings/settings.md#join_use_nulls)
- [partial_merge_join_optimizations](../../../operations/settings/settings.md#partial_merge_join_optimizations)
- [partial_merge_join_rows_in_right_blocks](../../../operations/settings/settings.md#partial_merge_join_rows_in_right_blocks)
- [join_on_disk_max_files_to_merge](../../../operations/settings/settings.md#join_on_disk_max_files_to_merge)
- [any_join_distinct_right_table_keys](../../../operations/settings/settings.md#any_join_distinct_right_table_keys)
ClickHouseが`CROSS JOIN`を`INNER JOIN`として書き直すのに失敗した時の動作を定義するために、`cross_to_inner_join_rewrite`設定を使用してください。デフォルト値は`1`であり、これにより結合は継続されますが、遅くなります。エラーを発生させたい場合は`cross_to_inner_join_rewrite`を`0`に設定し、全てのカンマ/クロス結合を書く直すことを強制したい場合は`2`に設定してください。値が`2`のときに書き換えが失敗すると、「`WHERE`セクションを簡略化してみてください」というエラーメッセージが表示されます。
## ONセクションの条件
`ON`セクションは`AND`および`OR`演算子を使用して結合された複数の条件を含むことができます。結合キーを指定する条件は、左テーブルと右テーブルの両方を参照し、等号演算子を使用しなければなりません。他の条件は、その他の論理演算子を使用できますが、**クエリ**の左または右テーブルのいずれかを参照する必要があります。
条件が満たされると**行**が結合されます。条件が満たされない場合でも、`JOIN`タイプによっては**行**が結果に含まれることがあります。注意すべき点は、同じ条件が`WHERE`セクションに配置され、条件が満たされていない場合、**行**は常に結果からフィルタリングされます。
`ON`句内の`OR`演算子はハッシュ結合アルゴリズムを使用して動作します — `JOIN`の結合キーを持つ各`OR`引数に対して、個別のハッシュテーブルが作成されるため、メモリ消費と**クエリ**の実行時間は`ON`句の`OR`の表現の数の増加に伴い線形に増加します。
:::note
異なるテーブルの**カラム**に言及する条件の場合、現時点では等号演算子(`=`)のみがサポートされています。
:::
**例**
`table_1`と`table_2`を考慮してください:
```
┌─Id─┬─name─┐ ┌─Id─┬─text───────────┬─scores─┐
│ 1 │ A │ │ 1 │ Text A │ 10 │
│ 2 │ B │ │ 1 │ Another text A │ 12 │
│ 3 │ C │ │ 2 │ Text B │ 15 │
└────┴──────┘ └────┴────────────────┴────────┘
```
1つの結合キー条件と`table_2`に対する追加の条件を持つ**クエリ**
``` sql
SELECT name, text FROM table_1 LEFT OUTER JOIN table_2
ON table_1.Id = table_2.Id AND startsWith(table_2.text, 'Text');
```
結果には、nameが`C`でテキストが空の**行**が含まれています。これは、`OUTER`タイプの結合が使用されているために結果に含まれています。
```
┌─name─┬─text───┐
│ A │ Text A │
│ B │ Text B │
│ C │ │
└──────┴────────┘
```
`INNER`タイプの結合と複数の条件を持つ**クエリ**
``` sql
SELECT name, text, scores FROM table_1 INNER JOIN table_2
ON table_1.Id = table_2.Id AND table_2.scores > 10 AND startsWith(table_2.text, 'Text');
```
結果:
```
┌─name─┬─text───┬─scores─┐
│ B │ Text B │ 15 │
└──────┴────────┴────────┘
```
`INNER`タイプの結合と`OR`を含む条件を持つ**クエリ**
``` sql
CREATE TABLE t1 (`a` Int64, `b` Int64) ENGINE = MergeTree() ORDER BY a;
CREATE TABLE t2 (`key` Int32, `val` Int64) ENGINE = MergeTree() ORDER BY key;
INSERT INTO t1 SELECT number as a, -a as b from numbers(5);
INSERT INTO t2 SELECT if(number % 2 == 0, toInt64(number), -number) as key, number as val from numbers(5);
SELECT a, b, val FROM t1 INNER JOIN t2 ON t1.a = t2.key OR t1.b = t2.key;
```
結果:
```
┌─a─┬──b─┬─val─┐
│ 0 │ 0 │ 0 │
│ 1 │ -1 │ 1 │
│ 2 │ -2 │ 2 │
│ 3 │ -3 │ 3 │
│ 4 │ -4 │ 4 │
└───┴────┴─────┘
```
`INNER`タイプの結合と`OR`および`AND`を含む条件を持つ**クエリ**
:::note
デフォルトでは、異なるテーブルからのカラムを使う条件はサポートされません。例えば`t1.a = t2.key AND t1.b > 0 AND t2.b > t2.c`は`t1.b > 0`が`t1`のカラムのみを使用し、`t2.b > t2.c`が`t2`のカラムのみを使用するため可能です。しかし、`t1.a = t2.key AND t1.b > t2.key`のような条件のエクスペリメンタルサポートを試みることができます。詳細については以下を参照してください。
:::
``` sql
SELECT a, b, val FROM t1 INNER JOIN t2 ON t1.a = t2.key OR t1.b = t2.key AND t2.val > 3;
```
結果:
```
┌─a─┬──b─┬─val─┐
│ 0 │ 0 │ 0 │
│ 2 │ -2 │ 2 │
│ 4 │ -4 │ 4 │
└───┴────┴─────┘
```
## [試験的機能] 異なるテーブルのカラムに対する不等式条件を伴う結合
:::note
この機能は試験的です。これを利用するには、設定ファイルや`SET`コマンドを用いて`allow_experimental_join_condition`を1に設定してください
```sql
SET allow_experimental_join_condition=1
```
そうでなければ、`INVALID_JOIN_ON_EXPRESSION`が返されます。
:::
ClickHouseは現在、等式条件に加えて不等式条件を持つ`ALL/ANY/SEMI/ANTI INNER/LEFT/RIGHT/FULL JOIN`をサポートしています。不等式条件は`hash`および`grace_hash`結合アルゴリズムのみでサポートされています。不等式条件は`join_use_nulls`ではサポートされていません。
**例**
テーブル`t1`:
```
┌─key──┬─attr─┬─a─┬─b─┬─c─┐
│ key1 │ a │ 1 │ 1 │ 2 │
│ key1 │ b │ 2 │ 3 │ 2 │
│ key1 │ c │ 3 │ 2 │ 1 │
│ key1 │ d │ 4 │ 7 │ 2 │
│ key1 │ e │ 5 │ 5 │ 5 │
│ key2 │ a2 │ 1 │ 1 │ 1 │
│ key4 │ f │ 2 │ 3 │ 4 │
└──────┴──────┴───┴───┴───┘
```
テーブル`t2`
```
┌─key──┬─attr─┬─a─┬─b─┬─c─┐
│ key1 │ A │ 1 │ 2 │ 1 │
│ key1 │ B │ 2 │ 1 │ 2 │
│ key1 │ C │ 3 │ 4 │ 5 │
│ key1 │ D │ 4 │ 1 │ 6 │
│ key3 │ a3 │ 1 │ 1 │ 1 │
│ key4 │ F │ 1 │ 1 │ 1 │
└──────┴──────┴───┴───┴───┘
```
```sql
SELECT t1.*, t2.* from t1 LEFT JOIN t2 ON t1.key = t2.key and (t1.a < t2.a) ORDER BY (t1.key, t1.attr, t2.key, t2.attr);
```
```
key1 a 1 1 2 key1 B 2 1 2
key1 a 1 1 2 key1 C 3 4 5
key1 a 1 1 2 key1 D 4 1 6
key1 b 2 3 2 key1 C 3 4 5
key1 b 2 3 2 key1 D 4 1 6
key1 c 3 2 1 key1 D 4 1 6
key1 d 4 7 2 0 0 \N
key1 e 5 5 5 0 0 \N
key2 a2 1 1 1 0 0 \N
key4 f 2 3 4 0 0 \N
```
## JOINキー内のNULL値
NULLはどの値とも、また自身とも等しくありません。つまり、JOINキーにNULL値が一方のテーブルにある場合、もう一方のテーブルのNULL値と一致しません。
**例**
テーブル`A`:
```
┌───id─┬─name────┐
│ 1 │ Alice │
│ 2 │ Bob │
│ ᴺᵁᴸᴸ │ Charlie │
└──────┴─────────┘
```
テーブル`B`:
```
┌───id─┬─score─┐
│ 1 │ 90 │
│ 3 │ 85 │
│ ᴺᵁᴸᴸ │ 88 │
└──────┴───────┘
```
```sql
SELECT A.name, B.score FROM A LEFT JOIN B ON A.id = B.id
```
```
┌─name────┬─score─┐
│ Alice │ 90 │
│ Bob │ 0 │
│ Charlie │ 0 │
└─────────┴───────┘
```
`A`テーブルの`Charlie`行と`B`テーブルのスコア88の行は、JOINキーのNULL値のため結果に含まれていないことに注意してください。
NULL値を一致させたい場合は、`isNotDistinctFrom`関数を使用してJOINキーを比較します。
```sql
SELECT A.name, B.score FROM A LEFT JOIN B ON isNotDistinctFrom(A.id, B.id)
```
```
┌─name────┬─score─┐
│ Alice │ 90 │
│ Bob │ 0 │
│ Charlie │ 88 │
└─────────┴───────┘
```
## ASOF JOINの使用方法
`ASOF JOIN`は、正確な一致がないレコードを結合するときに役立ちます。
アルゴリズムには特別な**カラム**がテーブルに必要です。この**カラム**:
- 順序付けられたシーケンスを含まなければならない
- 次のいずれかの型を持つことができる: [Int, UInt](../../../sql-reference/data-types/int-uint.md), [Float](../../../sql-reference/data-types/float.md), [Date](../../../sql-reference/data-types/date.md), [DateTime](../../../sql-reference/data-types/datetime.md), [Decimal](../../../sql-reference/data-types/decimal.md)
- `hash`結合アルゴリズムでは、`JOIN`の句に唯一の**カラム**にすることはできない
構文 `ASOF JOIN ... ON`:
``` sql
SELECT expressions_list
FROM table_1
ASOF LEFT JOIN table_2
ON equi_cond AND closest_match_cond
```
任意の数の等式条件と厳密に1つの最も近い一致条件を使用できます。例えば、`SELECT count() FROM table_1 ASOF LEFT JOIN table_2 ON table_1.a == table_2.b AND table_2.t <= table_1.t`。
最も近い一致でサポートされる条件: `>`, `>=`, `<`, `<=`。
構文 `ASOF JOIN ... USING`:
``` sql
SELECT expressions_list
FROM table_1
ASOF JOIN table_2
USING (equi_column1, ... equi_columnN, asof_column)
```
`ASOF JOIN`は`equi_columnX`を等価結合に使用し、`asof_column`を最も近い一致での結合に使用します。この`asof_column`は常に`USING`句の最後の**カラム**です。
例えば、以下のテーブルを考慮してください:
table_1 table_2
event | ev_time | user_id event | ev_time | user_id
----------|---------|---------- ----------|---------|----------
... ...
event_1_1 | 12:00 | 42 event_2_1 | 11:59 | 42
... event_2_2 | 12:30 | 42
event_1_2 | 13:00 | 42 event_2_3 | 13:00 | 42
... ...
`ASOF JOIN`は、`table_1`のユーザーイベントのタイムスタンプを取得し、最も近い一致条件に対応する`table_1`イベントのタイムスタンプに最も近い`table_2`のイベントを見つけます。等しいタイムスタンプの値が利用可能な場合、最も近いと見なされます。この例では、`user_id`列は等価結合に使用され、`ev_time`列は最も近い一致での結合に使用されます。この例では、`event_1_1`は`event_2_1`と結合され、`event_1_2`は`event_2_3`と結合されますが、`event_2_2`は結合されません。
:::note
`ASOF JOIN`は`hash`および`full_sorting_merge`結合アルゴリズムのみでサポートされます。
[Join](../../../engines/table-engines/special/join.md)テーブルエンジンではサポートされていません。
:::
## PASTE JOINの使用方法
`PASTE JOIN`の結果は、左サブクエリのすべての**カラム**の後に右サブクエリのすべての**カラム**が続くテーブルです。
元のテーブルでのポジションに基づいて**行**が一致します(**行**の順序は定義されている必要があります)。
サブクエリが異なる数の**行**を返す場合、余分な**行**はカットされます。
例:
```SQL
SELECT *
FROM
(
SELECT number AS a
FROM numbers(2)
) AS t1
PASTE JOIN
(
SELECT number AS a
FROM numbers(2)
ORDER BY a DESC
) AS t2
┌─a─┬─t2.a─┐
0 1
1 0
└───┴──────┘
```
注意: この場合、結果は並列で読み取ると非決定的になる可能性があります。例:
```SQL
SELECT *
FROM
(
SELECT number AS a
FROM numbers_mt(5)
) AS t1
PASTE JOIN
(
SELECT number AS a
FROM numbers(10)
ORDER BY a DESC
) AS t2
SETTINGS max_block_size = 2;
┌─a─┬─t2.a─┐
2 9
3 8
└───┴──────┘
┌─a─┬─t2.a─┐
0 7
1 6
└───┴──────┘
┌─a─┬─t2.a─┐
4 5
└───┴──────┘
```
## 分散JOIN
分散テーブルを含むJOINを実行する方法は2つあります:
- 通常の`JOIN`を使用する場合、クエリはリモートサーバーに送信されます。サブクエリはそれぞれのサーバーで実行され、右テーブルが作成され、このテーブルとのJOINが実行されます。言い換えれば、右テーブルは各サーバーで個別に形成されます。
- `GLOBAL ... JOIN`を使用する場合、まずリクエスタサーバーが右テーブルを計算するためのサブクエリを実行します。この一時テーブルは各リモートサーバーに渡され、送信された一時データを使用してクエリが実行されます。
`GLOBAL`を使用する際は注意してください。詳細は[分散サブクエリ](../../../sql-reference/operators/in.md#select-distributed-subqueries)セクションを参照してください。
## 暗黙の型変換
`INNER JOIN`, `LEFT JOIN`, `RIGHT JOIN`, および`FULL JOIN` **クエリ**は、「結合キー」の暗黙的な型変換をサポートしています。ただし、左テーブルと右テーブルからの結合キーが単一の型に変換できない場合(例えば、`UInt64`と`Int64`、あるいは`String`と`Int32`)にクエリは実行されません。
**例**
以下のテーブル `t_1` を考慮してください:
```text
┌─a─┬─b─┬─toTypeName(a)─┬─toTypeName(b)─┐
│ 1 │ 1 │ UInt16 │ UInt8 │
│ 2 │ 2 │ UInt16 │ UInt8 │
└───┴───┴───────────────┴───────────────┘
```
そしてテーブル `t_2`
```text
┌──a─┬────b─┬─toTypeName(a)─┬─toTypeName(b)───┐
│ -1 │ 1 │ Int16 │ Nullable(Int64) │
│ 1 │ -1 │ Int16 │ Nullable(Int64) │
│ 1 │ 1 │ Int16 │ Nullable(Int64) │
└────┴──────┴───────────────┴─────────────────┘
```
以下のクエリ
```sql
SELECT a, b, toTypeName(a), toTypeName(b) FROM t_1 FULL JOIN t_2 USING (a, b);
```
が返すセット:
```text
┌──a─┬────b─┬─toTypeName(a)─┬─toTypeName(b)───┐
│ 1 │ 1 │ Int32 │ Nullable(Int64) │
│ 2 │ 2 │ Int32 │ Nullable(Int64) │
│ -1 │ 1 │ Int32 │ Nullable(Int64) │
│ 1 │ -1 │ Int32 │ Nullable(Int64) │
└────┴──────┴───────────────┴─────────────────┘
```
## 使用の推奨事項
### 空またはNULLセルの処理
テーブルを結合するとき、空のセルが生じる場合があります。[join_use_nulls](../../../operations/settings/settings.md#join_use_nulls)設定はClickHouseがこれらのセルをどのように埋めるかを定義します。
`JOIN`キーが[Nullable](../../../sql-reference/data-types/nullable.md)フィールドである場合、少なくとも1つのキーが[NULL](../../../sql-reference/syntax.md#null-literal)値を持っている**行**は結合されません。
### 構文
`USING`で指定された**カラム**は両方のサブクエリで同じ名前を持たなければならず、他の**カラム**は異なる名前でなければなりません。サブクエリ内で**カラム**の名前を変更するためにエイリアスを使用できます。
`USING`句は1つ以上の**カラム**を指定することで、これらのカラムの等価性を確立します。**カラム**のリストは括弧を用いずに設定されます。より複雑な結合条件はサポートされていません。
### 構文制限
1つの`SELECT` **クエリ**に複数の`JOIN`句がある場合:
- `*`による全てのカラムの取得は、サブクエリの**カラム**でなくテーブルが結合される場合のみ利用可能です。
- `PREWHERE`句は利用できません。
- `USING`句は利用できません。
`ON`、`WHERE`、および`GROUP BY`句の場合:
- `ON`、`WHERE`、および`GROUP BY`句で任意の式を使用することはできませんが、`SELECT`句で式を定義し、エイリアスを介してこれらの句で使用することはできます。
### パフォーマンス
`JOIN`を実行する際、クエリの他のステージに関連する実行順序の最適化はありません。JOIN右テーブルの検索は`WHERE`でのフィルタリングの前に、集計の前に実行されます。
同じ`JOIN`を伴うクエリを実行するたびにサブクエリが再度実行されます。結果はキャッシュされません。これを避けるために、特殊な[Join](../../../engines/table-engines/special/join.md)テーブルエンジンを使用します。このエンジンは、常にRAMにある結合のための準備された配列です。
場合によっては、`JOIN`の代わりに[IN](../../../sql-reference/operators/in.md)を使用する方が効率的です。
ディメンションテーブル(広告キャンペーンの名前などのディメンションプロパティを含む比較的小さなテーブル)との結合に`JOIN`が必要な場合、右テーブルが毎回再アクセスされるため`JOIN`はあまり便利ではないかもしれません。そういった場合には「Dictionary」機能を使用することをお勧めします。詳細は[Dictionaries](../../../sql-reference/dictionaries/index.md)セクションを参照してください。
### メモリ制限
デフォルトでClickHouseは[ハッシュ結合](https://en.wikipedia.org/wiki/Hash_join)アルゴリズムを使用します。ClickHouseはright_tableを取り、RAMにハッシュテーブルを作成します。`join_algorithm = 'auto'`が有効な場合、メモリ消費のしきい値を超えるとClickHouseは[マージ](https://en.wikipedia.org/wiki/Sort-merge_join)結合アルゴリズムにフォールバックします。`JOIN`アルゴリズムの説明は[join_algorithm](../../../operations/settings/settings.md#join_algorithm)設定を参照してください。
`JOIN`操作のメモリ消費を制限する必要がある場合、以下の設定を使用します:
- [max_rows_in_join](../../../operations/settings/query-complexity.md#settings-max_rows_in_join) — ハッシュテーブルの行数を制限します。
- [max_bytes_in_join](../../../operations/settings/query-complexity.md#settings-max_bytes_in_join) — ハッシュテーブルのサイズを制限します。
これらの制限のいずれかが到達された場合、ClickHouseは[join_overflow_mode](../../../operations/settings/query-complexity.md#settings-join_overflow_mode)設定に指示された通りに動作します。
## 例
``` sql
SELECT
CounterID,
hits,
visits
FROM
(
SELECT
CounterID,
count() AS hits
FROM test.hits
GROUP BY CounterID
) ANY LEFT JOIN
(
SELECT
CounterID,
sum(Sign) AS visits
FROM test.visits
GROUP BY CounterID
) USING CounterID
ORDER BY hits DESC
LIMIT 10
```
``` text
┌─CounterID─┬───hits─┬─visits─┐
│ 1143050 │ 523264 │ 13665 │
│ 731962 │ 475698 │ 102716 │
│ 722545 │ 337212 │ 108187 │
│ 722889 │ 252197 │ 10547 │
│ 2237260 │ 196036 │ 9522 │
│ 23057320 │ 147211 │ 7689 │
│ 722818 │ 90109 │ 17847 │
│ 48221 │ 85379 │ 4652 │
│ 19762435 │ 77807 │ 7026 │
│ 722884 │ 77492 │ 11056 │
└───────────┴────────┴────────┘
```