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

MaxCompute:Common Table Expression (CTE)

最終更新日:Jun 13, 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_query 内の SELECT リストからカラム名を継承します。

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 は 1 回定義され、両方の 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 を接続します。ALL を含まない UNION はサポートされていません。

制限事項

  • 再帰 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.name の条件により、zhang_3 が上司の従業員が検出されます。結果:li_4 および wang_5level = 1 で取得されます。

    • 反復 2li_4 または wang_5 が上司の従業員を検出します。結果:zhao_6 および qian_7level = 2 で取得されます。

    • 反復 3zhao_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;

-- 結果:
+------------+
| a          |
+------------+
| 0.8414709848078965 |
| 0.8414709848078965 |
+------------+

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

制限事項

  • 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 は Query Acceleration (MCQA) ではサポートされていません。タスクが interactive_auto_rerun=true を設定した MCQA モードで実行される場合、通常モードにフォールバックします。それ以外の場合、タスクは失敗します。