SELECT 文に MAPJOIN ヒントを追加すると、結合が強制的にマップステージで実行され、シャッフルステージとリデュースステージがバイパスされます。これにより、大きいテーブルを 1 つ以上の小さいテーブルと結合する際のデータ転送のオーバーヘッドが削減され、クエリパフォーマンスが向上します。オプティマイザーがクエリにマップステージ結合を自動的に適用しない場合は、MAPJOIN を使用してください。
仕組み
MaxCompute での標準的な JOIN は、マップ、シャッフリング、リデュースの 3 つのステージを経て実行されます。実際の JOIN ロジックはリデュースステージで実行され、ノード間でデータをシャッフリングする必要があります。
MAPJOIN はこの動作を変更します。マップステージ中に、指定された小さいテーブルの全コンテンツをメモリにロードします。その後、各マッパーはメモリ内のデータに対してローカルで結合を実行し、シャッフルステージとリデュースステージを完全に排除します。
ヒントでは、メモリにロードする小さいテーブルを指定します。大きいテーブルから行を読み取るすべてのマッパーに対して、小さいテーブルはメモリから完全に読み取られます。
SELECT /*+ mapjoin(a) */ a.shop_name, a.total_price, b.total_price
FROM sale_detail_sj a JOIN sale_detail b
ON a.total_price < b.total_price OR a.total_price + b.total_price < 500;
この例では、a (sale_detail_sj のエイリアス) が小さいテーブルです。ヒントで a が指定されているため、各マッパーのメモリにロードされるのは a です。
制限事項
メモリとテーブル数
| 制約 | 制限 | 注意 |
|---|---|---|
| すべての小さいテーブルの合計メモリ | 512 MB | MaxCompute に保存されている圧縮サイズではなく、データがメモリにロードされて解凍された後のサイズで測定されます。 |
| 小さいテーブルの最大数 | 128 | 128 を超える数を指定すると、構文エラーが返されます。 |
サポートされる JOIN タイプ
| JOIN タイプ | サポート | 要件 |
|---|---|---|
| INNER JOIN | はい | 左テーブルまたは右テーブルのどちらかを大きいテーブルにできます。 |
| LEFT OUTER JOIN | はい | 左テーブルを大きいテーブルにする必要があります。 |
| RIGHT OUTER JOIN | はい | 右テーブルを大きいテーブルにする必要があります。 |
| FULL OUTER JOIN | いいえ | — |
注意事項
SELECT の直後に /*+ mapjoin(<table_name>) */ を追加します。以下の点にご注意ください。
-
元のテーブル名ではなく、エイリアスを参照します。小さいテーブルまたはサブクエリにエイリアスがある場合は、ヒント内でそのエイリアスを使用します。
-
サブクエリは小さいテーブルとしてサポートされています。テーブル参照の代わりにサブクエリを使用し、ヒントでそのエイリアスを参照します。
-
複数の小さいテーブルはカンマで区切ります:
/*+ mapjoin(a,b,c) */ -
非等値結合と OR 条件がサポートされています。標準の MaxCompute SQL では ON 条件での非等値結合や OR ロジックは許可されていませんが、MAPJOIN では許可されています。
-
デカルト積は
ON 1 = 1を使用してサポートされています (例:SELECT /*+ mapjoin(a) */ a.id FROM shop a JOIN table_name b ON 1=1) が、これにより出力データ量が大幅に増加する可能性があります。
SCALAR、IN、NOT IN、EXISTS、NOT EXISTS などのサブクエリタイプは、実行時に JOIN 操作に変換される場合があります。サブクエリの結果が小さいテーブルとして適格である場合は、サブクエリ文に MAPJOIN ヒントを追加して、マップステージ結合アルゴリズムを明示的に適用します。
サンプルデータ
このトピックの例では、sale_detail テーブルと sale_detail_sj テーブルを使用します。次の文を実行してテーブルを作成し、サンプルデータを挿入します。
-- sale_detail という名前のパーティションテーブルを作成します。
CREATE TABLE IF NOT EXISTS sale_detail
(
shop_name STRING,
customer_id STRING,
total_price DOUBLE
)
PARTITIONED BY (sale_date STRING, region STRING);
CREATE TABLE IF NOT EXISTS sale_detail_sj
(
shop_name STRING,
customer_id STRING,
total_price DOUBLE
)
PARTITIONED BY (sale_date STRING, region STRING);
-- パーティションを追加します。
ALTER TABLE sale_detail ADD PARTITION (sale_date='2013', region='china');
ALTER TABLE sale_detail_sj ADD PARTITION (sale_date='2013', region='china');
-- サンプルデータを挿入します。
INSERT INTO sale_detail PARTITION (sale_date='2013', region='china')
VALUES ('s1','c1',100.1),('s2','c2',100.2),('s3','c3',100.3);
INSERT INTO sale_detail_sj PARTITION (sale_date='2013', region='china')
VALUES ('s1','c1',100.1),('s2','c2',100.2),('s5','c2',100.2),('s2','c2',100.2);
例
sale_detail_sj (小さいテーブル、エイリアスは a) と sale_detail (大きいテーブル、エイリアスは b) を非等値条件で結合します。a の total_price が b の total_price より小さいか、または両方の価格の合計が 500 未満である行を返します。
-- パーティションテーブルのフルスキャンを許可します。
SET odps.sql.allow.fullscan=true;
-- 非等値結合条件で MAPJOIN を使用します。
SELECT /*+ mapjoin(a) */
a.shop_name,
a.total_price,
b.total_price
FROM sale_detail_sj a JOIN sale_detail b
ON a.total_price < b.total_price OR a.total_price + b.total_price < 500;
クエリは次の結果を返します。
+-----------+-------------+--------------+
| shop_name | total_price | total_price2 |
+-----------+-------------+--------------+
| s1 | 100.1 | 100.1 |
| s2 | 100.2 | 100.1 |
| s5 | 100.2 | 100.1 |
| s2 | 100.2 | 100.1 |
| s1 | 100.1 | 100.2 |
| s2 | 100.2 | 100.2 |
| s5 | 100.2 | 100.2 |
| s2 | 100.2 | 100.2 |
| s1 | 100.1 | 100.3 |
| s2 | 100.2 | 100.3 |
| s5 | 100.2 | 100.3 |
| s2 | 100.2 | 100.3 |
+-----------+-------------+--------------+