ビューとインデックス

ビューとインデックス

図書館で本を頻繁に借りる場面を想像してください。本を見つけるには2つの方法があります:1つ目は毎回すべての棚を探す方法(フルテーブルスキャン)、2つ目はまずインデックスカード目録を確認して正しい棚に直接行く方法(インデックスクエリ)。図書館の「おすすめ読書リスト」はビューのようなものです — 本物の棚ではありませんが、異なる棚から厳選された本のリストで、目的のものを見つけやすくします。SQLのビューインデックスも同じように動作します。


1. コアコンセプト

ビュー — 概念

ビュー仮想テーブルで、データを保存しませんがSELECTクエリ文を保存します。ビューにアクセスするたびに、データベースはビューの定義されたクエリを実行し、結果を返します。

SQL
-- ビューを作成:従業員情報ビュー
CREATE VIEW v_employee_info AS
SELECT 
    e.employee_id,
    CONCAT(e.first_name, e.last_name) AS name,
    e.salary,
    d.department_name AS department,
    d.city
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id;

ビューの使用は通常のテーブルと同じです:

SQL
-- ビューをクエリ
SELECT * FROM v_employee_info WHERE salary > 10000;

ビューの作成

SQL
CREATE VIEW view_name AS
SELECT statement;
SQL
-- 部門統計ビューを作成
CREATE VIEW v_department_stats AS
SELECT 
    d.department_id,
    d.department_name AS department,
    COUNT(e.employee_id) AS employee_count,
    AVG(e.salary) AS avg_salary,
    SUM(e.salary) AS total_salary
FROM departments d
LEFT JOIN employees e ON d.department_id = e.department_id
GROUP BY d.department_id, d.department_name;

ビューの変更

SQL
-- 方法1:CREATE OR REPLACE(推奨)
CREATE OR REPLACE VIEW v_employee_info AS
SELECT 
    e.employee_id,
    CONCAT(e.first_name, e.last_name) AS name,
    e.salary,
    e.hire_date,
    d.department_name AS department
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id;

-- 方法2:ALTER VIEW
ALTER VIEW v_employee_info AS
SELECT employee_id, first_name, last_name, salary FROM employees;

ビューの削除

SQL
DROP VIEW IF EXISTS v_employee_info;
💡 ヒント:ビューを削除しても基盤となるテーブルデータには影響しません — ビューはクエリの「ショートカット」にすぎません。

ビューの利点

利点 説明
複雑なクエリの簡素化 複雑なJOINをビューとしてカプセル化;SELECT * FROM view_nameで使用
データセキュリティ ユーザーに特定のカラムや行のみ公開し、機密データを非表示
論理的独立性 ビュー定義の変更がビューを使用するアプリケーションに影響しない
一貫したロジック すべてのユーザーが同じクエリロジックを使用し、重複記述を回避

ビューの欠点

欠点 説明
パフォーマンスオーバーヘッド アクセスごとに基盤クエリを実行;複雑なビューは遅くなる可能性
更新制限 JOIN、集約関数、DISTINCTを含むビューは通常更新不可
デバッグの困難さ 多層ネストされたビューはトラブルシューティングの困難さを増加
💡 ヒント:ビュー自体はデータを保存しません — それらは「クエリのエイリアス」です。基盤となるテーブルデータが変更されると、ビューの結果も相应に変更されます。


インデックス — 概念

インデックスデータ構造で、本の目次に似ており、データベースがデータを素早く位置特定し、フルテーブルスキャンを回避するのに役立ちます。

SQL
-- インデックスなし:フルテーブルスキャン(行ごとに検索)
SELECT * FROM employees WHERE last_name = 'Zhang';

-- インデックスあり:直接ルックアップ(インデックス経由)
CREATE INDEX idx_last_name ON employees(last_name);
SELECT * FROM employees WHERE last_name = 'Zhang';

インデックスの作成

SQL
-- 通常インデックス(重複値を許可)
CREATE INDEX idx_column ON table_name(column_name);

-- ユニークインデックス(重複値を許可しない)
CREATE UNIQUE INDEX idx_column ON table_name(column_name);

-- フルテキストインデックス(全文検索用)
CREATE FULLTEXT INDEX idx_column ON table_name(column_name);

-- 複合インデックス(複数カラムの組み合わせ)
CREATE INDEX idx_columns ON table_name(column1, column2);

インデックスの削除

SQL
-- MySQL構文
DROP INDEX idx_column ON table_name;

-- 標準構文
ALTER TABLE table_name DROP INDEX idx_column;

インデックスの種類

種類 キーワード 説明 ユースケース
通常インデックス INDEX 基本インデックス、重複値を許可 頻繁にクエリされるカラム
ユニークインデックス UNIQUE INDEX カラム値が一意である必要がある メール、ID番号など
フルテキストインデックス FULLTEXT INDEX 全文検索をサポート 記事コンテンツ、製品説明
複合インデックス INDEX(col1, col2) 複数カラムの組み合わせインデックス 複数条件のクエリ

複合インデックスと最左プレフィックスルール

複合インデックスは最左プレフィックスルールに従います:クエリ条件はインデックスの最も左のカラムから一致する必要があります。

SQL
-- 複合インデックスを作成
CREATE INDEX idx_name_age ON employees(last_name, first_name, salary);

-- ✅ インデックスを使用可能(最左カラムに一致)
SELECT * FROM employees WHERE last_name = 'Zhang';
SELECT * FROM employees WHERE last_name = 'Zhang' AND first_name = 'Wei';

-- ❌ インデックスを使用不可(最左カラムをスキップ)
SELECT * FROM employees WHERE first_name = 'Wei';
SELECT * FROM employees WHERE salary > 10000;
💡 ヒント:複合インデックスを設計する際は、クエリで最も頻繁に使用されるカラムを左に配置してください。

インデックスのコスト

コスト 説明
ストレージスペース インデックスには追加のディスクスペースが必要
書き込みパフォーマンス INSERT/UPDATE/DELETEはインデックスの更新を同期する必要がある
メンテナンスコスト インデックスが多すぎると書き込み速度が低下
💡 ヒント:多ければ多いほど良いというわけではありません!頻繁にクエリされ、データ量が多いカラムにのみインデックスを作成してください。小さなテーブル(数百行)は通常インデックスが不要です。


2. 基本構文

ビューの構文

SQL
-- ビューを作成
CREATE [OR REPLACE] VIEW view_name AS SELECT statement;

-- ビューをクエリ(通常のテーブルと同じ)
SELECT * FROM view_name WHERE condition;

-- ビューを削除
DROP VIEW [IF EXISTS] view_name;
💡 ヒント:ビューにはv_またはview_プレフィックスを付けることを推奨します。これにより実テーブルと容易に区別できます。

インデックスの構文

SQL
-- インデックスを作成
CREATE [UNIQUE|FULLTEXT] INDEX index_name ON table_name(column1, column2, ...);

-- インデックスを削除
DROP INDEX index_name ON table_name;

-- テーブルのインデックスを表示
SHOW INDEX FROM table_name;
💡 ヒント:主キー(PRIMARY KEY)とユニーク制約(UNIQUE)は自動的にインデックスを作成します — 手動で作成する必要はありません。

💡 ヒントEXPLAINを使用してクエリがインデックスを使用しているか確認できます:EXPLAIN SELECT * FROM employees WHERE last_name = 'Zhang';


例:ビューの作成と使用(難易度⭐)

SQL
-- 注文詳細ビューを作成
CREATE OR REPLACE VIEW v_order_detail AS
SELECT 
    o.order_id,
    CONCAT(e.first_name, e.last_name) AS owner,
    d.department_name AS department,
    o.order_date,
    o.total_amount AS amount,
    o.status
FROM orders o
LEFT JOIN employees e ON o.employee_id = e.employee_id
LEFT JOIN departments d ON e.department_id = d.department_id;

-- ビューを使用してクエリ
SELECT * FROM v_order_detail WHERE status = 'completed';
▶ 試してみよう

出力:

TEXT
order_id | owner      | department | order_date | amount   | status
---------+------------+------------+------------+----------+-----------
    1001 | WangQiang  | Sales      | 2026-05-10 | 2500.00  | completed
    1002 | WangQiang  | Sales      | 2026-05-15 | 1800.00  | completed
    1005 | ZhaoMin    | Sales      | 2026-06-10 | 4100.00  | completed
SQL
-- ビューを使用して部門別の注文金額を集約
SELECT department, COUNT(*) AS order_count, SUM(amount) AS total_amount
FROM v_order_detail
GROUP BY department;

クエリのアプローチ:

  1. ビューは複雑な3テーブルJOINロジックをカプセル化
  2. 簡単なクエリだけで済む — JOINを再記述する必要なし
  3. 基盤データが変更されるとビューの結果は自動的に更新

例:インデックスを作成してクエリパフォーマンスを最適化(難易度⭐⭐)

SQL
-- 現在のクエリの実行計画を表示
EXPLAIN SELECT * FROM employees WHERE last_name = 'Zhang';
▶ 試してみよう

出力(インデックスなし):

TEXT
+----+-------------+-----------+------+---------------+------+---------+------+------+-------+
| id | select_type | table     | type | possible_keys | key  | key_len | ref  | rows | Extra |
+----+-------------+-----------+------+---------------+------+---------+------+------+-------+
|  1 | SIMPLE      | employees | ALL  | NULL          | NULL | NULL    | NULL |    6 | Using where |
+----+-------------+-----------+------+---------------+------+---------+------+------+-------+

type = ALLはフルテーブルスキャンを示し、rows = 6はすべての行がスキャンされたことを示します。

SQL
-- インデックスを作成
CREATE INDEX idx_last_name ON employees(last_name);

-- 実行計画を再表示
EXPLAIN SELECT * FROM employees WHERE last_name = 'Zhang';

出力(インデックスあり):

TEXT
+----+-------------+-----------+------+---------------+---------------+---------+-------+------+-------+
| id | select_type | table     | type | possible_keys | key           | key_len | ref   | rows | Extra |
+----+-------------+-----------+------+---------------+---------------+---------+-------+------+-------+
|  1 | SIMPLE      | employees | ref  | idx_last_name | idx_last_name | 152     | const |    1 |       |
+----+-------------+-----------+------+---------------+---------------+---------+-------+------+-------+

type = refはインデックスが使用されていることを示し、rows = 1は1行のみスキャンされたことを示します。

SQL
-- 複合インデックスを作成
CREATE INDEX idx_status_date ON orders(status, order_date);

-- インデックスを使用できるクエリ
EXPLAIN SELECT * FROM orders WHERE status = 'completed' AND order_date > '2026-06-01';

-- テーブルのすべてのインデックスを表示
SHOW INDEX FROM employees;

クエリのアプローチ:

  1. EXPLAINのtypeカラム:ALL(フルテーブルスキャン)→ ref(インデックス参照)はパフォーマンスの向上を示す
  2. rowsカラム:スキャン行数が少ないほど良い
  3. 複合インデックスは最左プレフィックスルールに従って使用する必要がある

3. 一般的な応用シナリオ

シナリオ1:ビューで権限管理を簡素化

SQL
-- 機密情報を含まないビューを作成
CREATE OR REPLACE VIEW v_employee_public AS
SELECT 
    employee_id,
    CONCAT(first_name, last_name) AS name,
    d.department_name AS department
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id;

-- 一般ユーザーはこのビューのみアクセスでき、給与やその他の機密情報にはアクセスできない
SELECT * FROM v_employee_public;
💡 アプローチ:ビューから機密カラム(給与、メールなど)を除外し、ビューを通じてデータアクセスを制御します。

シナリオ2:ビューでデータレポートを実装

SQL
-- 月次売上レポートビューを作成
CREATE OR REPLACE VIEW v_monthly_sales AS
SELECT 
    DATE_FORMAT(order_date, '%Y-%m') AS month,
    COUNT(*) AS order_count,
    SUM(total_amount) AS total_sales,
    AVG(total_amount) AS avg_order_amount,
    COUNT(DISTINCT customer_id) AS customer_count
FROM orders
WHERE status != 'cancelled'
GROUP BY DATE_FORMAT(order_date, '%Y-%m');

-- レポートをクエリ
SELECT * FROM v_monthly_sales ORDER BY month;

出力:

TEXT
month   | order_count | total_sales | avg_order_amount | customer_count
--------+-------------+-------------+------------------+---------------
2026-05 |           2 |     4300.00 |          2150.00 |              2
2026-06 |           3 |     8250.00 |          2750.00 |              3

❓ よくある質問

質問:ビューと一時テーブルの違いは何ですか? 回答: ビューはデータを保存せず、クエリごとに基盤SQLを実行するため、結果は常に最新です;一時テーブルはデータスナップショットを保存し、クエリは高速ですがデータが古くなる可能性があります。ビューは長期的なクエリテンプレートに適し、一時テーブルは中間計算ステップに適しています。

質問:ビューはデータを変更できますか? 回答: ビューの定義によります。JOIN、集約関数、DISTINCT、サブクエリを含むビューは通常更新不可(SELECTのみ)です。シンプルな単一テーブルビュー(集約なし)はUPDATE可能です。実際には、ビューは主にクエリに使用され、ビューを通じたデータの変更は推奨しません。

質問:インデックスは多いほど良いですか? 回答: いいえ。インデックスはストレージスペースを消費し、INSERT/UPDATE/DELETEのパフォーマンスを低下させます。頻繁にクエリされ、高いカーディナリティ(多くのユニーク値)を持つカラムにのみインデックスを作成してください。小さなテーブルは通常インデックスが不要です。

質問:クエリがインデックスを使用しているかどうかはどうやって確認できますか? 回答: EXPLAINコマンドを使用して実行計画を表示します。typeカラムに注目:ALL(フルテーブルスキャン、最も遅い)→ indexrangerefeq_refconst(最も速い)。typeALLでテーブルに大量のデータがある場合、インデックスが必要な可能性があります。


📖 まとめ


📝 演習

演習1(⭐):給与が10000より高い従業員の情報(名前、給与、部門名)を表示するビューv_high_salaryを作成し、そのビューを使用して「技術」部門の高給与従業員をクエリしてください。

演習2(⭐⭐):部門名、従業員数、平均給与、最高給与、最低給与を含むビューv_department_reportを作成してください。その後、そのビューを使用して平均給与が最も高い部門を検索してください。

演習3(⭐⭐)ordersテーブルのcustomer_idカラムにインデックスを作成し、EXPLAINを使用してインデックス作成前後のクエリパフォーマンスを比較してください。その後、orders(status, order_date)に複合インデックスを作成し、どのクエリがそれを使用できるかテストしてください。


次のレッスン

👉 18-practice-aggregate - 練習:データ分析:集約関数、グループ化クエリ、条件式、高度な関数を一緒に適用し、実践シナリオを通じてデータ分析スキルを向上させましょう!

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%