インラインテーブルとは?SQLクエリ内で一時的に作るテーブルを解説

データベース・SQL

SQLでデータベースを扱っていると、「一時的にテーブルを作って計算したい」という場面に出会うことがあります。

「サブクエリって複雑そう…」
「一時テーブルを作るのは面倒だな」

そんな時に役立つのがインラインテーブルです。

クエリの中で直接テーブルを定義して、その場で使える——このシンプルで強力な機能を使いこなせば、SQLの表現力が格段に上がります。

今回は、インラインテーブルの基本から、実際の使い方、メリット・デメリット、そして実務での活用例まで、分かりやすく解説していきますね。

スポンサーリンク
  1. インラインテーブルとは?基本を理解しよう
    1. クエリ内で定義される一時的なテーブル
    2. 通常のテーブルとの違い
  2. インラインテーブルの基本的な書き方
    1. FROM句内のサブクエリ
    2. シンプルな例
    3. VALUES句を使った定義
    4. WITH句(共通テーブル式)との関係
  3. インラインテーブルの実用例
    1. 例1:集計結果をさらに加工する
    2. 例2:複雑な計算結果に番号を振る
    3. 例3:複数のテーブルを結合する前に絞り込む
    4. 例4:複数の集計を並べる
  4. WITH句(CTE)との使い分け
    1. WITH句(共通テーブル式)とは
    2. インラインテーブル vs WITH句
    3. 使い分けの目安
    4. WITH句の複数定義
  5. インラインテーブルのメリット
    1. 一時テーブルが不要
    2. クエリが再利用しやすい
    3. デバッグが容易
    4. 複雑な処理を段階的に構築
  6. インラインテーブルのデメリット
    1. 可読性が低下することがある
    2. パフォーマンスの問題
    3. DBMSによる最適化の違い
  7. 実行計画とパフォーマンス
    1. EXPLAINで確認
    2. マテリアライゼーション(実体化)
    3. インデックスのヒント
  8. 各DBMSでの違いと注意点
    1. MySQL
    2. PostgreSQL
    3. SQL Server
    4. Oracle
  9. 実務での活用パターン
    1. レポート作成
    2. データクレンジング
    3. ランキング作成
    4. データのピボット
  10. よくある質問と回答
    1. Q1:インラインテーブルに別名を付け忘れたらどうなる?
    2. Q2:インラインテーブルとビューの違いは?
    3. Q3:パフォーマンスが悪い時はどうすれば?
    4. Q4:ネストはどこまで深くしていい?
    5. Q5:インラインテーブルにINDEXは使える?
  11. まとめ:インラインテーブルで柔軟なクエリを書こう

インラインテーブルとは?基本を理解しよう

クエリ内で定義される一時的なテーブル

インラインテーブルとは、SQL文の中で直接定義される一時的なテーブルのことです。

別名として:

  • 派生テーブル(Derived Table)
  • インラインビュー(Inline View)
  • サブクエリテーブル

とも呼ばれます。

特徴:

  • FROM句の中で定義する
  • そのクエリの実行中だけ存在する
  • 実際のテーブルとして保存されない
  • 名前を付けて、通常のテーブルのように扱える

通常のテーブルとの違い

通常のテーブル:

-- テーブルを作成(永続的)
CREATE TABLE users (
    id INT,
    name VARCHAR(100),
    age INT
);

-- データを挿入
INSERT INTO users VALUES (1, '田中', 28);

テーブルがデータベースに保存され、何度でも使えます。

インラインテーブル:

-- クエリの中で一時的にテーブルを作成
SELECT *
FROM (
    SELECT 1 AS id, '田中' AS name, 28 AS age
    UNION ALL
    SELECT 2, '佐藤', 35
    UNION ALL
    SELECT 3, '鈴木', 42
) AS inline_users;

クエリが終われば消える、使い捨てのテーブルです。

インラインテーブルの基本的な書き方

FROM句内のサブクエリ

最も一般的な形式です。

基本構文:

SELECT カラム名
FROM (
    -- ここにサブクエリ
    SELECT ...
    FROM テーブル名
    WHERE 条件
) AS 別名;

重要なポイント:

  • サブクエリを丸括弧 () で囲む
  • 必ず別名(エイリアス)を付ける
  • 別名は AS を使って指定(DBMSによってはASは省略可)

シンプルな例

例1:年齢が30歳以上のユーザーを抽出してから処理

SELECT *
FROM (
    SELECT id, name, age
    FROM users
    WHERE age >= 30
) AS adult_users;

何が起こっているか:

  1. 内側のクエリで年齢30歳以上を抽出
  2. その結果を adult_users という名前のインラインテーブルとして扱う
  3. 外側のクエリでそのテーブルから全データを取得

VALUES句を使った定義

SQLによっては、VALUES句で直接データを定義できます。

例2:固定データをインラインテーブルとして使う

SELECT *
FROM (
    VALUES
        (1, '東京', 1400),
        (2, '大阪', 880),
        (3, '名古屋', 230)
) AS cities(id, name, population);

メリット:

  • テストデータを手軽に作成できる
  • 小さなマスターデータを一時的に使える
  • クエリが自己完結する

WITH句(共通テーブル式)との関係

WITH句(CTE: Common Table Expression)も、一種のインラインテーブルです。

WITH adult_users AS (
    SELECT id, name, age
    FROM users
    WHERE age >= 30
)
SELECT *
FROM adult_users;

違い:

  • FROM句のサブクエリ:その場限り
  • WITH句:同じクエリ内で複数回参照できる、読みやすい

WITH句については後ほど詳しく説明します。

インラインテーブルの実用例

例1:集計結果をさらに加工する

シナリオ:部門ごとの平均給与を計算し、全体平均より高い部門を抽出

-- インラインテーブルを使わない場合(一時テーブルが必要)
CREATE TEMPORARY TABLE dept_avg AS
SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department;

SELECT *
FROM dept_avg
WHERE avg_salary > (SELECT AVG(avg_salary) FROM dept_avg);

-- インラインテーブルを使う場合(一発で完結)
SELECT *
FROM (
    SELECT department, AVG(salary) AS avg_salary
    FROM employees
    GROUP BY department
) AS dept_avg
WHERE avg_salary > (
    SELECT AVG(avg_salary)
    FROM (
        SELECT department, AVG(salary) AS avg_salary
        FROM employees
        GROUP BY department
    ) AS dept_avg2
);

一時テーブルを作らずに、1つのクエリで完結します。

例2:複雑な計算結果に番号を振る

シナリオ:商品の売上ランキングを作成

SELECT
    ROW_NUMBER() OVER (ORDER BY total_sales DESC) AS rank,
    product_name,
    total_sales
FROM (
    SELECT
        p.product_name,
        SUM(o.quantity * o.price) AS total_sales
    FROM orders o
    JOIN products p ON o.product_id = p.id
    GROUP BY p.product_name
) AS sales_summary
ORDER BY rank;

処理の流れ:

  1. 内側:商品ごとの売上合計を計算
  2. インラインテーブル sales_summary として結果を保持
  3. 外側:売上順にランク番号を付与

例3:複数のテーブルを結合する前に絞り込む

シナリオ:2023年の注文データだけを使って分析

SELECT
    u.name AS user_name,
    orders_2023.order_count,
    orders_2023.total_amount
FROM users u
JOIN (
    SELECT
        user_id,
        COUNT(*) AS order_count,
        SUM(amount) AS total_amount
    FROM orders
    WHERE order_date >= '2023-01-01'
      AND order_date < '2024-01-01'
    GROUP BY user_id
) AS orders_2023 ON u.id = orders_2023.user_id;

メリット:

  • 不要なデータを事前に除外できる
  • パフォーマンスが向上する可能性がある

例4:複数の集計を並べる

シナリオ:月別・年別の売上を一覧表示

SELECT 'Monthly' AS period, month, sales FROM (
    SELECT DATE_FORMAT(order_date, '%Y-%m') AS month, SUM(amount) AS sales
    FROM orders
    GROUP BY DATE_FORMAT(order_date, '%Y-%m')
) AS monthly_sales

UNION ALL

SELECT 'Yearly' AS period, year, sales FROM (
    SELECT DATE_FORMAT(order_date, '%Y') AS year, SUM(amount) AS sales
    FROM orders
    GROUP BY DATE_FORMAT(order_date, '%Y')
) AS yearly_sales;

それぞれの集計をインラインテーブルで行い、UNIONで結合しています。

WITH句(CTE)との使い分け

WITH句(共通テーブル式)とは

WITH句は、クエリの冒頭で一時的なテーブルを定義する方法です。

基本構文:

WITH テーブル名 AS (
    SELECT ...
)
SELECT *
FROM テーブル名;

インラインテーブル vs WITH句

インラインテーブル(FROM句のサブクエリ):

SELECT *
FROM (
    SELECT department, AVG(salary) AS avg_salary
    FROM employees
    GROUP BY department
) AS dept_avg
WHERE avg_salary > 50000;

メリット:

  • その場で定義できる
  • シンプルなクエリに向いている

デメリット:

  • 複数回参照すると、同じサブクエリを繰り返す必要がある
  • 複雑になると読みにくい

WITH句(CTE):

WITH dept_avg AS (
    SELECT department, AVG(salary) AS avg_salary
    FROM employees
    GROUP BY department
)
SELECT *
FROM dept_avg
WHERE avg_salary > 50000;

メリット:

  • 読みやすい(上から順に処理が分かる)
  • 同じ結果を複数回参照できる
  • 再帰クエリが書ける(後述)

デメリット:

  • やや冗長になることがある

使い分けの目安

インラインテーブルを使う場合:

  • 一度だけ使う簡単な加工
  • ネストが浅い(1〜2階層)
  • すぐに結果を確認したい時

WITH句を使う場合:

  • 同じ結果を複数回使いたい
  • ネストが深い(3階層以上)
  • クエリの可読性を重視
  • チームで共有するクエリ

WITH句の複数定義

WITH句は複数のテーブルを定義できます。

WITH
    -- 部門ごとの平均給与
    dept_avg AS (
        SELECT department, AVG(salary) AS avg_salary
        FROM employees
        GROUP BY department
    ),
    -- 全体の平均給与
    overall_avg AS (
        SELECT AVG(salary) AS avg_salary
        FROM employees
    )
-- メインクエリ
SELECT
    da.department,
    da.avg_salary,
    oa.avg_salary AS company_avg,
    da.avg_salary - oa.avg_salary AS difference
FROM dept_avg da
CROSS JOIN overall_avg oa
ORDER BY difference DESC;

利点:

  • クエリの構造が明確
  • 段階的に処理を書ける
  • デバッグしやすい

インラインテーブルのメリット

一時テーブルが不要

従来の方法:

-- 一時テーブルを作成
CREATE TEMPORARY TABLE temp_result AS
SELECT ...;

-- 一時テーブルを使用
SELECT * FROM temp_result;

-- 一時テーブルを削除
DROP TEMPORARY TABLE temp_result;

インラインテーブル:

-- 1つのクエリで完結
SELECT *
FROM (
    SELECT ...
) AS temp_result;

メリット:

  • 作成・削除の手間がない
  • クエリが自己完結する
  • 一時テーブルの管理が不要

クエリが再利用しやすい

インラインテーブルを使ったクエリは、そのままコピーすれば他の環境でも動きます。

理由:

  • 外部のテーブルに依存しない(元のテーブルは除く)
  • 一時テーブルの作成権限が不要
  • スクリプトとして保存しやすい

デバッグが容易

インラインテーブルの部分だけを実行して、中間結果を確認できます。

-- まず内側だけを実行して確認
SELECT department, AVG(salary) AS avg_salary
FROM employees
GROUP BY department;

-- 問題なければ、外側を追加
SELECT *
FROM (
    SELECT department, AVG(salary) AS avg_salary
    FROM employees
    GROUP BY department
) AS dept_avg
WHERE avg_salary > 50000;

複雑な処理を段階的に構築

段階的なアプローチ:

  1. 基本的な集計を書く
  2. インラインテーブルで包む
  3. さらに外側で加工を追加

このように、少しずつ機能を追加できます。

インラインテーブルのデメリット

可読性が低下することがある

ネストが深くなると、読みにくくなります。

悪い例:

SELECT *
FROM (
    SELECT *
    FROM (
        SELECT *
        FROM (
            SELECT * FROM users
        ) AS t1
    ) AS t2
) AS t3;

こうなると、何をしているのか分かりません。

対策:

  • ネストは2〜3階層まで
  • 深くなる場合はWITH句を使う

パフォーマンスの問題

同じサブクエリを複数回書くと:

SELECT
    (SELECT AVG(age) FROM users) AS avg_age,
    name,
    age - (SELECT AVG(age) FROM users) AS diff
FROM users;

平均年齢を2回計算しています(無駄)。

改善:

WITH avg_table AS (
    SELECT AVG(age) AS avg_age FROM users
)
SELECT
    a.avg_age,
    u.name,
    u.age - a.avg_age AS diff
FROM users u
CROSS JOIN avg_table a;

WITH句で1回だけ計算し、結果を再利用します。

DBMSによる最適化の違い

データベースシステムによって、インラインテーブルの最適化方法が異なります。

注意点:

  • インラインテーブルが大きいと、メモリを消費する
  • インデックスが使えない場合がある
  • 統計情報がないため、オプティマイザが最適なプランを選べないことも

対策:

  • 実行計画を確認する(EXPLAIN)
  • 必要に応じて一時テーブルやビューを使う

実行計画とパフォーマンス

EXPLAINで確認

インラインテーブルのパフォーマンスを確認するには、実行計画を見ましょう。

MySQL / PostgreSQL:

EXPLAIN
SELECT *
FROM (
    SELECT department, AVG(salary) AS avg_salary
    FROM employees
    GROUP BY department
) AS dept_avg
WHERE avg_salary > 50000;

確認ポイント:

  • どのテーブルをスキャンしているか
  • インデックスが使われているか
  • 推定される処理行数
  • 結合方法

マテリアライゼーション(実体化)

一部のDBMSは、インラインテーブルを一時的に実体化(メモリやディスクに保存)します。

利点:

  • 複数回参照しても、1回だけ計算
  • 複雑な処理が速くなることがある

欠点:

  • 実体化のオーバーヘッド
  • メモリやディスクを消費

制御方法(PostgreSQL):

-- 実体化しない(OFFSET 0 トリック)
SELECT *
FROM (
    SELECT * FROM large_table
    OFFSET 0  -- 実体化を防ぐ
) AS sub;

インデックスのヒント

インラインテーブルには、インデックスがありません。

対策:

  • 元のテーブルの段階で絞り込む
  • WHERE条件を内側のクエリに入れる
  • 結果が大きくなりすぎないようにする
-- 良い例:早めに絞り込む
SELECT *
FROM (
    SELECT *
    FROM large_table
    WHERE created_at >= '2024-01-01'  -- インデックスが使える
) AS filtered
WHERE category = 'Electronics';

-- 悪い例:全件取得してから絞り込む
SELECT *
FROM (
    SELECT *
    FROM large_table  -- 全件取得(遅い)
) AS filtered
WHERE created_at >= '2024-01-01'
  AND category = 'Electronics';

各DBMSでの違いと注意点

MySQL

特徴:

  • インラインテーブルに必ず別名が必要
  • 派生テーブル(Derived Table)と呼ばれる

エイリアスの必須性:

-- エラー:別名がない
SELECT * FROM (SELECT * FROM users);

-- 正しい
SELECT * FROM (SELECT * FROM users) AS u;

最適化:

  • MySQL 5.7以降は、派生テーブルの最適化が改善
  • マテリアライゼーションと結合プッシュダウンを自動判断

PostgreSQL

特徴:

  • インラインビュー(Inline View)と呼ばれることが多い
  • 強力な最適化機能

エイリアス:

-- PostgreSQLでは別名は推奨だが、一部のケースで省略可能
-- ただし、明示的に付けるべき
SELECT * FROM (SELECT * FROM users) AS u;

CTE(WITH句)の最適化:

  • PostgreSQL 12以降、CTEのインライン展開が改善
  • MATERIALIZED / NOT MATERIALIZED キーワードで制御可能
WITH dept_avg AS MATERIALIZED (
    SELECT department, AVG(salary) AS avg_salary
    FROM employees
    GROUP BY department
)
SELECT * FROM dept_avg;

SQL Server

特徴:

  • 派生テーブル(Derived Table)をサポート
  • 強力なクエリオプティマイザ

VALUES句:

SELECT *
FROM (VALUES
    (1, '東京'),
    (2, '大阪')
) AS Cities(ID, Name);

テーブル変数との違い:

  • DECLARE @table TABLE は永続的なセッション変数
  • インラインテーブルはクエリ内だけ

Oracle

特徴:

  • インラインビューと呼ばれる
  • 歴史が長く、最適化が洗練されている

エイリアス:

-- Oracleでは AS は不要(省略可能)
SELECT * FROM (SELECT * FROM users) u;

ROWNUM との組み合わせ:

-- 上位10件を取得
SELECT *
FROM (
    SELECT * FROM users ORDER BY created_at DESC
)
WHERE ROWNUM <= 10;

実務での活用パターン

レポート作成

月次レポート:

SELECT
    report_month,
    total_sales,
    total_orders,
    avg_order_value
FROM (
    SELECT
        DATE_FORMAT(order_date, '%Y-%m') AS report_month,
        SUM(amount) AS total_sales,
        COUNT(*) AS total_orders,
        AVG(amount) AS avg_order_value
    FROM orders
    WHERE order_date >= DATE_SUB(CURDATE(), INTERVAL 12 MONTH)
    GROUP BY DATE_FORMAT(order_date, '%Y-%m')
) AS monthly_report
ORDER BY report_month DESC;

データクレンジング

重複データの除外:

SELECT DISTINCT *
FROM (
    SELECT
        email,
        MAX(created_at) AS latest_date
    FROM users
    GROUP BY email
) AS unique_emails
JOIN users u ON unique_emails.email = u.email
             AND unique_emails.latest_date = u.created_at;

ランキング作成

売上トップ10の商品:

SELECT *
FROM (
    SELECT
        product_name,
        total_sales,
        RANK() OVER (ORDER BY total_sales DESC) AS sales_rank
    FROM (
        SELECT
            p.name AS product_name,
            SUM(o.quantity * o.price) AS total_sales
        FROM orders o
        JOIN products p ON o.product_id = p.id
        GROUP BY p.name
    ) AS sales_data
) AS ranked_products
WHERE sales_rank <= 10;

データのピボット

年度別・部門別の集計:

SELECT
    department,
    SUM(CASE WHEN year = 2022 THEN sales ELSE 0 END) AS sales_2022,
    SUM(CASE WHEN year = 2023 THEN sales ELSE 0 END) AS sales_2023,
    SUM(CASE WHEN year = 2024 THEN sales ELSE 0 END) AS sales_2024
FROM (
    SELECT
        department,
        YEAR(order_date) AS year,
        SUM(amount) AS sales
    FROM orders
    GROUP BY department, YEAR(order_date)
) AS yearly_sales
GROUP BY department;

よくある質問と回答

Q1:インラインテーブルに別名を付け忘れたらどうなる?

A:多くのDBMSでエラーになります。

エラーメッセージの例(MySQL):

Every derived table must have its own alias

対策:
必ず AS エイリアス名 を付ける習慣をつけましょう。

Q2:インラインテーブルとビューの違いは?

A:永続性と再利用性が異なります。

インラインテーブル:

  • クエリ実行時のみ存在
  • そのクエリでしか使えない
  • データベースに保存されない

ビュー:

  • データベースに定義として保存される
  • 複数のクエリから参照できる
  • 権限管理ができる

使い分け:

  • 一度きり → インラインテーブル
  • 頻繁に使う → ビュー

Q3:パフォーマンスが悪い時はどうすれば?

A:以下の方法を試してください。

対策:

  1. 実行計画を確認(EXPLAIN)
  2. 内側のクエリで絞り込みを強化
  3. WITH句に変更(複数回参照する場合)
  4. 一時テーブルやビューを検討(大量データの場合)
  5. インデックスを追加(元のテーブルに)

Q4:ネストはどこまで深くしていい?

A:2〜3階層が限度です。

推奨:

  • 1階層:シンプル、読みやすい
  • 2階層:まだOK
  • 3階層:限界、WITH句を検討
  • 4階層以上:避けるべき

改善方法:
WITH句を使って、段階的に定義しましょう。

Q5:インラインテーブルにINDEXは使える?

A:いいえ、使えません。

理由:
インラインテーブルは一時的な結果セットで、物理的なテーブルではないため。

対策:

  • 元のテーブルの段階でインデックスを活用
  • 早めにデータを絞り込む
  • 結果が大きい場合は一時テーブルを検討

まとめ:インラインテーブルで柔軟なクエリを書こう

インラインテーブルは、SQLクエリの表現力を大きく広げる強力な機能です。

この記事のポイント:

  • インラインテーブルはFROM句内で定義される一時的なテーブル
  • 派生テーブル、インラインビューとも呼ばれる
  • 必ず別名(エイリアス)を付ける必要がある
  • 一時テーブルを作らずに、1つのクエリで複雑な処理が可能
  • WITH句(CTE)はインラインテーブルの発展形で、読みやすい
  • 集計結果の加工、ランキング作成、データクレンジングなどで活躍
  • ネストは2〜3階層まで、それ以上はWITH句を使う
  • パフォーマンスを考慮し、EXPLAINで実行計画を確認
  • MySQL、PostgreSQL、SQL Server、Oracleで微妙に動作が異なる
  • 一度きりの処理ならインラインテーブル、頻繁に使うならビュー

実践のコツ:

  • まず内側のクエリを単独で実行して確認
  • 動作を確認してから外側を追加
  • 複雑になったらWITH句に書き換え
  • 実行計画を見て、パフォーマンスを確認

インラインテーブルを使いこなせば、複雑なデータ分析も1つのクエリで表現できるようになります。

最初は簡単な例から始めて、少しずつ複雑な処理に挑戦してみてください。

SQLの世界が、ぐっと広がりますよ!

コメント

タイトルとURLをコピーしました