【楽しく学ぶSQL入門】第14章:システム開発現場で差がつくSQL応用構文とインデックスの仕組み

公開日:
更新日:

システム開発において、基本となるデータ操作を習得した後に重要となるのが、より実戦的で高度なクエリ構文やパフォーマンスの最適化。効率的なコーディングと検索速度の向上を両立させることは、大規模なデータを扱う上での必須要件です。本章では、覚えておくと表現の幅が劇的に広がる応用テクニックについて詳しく見ていきましょう。

システム開発現場で差がつくSQL応用構文

EXISTS構文

複数のSELECT文を組み合わせるアプローチとして「副問い合わせ(サブクエリ)」や「テーブル結合(JOIN)」が挙げられる。しかし、特定のデータが存在するかどうか判定する場合、EXISTS句を使うことで、よりシンプルかつ高速にSQLを記述できる局面があります。

SELECT 列名 FROM テーブル名
WHERE EXISTS ( 入れ子にするSELECT文 );

EXISTSは、主にWHERE句の中でデータの存在チェックを行う条件式として使用されます。条件に合致するレコードが「1件でも見つかった時点で判定を終了する」という性質があるため、データ量が多い場合に処理を大幅に効率化できるのが特徴。

具体的な動きをシミュレーションしてみましょう。例えば、以下のような「userテーブル」と「jobテーブル」が定義されているとします。それぞれのidは互いに対応関係を持っています。(例.id = 1の山田さんは営業職)

【userテーブル】

id name
1 山田
2 佐藤
3 小西
4 吉村
5 田中

【jobテーブル】

id job_name
1 営業
2 営業
3 技術
4 技術
5 事務

上記の状態から、EXISTSを用いて職種が「営業」であるユーザーのid and nameを抽出する場合、SQL文は以下のように組み立てます。

SELECT id, name
FROM user
WHERE EXISTS (
    SELECT * FROM job
    WHERE user.id = job.id
    AND job.job_name = '営業'
);

クエリの実行結果を確認しましょう。条件に合致したユーザーのみが出力されます。

id name
1 山田
2 佐藤

入れ子構造の内部にある 「user.id = job.id」 によって2つのテーブルの紐づきを制御し、かつ 「job.job_name = ‘営業’」 で絞り込み条件を表現する仕組みです。

なお、EXISTS句には存在しないことを条件とする否定形、すなわちNOT EXISTSも存在します。上記のSQLをNOT EXISTSに置き換えた場合、出力結果が反転してどのように変化するか、ぜひ考えてみてください。

参考サイト:MySQL 公式サイト(EXISTSサブクエリ)

CASE WHEN構文

CASE WHEN構文を使用することで、SQLのデータ参照領域において、JavaプログラムのIF文やSWITCH文のような条件分岐をシミュレートすることが可能になります。2パターンの表記法を用途に応じて使い分けるのを意識すること。

Ⅰ.特定の値を直接評価して分岐する場合

特定の列の値をそのまま比較対象としてマッピングしていくシンプルな記述方法です。

CASE 評価対象の列や式 
    WHEN 値1 THEN 値1の時に返す値
    [WHEN 値2 THEN 値2の時に返す値]
    [ELSE デフォルト値]
END

例として、収支テーブルの全データから日付、費目、および独自の「固定変動チェック」列を生成して取得してみましょう。費目が「定期代」であれば『固定費』、費目が「交通費」「飲食費」「娯楽費」なら『変動費』、それ以外の費目はすべて『不明』と明示して出力を試みます。

SELECT income_and_expenditure_date, expense,
    CASE expense 
        WHEN '定期代' THEN '固定費'
        WHEN '交通費' THEN '変動費'
        WHEN '飲食費' THEN '変動費'
        WHEN '娯楽費' THEN '変動費'
        ELSE '不明'
    END AS 固定変動チェック
FROM income_and_expenditure;

Ⅱ.複雑な比較条件で判定・分岐する場合

WHENの直後に個別の不等号判定や論理式を記述できる、極めて汎用性の高いアプローチです。

CASE 
    WHEN 条件1 THEN 条件1の時に返す値
    [WHEN 条件2 THEN 条件2の時に返す値]
    [ELSE デフォルト値]
END

例として、出費額の多寡に応じたラベル判定(出費が30,000円ちょうどなら『定期代』、1,000円以下なら『小出費』、3,000円以下なら『中出費』、値が未入力(NULL)の場合は『出費ではない』、それ以外は一律『大出費』とする)を行うSQLを組み立てます。

SELECT income_and_expenditure_date, expenditure,
    CASE 
        WHEN expenditure = 30000 THEN '定期代'
        WHEN expenditure <= 1000 THEN '小出費'
        WHEN expenditure <= 3000 THEN '中出費'
        WHEN expenditure IS NULL THEN '出費ではない'
        ELSE '大出費'
    END AS 出費額コメント
FROM income_and_expenditure;

参考サイト:MySQL 公式サイト(制御フロー関数)

INSERT ~ SELECT構文

通常のINSERT手続きでは、1回のリクエスト(1つのSQL文)に対して原則として1行ずつデータ登録を行う形式をとります。これに対し、検索結果を直接インプットとして利用する「副問い合わせを応用した挿入処理(INSERT ~ SELECT構文)」を用いると、一回の処理で複数行のレコードを一括して書き加えることが可能となります。

INSERT INTO テーブル名 (列名, 列名, ...)
SELECT文;

通常使用する挿入コマンドのVALUES句以下の領域が、丸ごとSELECT文の記述に置き換わっている点がポイント。その性質上、下部のSELECT文によって抽出した結果の「列数」と、受け手となるINSERT文側の「指定列数」、および双方の「データ型」の並び順が完全に一致していることが絶対の実行条件となります。ここが不整合を起こすとエラーの原因となる盲点。

例)収支テーブル内に存在するデータの中から、出費額が0より大きいレコードを費目ごとに集計。その合計額(SUM)と平均額(AVG)を算出。それらを「収支集計(total)テーブル」へ一括登録するバッチ処理を行う。

まずは、データの格納先となる「収支集計テーブル」を新規作成するためのDDLコマンドを発行。

CREATE TABLE total (
    expense VARCHAR(13),
    sum INT(10),
    average DECIMAL(10)
);

続けてINSERT 〜 SELECTを実行して集計データをまとめて書き込みます。

INSERT INTO total (expense, sum, average)
SELECT expense, SUM(expenditure), AVG(expenditure)
FROM income_and_expenditure
WHERE expenditure > 0
GROUP BY expense;

コマンド実行完了後に、内包されたSELECT単体の抽出データと、実際にtotalテーブルの中身に登録されたデータレコードをそれぞれ見比べて、差異がないか確認してみましょう。

参考サイト:MySQL 公式サイト(INSERT ... SELECT 構文)

インデックスの仕組みとパフォーマンス最適化

分厚い本の中から、特定のキーワードについて解説しているページを見つけ出したいとき、皆さんはどのような操作を行いますか?おそらく、最初のページから1枚ずつめくって探していくのではなく、本の巻末に用意されている「索引」を使って最短ルートで目的のページを見つけ出す方法をとるでしょう。これと同じ仕組みを、データベースのデータ高速検索機能として実装する方法が存在します。

SQLデータベースの管理システムで使用されるこの検索用の「索引」のことを、技術用語でINDEX(インデックス)と呼びます。システム開発現場では必須の知識です。

INDEXが持つ基本的なルール・特徴

  • インデックスは、テーブルの特定の「列(カラム)」をターゲットとして作成される。
  • インデックスを識別・制御するために、必ず固有の「名称(インデックス名)」を紐付ける必要がある。

インデックスの作成方法

特定の列に目印となるインデックスを紐付ける場合は、CREATE INDEX文を発行します。構文は以下の通り。

CREATE INDEX インデックス名 ON テーブル名 (列名);

例として、社員テーブルの部署ID列(group_id)を検索軸と想定し、`group_id_index` という名称のインデックスを作成する処理を組み立ててみましょう。

CREATE INDEX group_id_index ON employee(group_id);

不要になったインデックスの削除方法

システム運用ルールやアクセスパターンの変更に伴い、作成した索引情報を破棄したい場合には、DROP INDEX文を以下のように発行します。

DROP INDEX group_id_index ON employee;

インデックスの適用には、パフォーマンス面に絶大な恩恵がある一方で、トレードオフとなる副反応も存在します。システム開発現場ではこの両面を天秤にかけて設計を行うのが鉄則です。

インデックス運用のメリット

INDEXが構成されている列に対して条件検索がリクエストされた場合、データベースの管理システム(DBMS)がバックグラウンドで自動的に最適な索引ルートを選択。データ全件を探索する無駄が省かれるため、応答速度が劇的に高速化されるケースが多いです。効果を発揮する典型例は以下の通り。

  • WHERE句での絞り込み時:`SELECT * FROM employee WHERE group_id = 1;`
  • ORDER BY句での並び替え時:`SELECT * FROM employee ORDER BY group_id;`
  • JOINにおける結合条件の評価時:`SELECT * FROM employee AS e JOIN company_group AS c ON e.group_id = c.group_id;`

インデックス運用のデメリット

  • 物理的な索引情報を格納・保持しておくための追加のディスク記録容量が別途必要。
  • データの新規追加(INSERT)、書き換え(UPDATE)、レコード削除(DELETE)の処理負荷(オーバーヘッド)が増加する。テーブル側の実データを変更した際、同期して裏側のインデックス構造も常に最新の状態へ再帰的に書き換える必要があるため。

なんでもかんでも無差別にインデックスを付与する設計は逆効果を招きます。システム内でのアクセス頻度やクエリの特性を考慮し、最も効果の大きい項目に対して戦略的にインデックスを配置する工夫を凝らしましょう。

参考サイト:MySQL 公式サイト(最適化とインデックス)

お問い合わせ

CONTACT

業務システムに関するお困りごと、WEBサイトの制作など、
まずはお気軽にお問い合わせください。

会員サイト
CONTACT
06-6305-2278
採用サイトはこちらRECRUIT