すべてのプロダクト
Search
ドキュメントセンター

MaxCompute:MERGE INTO

最終更新日:Dec 24, 2025

トランザクションテーブルまたは Delta テーブルに対して INSERTUPDATEDELETE 操作を実行する必要がある場合、MERGE INTO 機能を使用してこれらの操作を単一の SQL 文にまとめることができます。この文は、ソーステーブルとの結合結果に基づいて、ターゲットのトランザクションテーブルに対して挿入、更新、または削除を実行します。このプロセスでは全表スキャンが 1 回で済むため、実行効率が向上します。

概要

MaxCompute は DELETE および UPDATE 操作をサポートしています。しかし、対象のテーブルに対してバッチ INSERTUPDATE、および DELETE 操作を実行するには、個別の SQL 文を記述する必要があります。この場合、複数回の全表スキャンが必要になります。MaxCompute の MERGE INTO 機能は、1 回の全表スキャンでこれらすべての操作を実行するため、個別の INSERTUPDATE、および DELETE 文を実行するよりも効率的です。

MERGE INTO 操作はアトミックです。ジョブは、そのすべての INSERTUPDATEDELETE 操作が成功した場合にのみ成功します。いずれかの内部操作が失敗した場合、ジョブ全体が失敗します。

INSERTUPDATEDELETE 操作を個別に実行する際に発生しうる問題も、MERGE INTO 文によって防ぐことができます。たとえば、これらの操作を個別に実行し、一部は成功し、他は失敗した場合、成功した変更をロールバックすることはできません。MERGE INTO 文はこの問題を回避します。

適用範囲

  • MERGE INTO 操作には、ターゲットのトランザクションテーブルに対する Select (読み取り) および Update 権限が必要です。詳細については、「MaxCompute の権限」をご参照ください。

  • 単一の MERGE INTO 文内で、同じ行に対して複数の INSERT または UPDATE 操作を実行しないでください。

  • MERGE INTO 文のターゲットテーブルは、トランザクションテーブルである必要があります。

  • MERGE INTO 文には、少なくとも 1 つの merge_action を含める必要があります。

  • 複数の not_matched_by_target_clause 句を使用する場合、search_condition を省略できる句は最大で 1 つです。

  • 複数の not_matched_by_source_clause 句を使用する場合、search_condition を省略できる句は最大で 1 つです。

  • 複数の matched_clause 句を使用する場合、search_condition を省略できる句は最大で 1 つです。

  • merge_condition および search_condition ではサブクエリはサポートされていません。

コマンドフォーマット

MERGE INTO <target_table> AS <alias_name_t>
USING <source expression | table_name> AS <alias_name_s>
-- ON 句は、ソーステーブルとターゲットテーブルの結合条件を指定します。
ON merge_condition
{ merge_action } +

merge_action ::= matched_clause | not_matched_by_target_clause | not_matched_by_source_clause

-- WHEN MATCHED...THEN 句は、ON 句の結果が true の場合の操作を指定します。複数の WHEN MATCHED...THEN 句で処理されるデータは重複できません。
matched_clause ::= WHEN MATCHED [ AND <search_condition> ] THEN { merge_update_clause | merge_delete_clause }

-- WHEN NOT MATCHED...THEN 句は、ON 句の結果が false の場合の操作を指定します。
not_matched_by_target_clause ::= WHEN NOT MATCHED [BY TARGET] [ AND <search_condition> ] THEN merge_insert_clause

not_matched_by_source_clause ::= WHEN NOT MATCHED BY SOURCE [ AND <search_condition> ] THEN  { merge_update_clause | merge_delete_clause }

merge_condition ::= BOOLEAN expression

search_condition ::= BOOLEAN expression

merge_update_clause ::= UPDATE SET <set_clause_list>

merge_delete_clause ::= DELETE

merge_insert_clause ::= INSERT <value_list> | ROW | *

パラメーター

パラメーター

必須

説明

target_table

はい

ターゲットテーブルの名前。テーブルは存在している必要があります。

alias_name_t

いいえ

ターゲットテーブルのエイリアス。

source expression|table_name

はい

結合するソーステーブル、ビュー、またはサブクエリの名前。

alias_name_s

いいえ

ソーステーブル、ビュー、またはサブクエリのエイリアス。

merge_condition

はい

BOOLEAN 値を返す条件式。結果は True または False である必要があります。

search_condition

いいえ

UPDATEDELETE、または INSERT 操作に対応する BOOLEAN 条件式。

  • 文に 3 つの WHEN 句が含まれる場合、UPDATEDELETEINSERT 操作はそれぞれ 1 回しか出現できません。

  • UPDATEDELETE の両方の操作が存在する場合、最初に出現する操作には [AND <BOOLEAN expression>] を含める必要があります。

  • WHEN NOT MATCHED 句は、最後の WHEN 句でなければなりません。

set_clause_list

いいえ

更新するデータ。このパラメーターは UPDATE 操作に必須です。

UPDATE の詳細については、「データの更新 (UPDATE)」をご参照ください。

value_list

いいえ

挿入するデータ。これは INSERT 操作に必須です。

VALUES の詳細については、「VALUES」をご参照ください。

merge_action

はい

`merge_action` には、MATCHED、NOT MATCHED BY TARGET、NOT MATCHED BY SOURCE の 3 つのオプションがあります。

各 `merge_action` には、オプションの検索条件 (`search_condition`) を含めることができます。`merge_action` は、マージ条件 (`merge_condition`) と検索条件 (`search_condition`) の両方が満たされた場合にのみ行に対して実行されます。複数の句が一致した場合、その行に対しては最初に一致した `merge_action` のみ実行されます。

matched_clause

いいえ

`matched_clause` には UPDATE と DELETE の 2 つのオプションがあり、ソーステーブルの行と一致した場合にターゲットテーブルの行を更新または削除する方法を定義します。

not_matched_by_target_clause

いいえ

一致する行が存在しない場合に、ソーステーブルからターゲットテーブルに行を挿入する方法を定義します。

not_matched_by_source_clause

いいえ

ソーステーブルに一致する行がない場合に、ターゲットテーブルの行を更新または削除する方法を定義します。

merge_insert_clause

いいえ

`merge_insert_clause` には、value_list、ROW、* の 3 つのオプションがあります。`value_list` オプションは values 式を使用して挿入するデータを指定します。ROW と * オプションは、一致したデータをソーステーブルからターゲットテーブルに直接挿入します。これらのオプションでは、ターゲットテーブルとソーステーブルの列数が同じであり、対応するデータの型に互換性がある必要があります。

  • 例 1:ターゲットテーブル `acid_address_book_base1` とソーステーブル `tmp_table1` を作成し、データを挿入します。MERGE INTO 操作を実行します。データが ON 条件を満たす場合、ターゲットテーブルをソーステーブルのデータで更新します。データが ON 条件を満たさず、ソーステーブルの _event_type_I である場合、データをターゲットテーブルに挿入します。以下のサンプルコマンドが使用されます。

    --ターゲットテーブル acid_address_book_base1 を作成します。
    CREATE TABLE IF NOT EXISTS acid_address_book_base1 
    (id BIGINT,first_name STRING,last_name STRING,phone STRING) 
    PARTITIONED BY(year STRING, month STRING, day STRING, hour STRING) 
    tblproperties ("transactional"="true"); 
    
    --ソーステーブル tmp_table1 を作成します。
    CREATE TABLE IF NOT EXISTS tmp_table1 
    (id BIGINT, first_name STRING, last_name STRING, phone STRING, _event_type_ STRING);
    
    --テストデータをターゲットテーブル acid_address_book_base1 に挿入します。
    INSERT OVERWRITE TABLE acid_address_book_base1 
    PARTITION(year='2020', month='08', day='20', hour='16') 
    VALUES (4, 'nihaho', 'li', '222'), (5, 'tahao', 'ha', '333'), 
    (7, 'djh', 'hahh', '555');
    
    --ターゲットテーブルをクエリして、データ挿入の結果を確認します。
    SET odps.sql.allow.fullscan=true;
    SELECT * FROM acid_address_book_base1;
    --以下の結果が返されます。
    +------------+------------+------------+------------+------------+------------+------------+------------+
    | id         | first_name | last_name  | phone      | year       | month      | day        | hour       |
    +------------+------------+------------+------------+------------+------------+------------+------------+
    | 4          | nihaho     | li         | 222        | 2020       | 08         | 20         | 16         |
    | 5          | tahao      | ha         | 333        | 2020       | 08         | 20         | 16         |
    | 7          | djh        | hahh       | 555        | 2020       | 08         | 20         | 16         |
    +------------+------------+------------+------------+------------+------------+------------+------------+
    
    --テストデータをソーステーブル tmp_table1 に挿入します。
    INSERT OVERWRITE TABLE tmp_table1 VALUES 
    (1, 'hh', 'liu', '999', 'I'), (2, 'cc', 'zhang', '888', 'I'),
    (3, 'cy', 'zhang', '666', 'I'),(4, 'hh', 'liu', '999', 'U'),
    (5, 'cc', 'zhang', '888', 'U'),(6, 'cy', 'zhang', '666', 'U');
    
    --ソーステーブルをクエリして、データ挿入の結果を確認します。
    SET odps.sql.allow.fullscan=true;
    SELECT * FROM tmp_table1;
    --以下の結果が返されます。
    +------------+------------+------------+------------+--------------+
    | id         | first_name | last_name  | phone      | _event_type_ |
    +------------+------------+------------+------------+--------------+
    | 1          | hh         | liu        | 999        | I            |
    | 2          | cc         | zhang      | 888        | I            |
    | 3          | cy         | zhang      | 666        | I            |
    | 4          | hh         | liu        | 999        | U            |
    | 5          | cc         | zhang      | 888        | U            |
    | 6          | cy         | zhang      | 666        | U            |
    +------------+------------+------------+------------+--------------+
    
    --merge into 操作を実行します。
    MERGE INTO acid_address_book_base1 AS t USING tmp_table1 as s 
    ON s.id = t.id AND t.year='2020' AND t.month='08' AND t.day='20' AND t.hour='16' 
    WHEN MATCHED THEN UPDATE SET t.first_name = s.first_name, t.last_name = s.last_name, t.phone = s.phone 
    WHEN NOT MATCHED AND (s._event_type_='I') THEN INSERT VALUES(s.id, s.first_name, s.last_name,s.phone,'2020','08','20','16');
    
    --ターゲットテーブルをクエリして、merge into 操作の結果を確認します。
    SET odps.sql.allow.fullscan=true;
    SELECT * FROM acid_address_book_base1;
    --以下の結果が返されます。
    +------------+------------+------------+------------+------------+------------+------------+------------+
    | id         | first_name | last_name  | phone      | year       | month      | day        | hour       |
    +------------+------------+------------+------------+------------+------------+------------+------------+
    | 4          | hh         | liu        | 999        | 2020       | 08         | 20         | 16         |
    | 5          | cc         | zhang      | 888        | 2020       | 08         | 20         | 16         |
    | 7          | djh        | hahh       | 555        | 2020       | 08         | 20         | 16         |
    | 1          | hh         | liu        | 999        | 2020       | 08         | 20         | 16         |
    | 2          | cc         | zhang      | 888        | 2020       | 08         | 20         | 16         |
    | 3          | cy         | zhang      | 666        | 2020       | 08         | 20         | 16         |
    +------------+------------+------------+------------+------------+------------+------------+------------+
  • 例 2:ターゲットテーブル `acid_target1` とソーステーブル `tmp_table2` を作成し、データを挿入します。MERGE INTO 操作を実行して、次の操作を行います:ソーステーブルの行がターゲットテーブルのどの行とも一致せず、その `name` が 'Nika' である場合、その行をターゲットテーブルに挿入します。ターゲットテーブルの行がソーステーブルのどの行とも一致せず、その `name` が 'Tommy' である場合、その行を削除します。ターゲットテーブルの行がソーステーブルのどの行とも一致せず、その `name` が 'Gene' である場合、その行を更新します。

    --ターゲットテーブル acid_target1 を作成します。
    CREATE TABLE IF NOT EXISTS acid_target1 
    (id BIGINT, name STRING, phone STRING)  
    tblproperties ("transactional"="true"); 
    
    --ソーステーブル tmp_table2 を作成します。
    CREATE TABLE IF NOT EXISTS tmp_table2 
    (id BIGINT, name STRING, phone STRING);
    
    --テストデータをターゲットテーブル acid_target1 に挿入します。
    INSERT OVERWRITE TABLE acid_target1 
    VALUES (1, 'Tommy', '111'), (2, 'Gene', '222'), 
    (3, 'Abram', '333');
    
    --ターゲットテーブルをクエリして、データ挿入の結果を確認します。
    SELECT * FROM acid_target1;
    --以下の結果が返されます。
    +------------+------+-------+
    | id         | name | phone |
    +------------+------+-------+
    | 1          | Tommy | 111   |
    | 2          | Gene | 222   |
    | 3          | Abram | 333   |
    +------------+------+-------+
    
    --テストデータをソーステーブル tmp_table2 に挿入します。
    INSERT OVERWRITE TABLE tmp_table2 
    VALUES (6, 'Queen', '666'), (7, 'Nika', '777');
    
    --ソーステーブルをクエリして、データ挿入の結果を確認します。
    SELECT * FROM tmp_table2;
    --以下の結果が返されます。
    +------------+------+-------+
    | id         | name | phone |
    +------------+------+-------+
    | 6          | Queen | 666   |
    | 7          | Nika | 777   |
    +------------+------+-------+
    
    --merge into 操作を実行します。
    --name が 'Nika' の行をソーステーブルからターゲットテーブルに挿入します。
    --name が 'Tommy' の行をターゲットテーブルから削除します。
    --ターゲットテーブルの name が 'Gene' の行を更新します。phone の値を '222' から '999' に設定します。
    MERGE INTO acid_target1 t
    USING tmp_table2 s 
    ON t.id = s.id
    WHEN NOT MATCHED BY TARGET AND s.name = 'Nika'  THEN
      INSERT (id, name, phone) VALUES(s.id, s.name, s.phone)
    WHEN NOT MATCHED BY SOURCE AND t.name = 'Tommy'  THEN
      DELETE
    WHEN NOT MATCHED BY SOURCE AND t.name = 'Gene'  THEN
      UPDATE SET t.phone = '999';
    
    --ターゲットテーブルをクエリして、merge into 操作の結果を確認します。
    SELECT * FROM acid_target1;
    --以下の結果が返されます。
    +------------+------+-------+
    | id         | name | phone |
    +------------+------+-------+
    | 2          | Gene | 999   |
    | 7          | Nika | 777   |
    | 3          | Abram | 333  |
    +------------+------+-------+
  • 例 3:ターゲットテーブル `merge_acid_dp` とソーステーブル `merge_acid_source` を作成し、データを挿入します。パーティションを指定せずに MERGE INTO コマンドを実行してデータを更新または挿入します。操作はターゲットテーブルのすべてのパーティションに適用されます。

    --ターゲットテーブル merge_acid_dp を作成します。
    CREATE TABLE IF NOT EXISTS merge_acid_dp(c1 BIGINT NOT NULL, c2 BIGINT NOT NULL)
    PARTITIONED BY (dd STRING, hh STRING) tblproperties ("transactional" = "true");
    --ソーステーブル merge_acid_source を作成します。
    CREATE TABLE IF NOT EXISTS merge_acid_source(c1 BIGINT NOT NULL, c2 BIGINT NOT NULL,
      c3 STRING, c4 STRING) lifecycle 30;
    
    --テストデータをターゲットテーブル merge_acid_dp に挿入します。
    INSERT OVERWRITE TABLE merge_acid_dp PARTITION (dd='01', hh='01')
    VALUES (1, 1), (2, 2);
    INSERT OVERWRITE TABLE merge_acid_dp PARTITION (dd='02', hh='02')
    VALUES (4, 1), (3, 2);
    
    --ターゲットテーブルをクエリして、データ挿入の結果を確認します。
    SET odps.sql.allow.fullscan=true;
    SELECT * FROM merge_acid_dp;
    --以下の結果が返されます。
    +------------+------------+----+----+
    | c1         | c2         | dd | hh |
    +------------+------------+----+----+
    | 1          | 1          | 01 | 01 |
    | 2          | 2          | 01 | 01 |
    | 4          | 1          | 02 | 02 |
    | 3          | 2          | 02 | 02 |
    +------------+------------+----+----+
    
    --テストデータをソーステーブル merge_acid_source に挿入します。
    INSERT OVERWRITE TABLE merge_acid_source VALUES(8, 2, '03', '03'),
    (5, 5, '05', '05'), (6, 6, '02', '02');
    
    --ソーステーブルをクエリして、データ挿入の結果を確認します。
    SET odps.sql.allow.fullscan=true;
    SELECT * FROM merge_acid_source;
    --以下の結果が返されます。
    +------------+------------+----+----+
    | c1         | c2         | c3 | c4 |
    +------------+------------+----+----+
    | 8          | 2          | 03 | 03 |
    | 5          | 5          | 05 | 05 |
    | 6          | 6          | 02 | 02 |
    +------------+------------+----+----+
    
    --merge into 操作を実行します。
    SET odps.sql.allow.fullscan=true;
    MERGE INTO merge_acid_dp tar USING merge_acid_source src 
    ON tar.c2 = src.c2 
    WHEN MATCHED THEN 
    UPDATE SET tar.c1 = src.c1 
    WHEN NOT MATCHED THEN 
    INSERT VALUES(src.c1, src.c2, src.c3, src.c4);
    
    --ターゲットテーブルをクエリして、merge into 操作の結果を確認します。
    SET odps.sql.allow.fullscan=true;
    SELECT * FROM merge_acid_dp;
    --以下の結果が返されます。
    +------------+------------+----+----+
    | c1         | c2         | dd | hh |
    +------------+------------+----+----+
    | 6          | 6          | 02 | 02 |
    | 5          | 5          | 05 | 05 |
    | 8          | 2          | 02 | 02 |
    | 8          | 2          | 01 | 01 |
    | 1          | 1          | 01 | 01 |
    | 4          | 1          | 02 | 02 |
    +------------+------------+----+----+
  • 例 4:ターゲットテーブル `merge_acid_sp` とソーステーブル `merge_acid_source` を作成し、データを挿入します。MERGE INTO コマンドを実行して、ターゲットテーブルの指定されたパーティションにデータを更新または挿入します。

    --ターゲットテーブル merge_acid_sp を作成します。
    CREATE TABLE IF NOT EXISTS merge_acid_sp(c1 BIGINT NOT NULL, c2 BIGINT NOT NULL)
    PARTITIONED BY (dd STRING, hh STRING) tblproperties ("transactional" = "true");
    --ソーステーブル merge_acid_source を作成します。
    CREATE TABLE IF NOT EXISTS merge_acid_source(c1 BIGINT NOT NULL, c2 BIGINT NOT NULL,
      c3 STRING, c4 STRING) lifecycle 30;
    
    --テストデータをターゲットテーブル merge_acid_sp に挿入します。
    INSERT OVERWRITE TABLE merge_acid_sp PARTITION (dd='01', hh='01')
    VALUES (1, 1), (2, 2);
    INSERT OVERWRITE TABLE merge_acid_sp PARTITION (dd='02', hh='02')
    VALUES (4, 1), (3, 2);
    
    --ターゲットテーブルをクエリして、データ挿入の結果を確認します。
    SET odps.sql.allow.fullscan=true;
    SELECT * FROM merge_acid_sp;
    --以下の結果が返されます。
    +------------+------------+----+----+
    | c1         | c2         | dd | hh |
    +------------+------------+----+----+
    | 1          | 1          | 01 | 01 |
    | 2          | 2          | 01 | 01 |
    | 4          | 1          | 02 | 02 |
    | 3          | 2          | 02 | 02 |
    +------------+------------+----+----+
    
    --テストデータをソーステーブル merge_acid_source に挿入します。
    INSERT OVERWRITE TABLE merge_acid_source VALUES(8, 2, '03', '03'),
    (5, 5, '05', '05'), (6, 6, '02', '02');
    
    --ソーステーブルをクエリして、データ挿入の結果を確認します。
    SET odps.sql.allow.fullscan=true;
    SELECT * FROM merge_acid_source;
    --以下の結果が返されます。
    +------------+------------+----+----+
    | c1         | c2         | c3 | c4 |
    +------------+------------+----+----+
    | 8          | 2          | 03 | 03 |
    | 5          | 5          | 05 | 05 |
    | 6          | 6          | 02 | 02 |
    +------------+------------+----+----+
    
    --merge into 操作を実行します。ON 条件で、更新または挿入操作がターゲットテーブルの dd = '01' および hh = '01' パーティションにのみ適用されるように指定します。
    SET odps.sql.allow.fullscan=true;
    MERGE INTO merge_acid_sp tar USING merge_acid_source src 
    ON tar.c2 = src.c2 AND tar.dd = '01' AND tar.hh = '01' 
    WHEN MATCHED THEN 
    UPDATE SET tar.c1 = src.c1 
    WHEN NOT MATCHED THEN 
    INSERT VALUES(src.c1, src.c2, src.c3, src.c4);
    
    --ターゲットテーブルをクエリして、merge into 操作の結果を確認します。
    SET odps.sql.allow.fullscan=true;
    SELECT * FROM merge_acid_sp;
    +------------+------------+----+----+
    | c1         | c2         | dd | hh |
    +------------+------------+----+----+
    | 5          | 5          | 05 | 05 |
    | 6          | 6          | 02 | 02 |
    | 8          | 2          | 01 | 01 |
    | 1          | 1          | 01 | 01 |
    | 4          | 1          | 02 | 02 |
    | 3          | 2          | 02 | 02 |
    +------------+------------+----+----+
  • 例 5:ターゲットテーブルとして Delta テーブル `mf_tt6` とソーステーブル `mf_delta` を作成し、データを挿入します。指定されたパーティションで MERGE INTO コマンドを実行して、それらのパーティション内のデータを更新、挿入、または削除します。

    --ターゲットテーブルとして Transactional table 2.0 mf_tt6 を作成します。
    CREATE TABLE IF NOT EXISTS mf_tt6 (pk BIGINT NOT NULL PRIMARY key, 
                      val BIGINT NOT NULL) 
                      PARTITIONED BY (dd STRING, hh STRING) 
                      tblproperties ("transactional"="true");
    --テストデータをターゲットテーブル mf_tt6 に挿入します。
    INSERT OVERWRITE TABLE mf_tt6 PARTITION (dd='01', hh='02') VALUES (1, 1), (2, 2), (3, 3);
    INSERT OVERWRITE TABLE mf_tt6 PARTITION (dd='01', hh='01') VALUES (1, 10), (2, 20), (3, 30);
    
    --現在のセッションでのみ全表スキャンを有効にします。select 文を実行して mf_tt6 テーブルのデータを表示します。
    SET odps.sql.allow.fullscan=true;
    SELECT * FROM mf_tt6;
    --以下の結果が返されます。
    +------------+------------+----+----+
    | pk         | val        | dd | hh |
    +------------+------------+----+----+
    | 1          | 10         | 01 | 01 |
    | 3          | 30         | 01 | 01 |
    | 2          | 20         | 01 | 01 |
    | 1          | 1          | 01 | 02 |
    | 3          | 3          | 01 | 02 |
    | 2          | 2          | 01 | 02 |
    +------------+------------+----+----+
    
    --ソーステーブル mf_delta を作成し、テストデータを挿入します。
    CREATE TABLE IF NOT EXISTS mf_delta AS SELECT pk, val FROM VALUES (1, 10), (2, 20), (6, 60) t (pk, val);
    
    --ソーステーブルをクエリして、データ挿入の結果を確認します。
    SELECT * FROM mf_delta;
    --以下の結果が返されます。
    +------+------+
    | pk   | val  |
    +------+------+
    | 1    | 10   |
    | 2    | 20   |
    | 6    | 60   |
    +------+------+
    
    --merge into 操作を実行します。ON 条件で、更新、挿入、または削除操作がターゲットテーブル mf_tt6 の dd = '01' および hh = '02' パーティションにのみ適用されるように指定します。
    
    MERGE INTO mf_tt6 USING mf_delta 
    ON mf_tt6.pk = mf_delta.pk AND mf_tt6.dd='01' AND mf_tt6.hh='02' 
    WHEN MATCHED AND (mf_tt6.pk > 1) THEN 
    UPDATE SET mf_tt6.val = mf_delta.val 
    WHEN MATCHED THEN DELETE 
    WHEN NOT MATCHED THEN 
    INSERT VALUES (mf_delta.pk, mf_delta.val, '01', '02');         
    
    --ターゲットテーブルをクエリして、merge into 操作の結果を確認します。
    SET odps.sql.allow.fullscan=true;
    SELECT * FROM mf_tt6;
    --以下の結果が返されます。
    +------------+------------+----+----+
    | pk         | val        | dd | hh |
    +------------+------------+----+----+
    | 1          | 10         | 01 | 01 |
    | 3          | 30         | 01 | 01 |
    | 2          | 20         | 01 | 01 |
    | 3          | 3          | 01 | 02 |
    | 6          | 60         | 01 | 02 |
    | 2          | 20         | 01 | 02 |
    +------------+------------+----+----+