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

MaxCompute:共通テーブル式 (CTE)

最終更新日:Aug 25, 2026

共通テーブル式 (CTE) は、SQL を簡略化するための一時的な名前付き結果セットです。MaxCompute は、標準 SQL の CTE をサポートし、SQL 文の可読性と実行効率を向上させます。本トピックでは、CTE の機能、構文、および使用例について説明します。

概要

  • CTE は、単一の DML 文の実行スコープ内で定義される一時的な結果セットと見なせます。派生テーブルと同様に、CTE はオブジェクトとして保存されず、クエリの実行中にのみ存在します。開発時に CTE を使用することで、SQL の可読性が向上し、複雑なクエリのメンテナンスが簡素化されます。

  • CTE は、WITH で始まる文レベルの句で、その後に式名が続きます。MaxCompute は、次の 2 種類の CTE をサポートしています。

    • 非再帰 CTE:自身を参照せず、反復しない CTE です。同じサブクエリロジックを再利用するクエリを簡略化するために使用します。

    • 再帰 CTE:自身を反復的に参照できる CTE で、SQL で再帰的なクエリ機能を実現します。通常、組織図などの階層データを走査するために使用されます。

  • マテリアライズされた CTE:CTE を定義する際に、SELECT 文で MATERIALIZE ヒントを使用すると、CTE の結果が一時テーブルにキャッシュされます。その後の参照ではキャッシュから読み込むため、深くネストされた CTE のシナリオではメモリ制限の問題を回避し、パフォーマンスを向上できます。

再帰 CTE

構文

WITH
  <cte_name> [(col_name [, col_name] ...)] AS (
    <cte_query>
  )
  [, <cte_name> [(col_name [, col_name] ...)] AS (
    <cte_query2>
  )
  , ...]

パラメーター

パラメーター

必須

説明

cte_name

はい

CTE の名前。WITH 句内で一意である必要があります。ステートメント内の後続の cte_name への参照は、この CTE から読み取られます。

col_name

いいえ

CTE の出力列名。省略した場合、列名は cte_querySELECT リストから継承されます。

cte_query

はい

結果セットが CTE を定義する SELECT 文。

次のクエリでは、UNION ALL を使用して 2 つの JOIN 操作を結合します。両方の JOIN は同じ左側のサブクエリを共有しており、CTE を使用しない場合は、これを複製する必要があります。

INSERT OVERWRITE TABLE srcp PARTITION (p='abc')
SELECT * FROM (
    SELECT a.key, b.value
    FROM (
        SELECT * FROM src WHERE key IS NOT NULL) a
    JOIN (
        SELECT * FROM src2 WHERE value > 0) b
    ON a.key = b.key
) c
UNION ALL
SELECT * FROM (
    SELECT a.key, b.value
    FROM (
        SELECT * FROM src WHERE key IS NOT NULL) a
    LEFT OUTER JOIN (
        SELECT * FROM src3 WHERE value > 0) b
    ON a.key = b.key AND b.key IS NOT NULL
) d;

CTE で書き換えることで、重複が排除されます。サブクエリ a は一度定義され、両方の join で再利用されます:

WITH
  a AS (SELECT * FROM src WHERE key IS NOT NULL),
  b AS (SELECT * FROM src2 WHERE value > 0),
  c AS (SELECT * FROM src3 WHERE value > 0),
  d AS (SELECT a.key, b.value FROM a JOIN b ON a.key = b.key),
  e AS (SELECT a.key, c.value FROM a LEFT OUTER JOIN c ON a.key = c.key AND c.key IS NOT NULL)
INSERT OVERWRITE TABLE srcp PARTITION (p='abc')
SELECT * FROM d UNION ALL SELECT * FROM e;

再帰 CTE

構文

WITH RECURSIVE <cte_name> [(col_name [, col_name] ...)] AS (
  <initial_part> UNION ALL <recursive_part>
)
SELECT ... FROM ...;

パラメーター

パラメーター

必須

説明

RECURSIVE

はい

再帰 CTE 句は WITH RECURSIVE で始める必要があります。

cte_name

はい

CTE の名前は、現在の WITH 句内で一意である必要があります。

col_name

いいえ

出力列名。省略した場合、列名は initial_part から推測されます。

initial_part

はい

シードデータセット (イテレーション 0) を生成する SELECT ステートメント。cte_name は参照できません。

recursive_part

はい

cte_name を参照して、前のイテレーションから次のイテレーションを算出する SELECT 文。

UNION ALL

はい

initial_partrecursive_part を結合します。UNION (ALL なし) はサポートされていません。

制限事項

  • 再帰 CTE は、INEXISTS、またはスカラサブクエリ内では使用できません。

  • デフォルトの最大反復回数は 10 です。odps.sql.rcte.max.iterate.num を設定して上限を増やします (最大値: 100)。

  • 中間結果はイテレーション間で保存されません。タスクが失敗した場合、実行は最初から再開されます。長時間実行される再帰計算の場合は、イテレーション数を制限するか、中間結果を一時テーブルに保存してください。

  • 再帰 CTE は、Query Acceleration (MaxQA/MCQA) モードではサポートされていません。

    • MCQA モードでは、interactive_auto_rerun=true が設定されている場合、タスクは通常モードにフォールバックします。それ以外の場合、タスクは失敗します。

    • MaxQA モードでは、自動フォールバックはサポートされていません。タスクは直接失敗するため、手動でバッチ処理クォータグループに再サブミットして再試行する必要があります。

  • 例1:cte_name という名前の再帰 CTE を定義

    -- 方法1:出力列名を明示的に指定
    WITH RECURSIVE cte_name(a, b) AS (
      SELECT 1L, 1L                                   -- initial_part:イテレーション0
      UNION ALL
      SELECT a+1, b+1 FROM cte_name WHERE a+1 <= 5    -- recursive_part:前のイテレーションを参照
    )
    SELECT * FROM cte_name ORDER BY a LIMIT 100;
    
    -- 方法2:initial_part から列名を推測
    WITH RECURSIVE cte_name AS (
      SELECT 1L AS a, 1L AS b
      UNION ALL
      SELECT a+1, b+1 FROM cte_name WHERE a+1 <= 5
    )
    SELECT * FROM cte_name ORDER BY a LIMIT 100;
    説明
    • recursive_part では、無限ループを回避するために終了条件を設定します。この例では、WHERE a + 1 <= 5 が終了条件となります。WHERE 条件が満たされない場合、現在のイテレーションで生成されたデータセットは空になり、イテレーションは停止します。

    • 出力列名が明示的に指定されていない場合、システムは自動推論をサポートします。たとえば、方法 2 では、initial_part の出力列名が再帰 CTE の出力列名として使用されます。

    結果:

    +------------+------------+
    | a          | b          |
    +------------+------------+
    | 1          | 1          |
    | 2          | 2          |
    | 3          | 3          |
    | 4          | 4          |
    | 5          | 5          |
    +------------+------------+
  • 例2:サブクエリ内の再帰 CTE (コンパイルエラー)

    再帰 CTE は、INEXISTS、またはスカラサブクエリ内では許可されていません。次のクエリはコンパイルに失敗します。

    WITH RECURSIVE cte_name(a, b) AS (
      SELECT 1L, 1L
      UNION ALL
      SELECT a+1, b+1 FROM cte_name WHERE a+1 <= 5)
    SELECT x, x IN (SELECT a FROM cte_name) FROM VALUES (1L), (2L) AS t(x);

    エラー:

    FAILED: ODPS-0130071:[5,31] Semantic analysis exception - using Recursive-CTE cte_name in scalar/in/exists sub-query is not allowed, please check your query, the query text location is from [line 5, column 13] to [line 5, column 40]
  • 例3:組織階層の走査

    employees テーブルを作成し、データを挿入します:

    CREATE TABLE employees(name STRING, boss_name STRING);
    INSERT INTO TABLE employees VALUES
      ('zhang_3', null),
      ('li_4',    'zhang_3'),
      ('wang_5',  'zhang_3'),
      ('zhao_6',  'li_4'),
      ('qian_7',  'wang_5');

    従業員の名前、マネージャーの名前、階層レベルの 3 つの出力列を持つ、company_hierarchy という名前の再帰 CTE を定義します:

    WITH RECURSIVE company_hierarchy(name, boss_name, level) AS (
        SELECT name, boss_name, 0L FROM employees WHERE boss_name IS NULL
        UNION ALL
        SELECT e.name, e.boss_name, h.level + 1
          FROM employees e
          JOIN company_hierarchy h ON e.boss_name = h.name
    )
    SELECT * FROM company_hierarchy ORDER BY level, boss_name, name LIMIT 1000;

    実行は次のように進みます。

    • イテレーション 0 (initial_part): boss_name IS NULL の従業員を選択し、 level = 0 を割り当てます。結果: ('zhang_3', NULL, 0)

    • イテレーション 1 (recursive_part) では、employees を作業テーブル (イテレーション 0) と結合します。条件 e.boss_name = h.namezhang_3 が管理する従業員を検索し、結果として li_4wang_5level = 1 の従業員として返されます。

    • イテレーション 2: li_4 または wang_5 が管理する従業員を検索します。結果: zhao_6qian_7 (level = 2)。

    • イテレーション 3: マネージャーが zhao_6 または qian_7 である従業員はいません。作業テーブルは空になり、イテレーションは停止します。

    結果:

    +---------+-----------+------------+
    | name    | boss_name | level      |
    +---------+-----------+------------+
    | zhang_3 | NULL      | 0          |
    | li_4    | zhang_3   | 1          |
    | wang_5  | zhang_3   | 1          |
    | zhao_6  | li_4      | 2          |
    | qian_7  | wang_5    | 2          |
    +---------+-----------+------------+
  • 例4循環データによる無限ループ

    例 3 の employees テーブルに、従業員が自身の上司であるレコードを挿入します。

    INSERT INTO TABLE employees VALUES('qian_7', 'qian_7');

    このレコードは、qian_7 のマネージャーが qian_7 自身であることを宣言しています。前述の再帰 CTE を実行すると無限ループが発生します。システムには最大反復回数の制限があるため、クエリは最終的に失敗します。

    エラー:

    FAILED: ODPS-0010000:System internal error - recursive-cte: company_hierarchy exceed max iterate number 10

マテリアライズされた CTE

非再帰 CTE の場合、MaxCompute は実行計画を生成する際にすべての CTE をインラインで展開します。例:

WITH v1 AS (SELECT SIN(1.0) AS a)
SELECT a FROM v1 UNION ALL SELECT a FROM v1;

結果:

+------------+
| a          |
+------------+
| 0.8414709848078965 |
| 0.8414709848078965 |
+------------+

これは SIN(1.0) を 2 回実行することに相当します:

SELECT a FROM (SELECT SIN(1.0) AS a)
UNION ALL
SELECT a FROM (SELECT SIN(1.0) AS a);

深くネストされた CTE を持つ複雑なシナリオでは、すべての CTE が最も基本的なリーフノードに展開されると、非常に大きな構文木が生成されます。これにより、実行計画の生成中に構文木のノードが多すぎるために失敗したり、メモリ制限の問題が発生したりする可能性があります。例:

WITH
  v1 AS (SELECT 1L AS a, 2L AS b, 3L AS c),
  v2 AS (SELECT * FROM v1 UNION ALL SELECT * FROM v1 UNION ALL SELECT * FROM v1),
  v3 AS (SELECT * FROM v2 UNION ALL SELECT * FROM v2 UNION ALL SELECT * FROM v2),
  v4 AS (SELECT * FROM v3 UNION ALL SELECT * FROM v3 UNION ALL SELECT * FROM v3),
  v5 AS (SELECT * FROM v4 UNION ALL SELECT * FROM v4 UNION ALL SELECT * FROM v4),
  v6 AS (SELECT * FROM v5 UNION ALL SELECT * FROM v5 UNION ALL SELECT * FROM v5),
  v7 AS (SELECT * FROM v6 UNION ALL SELECT * FROM v6 UNION ALL SELECT * FROM v6),
  v8 AS (SELECT * FROM v7 UNION ALL SELECT * FROM v7 UNION ALL SELECT * FROM v7),
  v9 AS (SELECT * FROM v8 UNION ALL SELECT * FROM v8 UNION ALL SELECT * FROM v8)
SELECT * FROM v9;

この問題に対処するため、MaxCompute はマテリアライズされた CTE 機能を提供します。これにより、CTE の計算結果がキャッシュされ、WITH 句外の SQL から完全な展開なしで参照できます。このメカニズムは、ネストされた CTE の展開によるメモリ制限の問題を効果的に回避し、CTE 文のパフォーマンスを向上させます。

使用例

非再帰 CTE のトップレベルの SELECT/*+ MATERIALIZE */ ヒントを追加すると、その結果が一時テーブルにキャッシュされます。これにより、後続の参照ではクエリが再実行されるのではなく、キャッシュから読み取りが行われます。

WITH v1 AS (SELECT /*+ MATERIALIZE */ SIN(1.0) AS a)
SELECT a FROM v1 UNION ALL SELECT a FROM v1;

-- Result:
+------------+
| a          |
+------------+
| 0.8414709848078965 |
| 0.8414709848078965 |
+------------+

ヒントが有効な場合、LogView の [Job Details] タブに複数の Fuxi ジョブが表示され、中間結果が保存されたことを確認できます。

制限事項

  • MATERIALIZE ヒントは、非再帰 CTE の最上位の SELECT 文に適用する必要があります。再帰 CTE には MATERIALIZE ヒントは不要です。

    • 誤った例:次の CTE では、最上位の文は SELECT ではなく UNION であるため、MATERIALIZE ヒントは有効になりません。

      WITH v1 AS (
        SELECT /*+ MATERIALIZE */ SIN(1.0) AS a
        UNION ALL
        SELECT /*+ MATERIALIZE */ SIN(1.0) AS a)
      SELECT a FROM v1 UNION ALL SELECT a FROM v1;
    • 正しい例:上記の誤った例をサブクエリでラップして書き直します。

      WITH 
        v1 AS (SELECT /*+ MATERIALIZE */ * FROM 
                 (SELECT SIN(1.0) AS a
                  UNION ALL 
                  SELECT SIN(1.0) AS a)
              ) 
      SELECT a FROM v1 UNION ALL SELECT a FROM v1;
  • CTE が RAND や非決定性の Java/Python UDF などの非決定性関数を使用する場合、CTE をマテリアライズすると関数の評価が 1 回だけキャッシュされ、後続の参照ではキャッシュされた値が返されるため、呼び出しごとに独立したランダムな値を期待するクエリのセマンティックな動作が変更されます。

  • マテリアライズド CTE は MCQA (クエリ高速化 1.0) - 非推奨ではサポートされていません。タスクが interactive_auto_rerun=true で MCQA モードで実行される場合、通常モードにフォールバックします。それ以外の場合、タスクは失敗します。