この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:4
ページ更新者:atom
更新日時:2026-06-11 07:07:02

タイトル: インラインビュー
SEOタイトル: SQL インラインビュー完全ガイド (FROM 句サブクエリ / CTE 比較)

この記事の要点
  • インラインビュー (Inline View) = FROM 句に書くサブクエリ。一時的な仮想テーブル
  • 構文: SELECT * FROM (SELECT ... FROM ... WHERE ...) AS xエイリアス (AS x) 必須
  • 通常の VIEW と違い 永続化されない。クエリ実行ごとに毎回評価
  • TOP N (上位 N 件) や順位付け (ROW_NUMBER) と相性が良い
  • 深くなりがちなので、可読性を上げたいときは CTE (WITH 句) に置き換えると良い

インラインビューとは

SQL の FROM 句にサブクエリを書き、その結果を仮想テーブルのように扱う技法をインラインビュー (Inline View) と呼びます。「インライン (in-line) = 行内」の意味で、永続的な VIEW オブジェクトを作らずクエリ内に埋め込みます。

-- 基本構文
SELECT *
FROM (
    SELECT user_id, COUNT(*) AS order_count
    FROM orders
    WHERE created_at >= '2026-01-01'
    GROUP BY user_id
) AS recent_orders   -- ★ エイリアス必須
WHERE order_count >= 10;

VIEW (永続ビュー) との違い

項目VIEW (永続ビュー)インラインビュー
定義場所DB に CREATE VIEW★ クエリ内 (FROM 句)
名前あり (再利用可能)そのクエリ限り
永続化定義は永続都度生成・破棄
権限管理VIEW にGRANT可能元テーブル権限
用途共通の集計・絞り込みを再利用その場の中間結果
-- 永続 VIEW
CREATE VIEW recent_orders AS
SELECT user_id, COUNT(*) AS order_count
FROM orders
WHERE created_at >= '2026-01-01'
GROUP BY user_id;

-- どこからでも使える
SELECT * FROM recent_orders WHERE order_count >= 10;

-- 一方インラインビューは 1 クエリ限り
SELECT *
FROM (SELECT user_id, COUNT(*) AS order_count
      FROM orders WHERE ... GROUP BY user_id) AS r
WHERE r.order_count >= 10;

典型ユースケース 1: 集計結果を更に絞り込む

SQL では集計関数の結果に対して直接 WHERE は使えず HAVING が必要ですが、インラインビューで集計後の絞り込みを分かりやすく書けます。

-- HAVING でも書ける
SELECT user_id, COUNT(*) AS cnt
FROM orders
GROUP BY user_id
HAVING COUNT(*) >= 10;

-- インラインビューで段階的に
SELECT *
FROM (
    SELECT user_id, COUNT(*) AS cnt
    FROM orders
    GROUP BY user_id
) AS t
WHERE cnt >= 10;
-- → t.cnt のエイリアスを再利用できる

典型ユースケース 2: TOP N (上位 N 件)

-- カテゴリごとに売上 TOP 3 (ROW_NUMBER + インラインビュー)
SELECT category_id, product_name, sales
FROM (
    SELECT
        category_id,
        product_name,
        sales,
        ROW_NUMBER() OVER (
            PARTITION BY category_id
            ORDER BY sales DESC
        ) AS rn
    FROM products
) AS ranked
WHERE rn <= 3;

-- 国別の人口最大都市
SELECT country, city, population
FROM (
    SELECT
        country,
        city,
        population,
        ROW_NUMBER() OVER (PARTITION BY country ORDER BY population DESC) AS rn
    FROM cities
) AS t
WHERE rn = 1;

典型ユースケース 3: 結合前に絞り込み

-- ❌ 全件 JOIN してから絞り込み (重い)
SELECT u.name, o.total
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.created_at >= '2026-01-01';

-- ✅ インラインビューで先に絞り込む
SELECT u.name, ro.total
FROM users u
JOIN (
    SELECT user_id, SUM(amount) AS total
    FROM orders
    WHERE created_at >= '2026-01-01'
    GROUP BY user_id
) AS ro ON u.id = ro.user_id;
-- → DB によっては自動最適化されるが、明示的に書くと安定

相関サブクエリとの違い

種類場所外側参照
スカラサブクエリSELECT / WHERESELECT (SELECT MAX(...) FROM ...)
インラインビュー★ FROM不可 (LATERAL 除く)FROM (SELECT ...) AS t
相関サブクエリWHERE / SELECT★ するWHERE EXISTS (SELECT 1 FROM ... WHERE x = outer.x)
CTE (WITH)クエリ先頭不可WITH x AS (...) SELECT ...

CTE (Common Table Expression / WITH 句)

インラインビューが深くネストすると読みづらいので、CTE で名前を付けて先頭に分離する方が読みやすくなります。

-- ❌ インラインビューが深い
SELECT *
FROM (
    SELECT category_id, AVG(price) AS avg_price
    FROM (
        SELECT category_id, price
        FROM products
        WHERE deleted_at IS NULL
    ) AS active_products
    GROUP BY category_id
) AS category_avg
WHERE avg_price > 1000;

-- ✅ CTE で段階的に
WITH active_products AS (
    SELECT category_id, price
    FROM products
    WHERE deleted_at IS NULL
),
category_avg AS (
    SELECT category_id, AVG(price) AS avg_price
    FROM active_products
    GROUP BY category_id
)
SELECT * FROM category_avg WHERE avg_price > 1000;

-- 再帰 CTE で組織図のような階層を辿る
WITH RECURSIVE org AS (
    SELECT id, name, parent_id, 0 AS level
    FROM employees WHERE parent_id IS NULL
    UNION ALL
    SELECT e.id, e.name, e.parent_id, o.level + 1
    FROM employees e
    JOIN org o ON e.parent_id = o.id
)
SELECT * FROM org ORDER BY level, id;

各 DB の対応状況

DBインラインビューCTE再帰 CTE
MySQL 8.0+
MySQL 5.7 以下××
MariaDB 10.2+
PostgreSQL
Oracle
SQL Server
SQLite 3.8.3+

パフォーマンスの注意点

  • 多くの DB はインラインビューをマージして最適化する (MySQL 5.7+, PostgreSQL, Oracle, SQL Server)
  • ただし ORDER BY / LIMIT / GROUP BY を含むと materialize される傾向 (= テンポラリテーブル化)
  • 大きな結果セットをインラインビューにするとメモリ・ディスク負荷
  • 外側からインデックスが効かなくなることがある → EXPLAIN で確認
  • PostgreSQL 12+ では CTE もマージされるようになった (古い PG は CTE が最適化フェンスだった)
-- 実行計画を確認
EXPLAIN
SELECT *
FROM (SELECT user_id, COUNT(*) AS cnt FROM orders GROUP BY user_id) AS t
WHERE cnt >= 10;

-- PostgreSQL の場合
EXPLAIN ANALYZE ...;

エイリアス必須に注意

-- ❌ MySQL / PostgreSQL: ERROR (エイリアスが無い)
SELECT * FROM (SELECT id FROM users);

-- ✅ AS で名前を付ける (AS は省略可だが付ける方が読みやすい)
SELECT * FROM (SELECT id FROM users) AS t;

-- インラインビュー内のカラム名が重複する場合は外側で参照不能
SELECT a, b
FROM (
    SELECT u.id AS a, o.id AS a, ...  -- ❌ 重複
    FROM users u JOIN orders o ON ...
) AS t;

-- 明示的に別名を付ける
SELECT t.uid, t.oid
FROM (
    SELECT u.id AS uid, o.id AS oid
    FROM users u JOIN orders o ON u.id = o.user_id
) AS t;

UPDATE / DELETE での利用

-- PostgreSQL / SQL Server で UPDATE FROM
UPDATE orders o
SET status = 'flagged'
FROM (
    SELECT user_id
    FROM orders
    GROUP BY user_id
    HAVING SUM(amount) > 1000000
) AS big_spenders
WHERE o.user_id = big_spenders.user_id;

-- MySQL は UPDATE with JOIN
UPDATE orders o
JOIN (
    SELECT user_id FROM orders GROUP BY user_id HAVING SUM(amount) > 1000000
) AS big ON o.user_id = big.user_id
SET o.status = 'flagged';

FAQ

Q: インラインビューと CTE、どちらを使うべき?
A: 1 度しか使わないならインラインビューでも CTE でもよいが、段階が深い・複数回参照するなら CTE が圧倒的に読みやすい。新規 SQL なら CTE 推奨。

Q: インラインビューでインデックスは効く?
A: 内側の元テーブルのインデックスは効きます。外側からインラインビュー結果に対する更なる検索は、Optimizer がマージできれば効くが、materialize されるとフルスキャンになることも。EXPLAIN で確認

Q: ネストが深くて読めない
A: CTE に書き直すか、永続 VIEW として切り出すと劇的に読みやすくなります。SQL は左から右・上から下に読める形にするのが原則。