本研修は、SQLの基礎から応用までを体系的に学び、実務で即戦力となるデータベーススキルを習得することを目的としています。データ分析からシステム開発まで、幅広い業務に対応できる実践的な知識を提供します。
データベース初心者からある程度の知識をお持ちの方まで幅広く対応しています。プログラミングの基礎知識があれば理想的ですが、必須ではありません。IT部門の方はもちろん、データを扱う業務に関わるすべての方に最適です。
IBMのレイモンド・F・ボイスにより開発された「構造化照会言語(Structured Query Language)」。リレーショナルデータベースを操作するための標準言語として誕生しました。
ANSI(米国規格協会)とISO(国際標準化機構)によってSQL標準が確立。データベース間での互換性が向上しました。
ほぼすべてのRDBMS(リレーショナルデータベース管理システム)で採用され、データアクセスの世界標準言語として不動の地位を確立しています。
データを格納する基本単位です。表形式で構成され、行と列からなります。例えば「顧客テーブル」や「注文テーブル」などのように、一つの実体や概念を表現します。
テーブル内の一つのデータセットを表します。顧客テーブルであれば、1人の顧客情報が1行に対応します。
特定の種類のデータを表します。例えば「氏名」「住所」「電話番号」などの項目がそれぞれの列となります。
主キーは各行を一意に識別する列で、外部キーは他のテーブルとの関連を表す列です。これらによってテーブル間の関係が定義されます。
企業向け高性能データベース。大規模システムでの信頼性が高く、金融機関や大企業で広く採用されています。独自の拡張SQLを持ちます。
オープンソースの人気データベース。Webアプリケーションとの相性が良く、PHPとの組み合わせが一般的です。Oracle社が所有しています。
高度な機能を持つオープンソースデータベース。拡張性に優れ、複雑なデータモデルや高度なトランザクション処理に適しています。
Microsoftのデータベース製品。Windowsプラットフォームとの統合が強みで、.NET開発との相性が良いです。Azure連携も充実しています。
取得したいデータの列(カラム)を指定します。「SELECT * FROM テーブル名」のように「*」を使用すると全列を選択できます。
データを取得するテーブルを指定します。複数のテーブルを指定することも可能です。
取得するデータの条件を指定します。「WHERE 年齢 >= 20」のように比較演算子を使った条件式を記述します。
SQL文は一般的にセミコロン(;)で終わります。大文字・小文字は区別されませんが、見やすさのため予約語は大文字で書くことが多いです。
-- 基本的なSELECT文
SELECT 氏名, 年齢, 住所 FROM 顧客;
-- 全列を選択
SELECT * FROM 商品;
-- 計算式を含む選択
SELECT 商品名, 単価, 在庫数, 単価 * 在庫数 AS 在庫金額
FROM 商品;
-- 条件付き抽出
SELECT 氏名, メールアドレス
FROM 顧客
WHERE 年齢 >= 20 AND 都道府県 = '東京都';
ORDER BY句を使用して結果をソートします。デフォルトは昇順(ASC)です。
例: SELECT * FROM 商品 ORDER BY 価格;
DESC キーワードを使用すると降順にソートできます。
例: SELECT * FROM 顧客 ORDER BY 登録日 DESC;
複数のカラムでソートする場合は、優先順位が高いものから順に指定します。
例: SELECT * FROM 注文 ORDER BY 顧客ID, 注文日 DESC;
完全一致を検索します。例:WHERE 部門 = '営業部'
大小関係で比較します。例:WHERE 価格 >= 1000
複数の値のいずれかに一致するものを検索します。例:WHERE 都道府県 IN ('東京都', '神奈川県', '千葉県')
LIKEを使って文字列の部分一致を検索します。例:WHERE 商品名 LIKE '%スマホ%'
AND, OR, NOTを使って複合条件を作成します。例:WHERE 価格 >= 1000 AND カテゴリ = '電化製品'
NULLは「値がない」「未知」「適用外」を表す特殊な値です。空文字('')やゼロ(0)とは異なります。例えば、顧客の退会日がNULLであれば「まだ退会していない」ことを意味します。
通常の等価演算子(=)ではNULLを検索できません。IS NULLまたはIS NOT NULL演算子を使用します。
-- NULL値を持つレコードを検索
SELECT * FROM 顧客 WHERE 退会日 IS NULL;
-- NULL値を持たないレコードを検索
SELECT * FROM 社員 WHERE 上司ID IS NOT NULL;
レコード数をカウントします。COUNT(*)は全行、COUNT(列名)はNULLでない値の数をカウントします。
数値の合計を計算します。例えば、全注文の合計金額を求める場合に使用します。
数値の平均値を計算します。NULL値は無視されます。商品の平均価格などを求める際に便利です。
最小値/最大値を求めます。数値だけでなく、日付や文字列にも使用できます。
GROUP BY句と組み合わせると、グループごとに集計値を計算できます。例: SELECT 部門, AVG(給与) FROM 社員 GROUP BY 部門;
クエリの中に別のクエリを埋め込む技術です。複雑な条件を表現できます。
リストに含まれる値の検索に利用します。例: SELECT * FROM 商品 WHERE カテゴリID IN (SELECT ID FROM カテゴリ WHERE 親カテゴリ = '電化製品');
条件に一致するレコードの存在確認に使います。例: SELECT * FROM 顧客 WHERE EXISTS (SELECT * FROM 注文 WHERE 注文.顧客ID = 顧客.ID);
-- 部門ごとの平均給与を求める
SELECT 部門, AVG(給与) AS 平均給与
FROM 社員
GROUP BY 部門;
-- 複数列でのグループ化
SELECT 部門, 役職, COUNT(*) AS 人数
FROM 社員
GROUP BY 部門, 役職;
-- 集計結果に条件を設定
SELECT 商品カテゴリ, SUM(売上) AS 総売上
FROM 販売
GROUP BY 商品カテゴリ
HAVING SUM(売上) > 1000000;
HAVINGはGROUP BYの後に記述し、集計結果に対する条件を指定します。WHEREとは異なり、集約関数を条件に使用できます。




JOINを使うと、複数のテーブルを関連付けて一度に取得できます。基本形は「SELECT * FROM テーブル1 [JOIN種類] JOIN テーブル2 ON 結合条件」です。INNER JOINは両方のテーブルで一致する行のみ、LEFT/RIGHT JOINは指定した側のテーブルの全行を取得します。
外側のクエリの値を参照するサブクエリです。行ごとに異なる結果が得られます。
SELECT 商品名, 価格,
(SELECT AVG(価格) FROM 商品 AS s
WHERE s.カテゴリ = 商品.カテゴリ) AS カテゴリ平均価格
FROM 商品;
単一の値を返すサブクエリです。SELECT句やWHERE句で使用できます。
SELECT 社員名,
(SELECT 部署名 FROM 部署
WHERE 部署.部署ID = 社員.部署ID) AS 所属部署
FROM 社員;
ビューは「仮想テーブル」です。実際のデータは保存せず、クエリの定義のみを保存します。ユーザーには通常のテーブルのように見えます。
CREATE VIEW ビュー名 AS SELECT文; の形式で作成します。例:CREATE VIEW 高額商品 AS SELECT * FROM 商品 WHERE 価格 > 10000;
通常のテーブルと同様に使用できます。例:SELECT * FROM 高額商品 WHERE カテゴリ = '電化製品';
複雑なクエリの簡略化、セキュリティ(特定の列のみ公開)、データアクセスの一貫性確保などが可能です。
-- 単一行の挿入
INSERT INTO 顧客 (氏名, 電話番号, メール)
VALUES ('山田太郎', '03-1234-5678', 'yamada@example.com');
-- 複数行の挿入
INSERT INTO 商品 (商品名, 価格, カテゴリ)
VALUES
('スマートフォンA', 80000, '電子機器'),
('ノートブックB', 120000, '電子機器'),
('ヘッドフォンC', 25000, 'オーディオ');
-- クエリ結果の挿入
INSERT INTO 売上集計 (カテゴリ, 合計金額, 集計日)
SELECT カテゴリ, SUM(価格 * 数量), CURRENT_DATE
FROM 注文明細 JOIN 商品 ON 注文明細.商品ID = 商品.ID
GROUP BY カテゴリ;
特定のレコードの値を更新します。
UPDATE 社員
SET 給与 = 給与 * 1.1
WHERE 部署 = '営業部';
一度に複数の列を更新できます。
UPDATE 商品
SET 在庫数 = 在庫数 - 10,
最終更新日 = CURRENT_DATE
WHERE 商品ID = 12345;
別テーブルの値を参照して更新できます。
UPDATE 顧客
SET 累計購入額 = (
SELECT SUM(金額)
FROM 注文
WHERE 注文.顧客ID = 顧客.ID
);
常にWHERE句で条件を指定し、対象を限定
特定の条件に合致するレコードのみ削除
テーブル全体のデータを削除(WHERE句なし)
DELETE文は一度実行すると元に戻せません。特にWHERE句がない場合はテーブルの全データが削除されます。実行前にSELECTで対象を確認するか、トランザクション内で実行することをお勧めします。例: DELETE FROM 注文 WHERE 注文日 < '2020-01-01';
トランザクションの開始を宣言します。以降の操作がひとつの単位として扱われます。
複数のSQL文(INSERT, UPDATE, DELETEなど)を実行します。この時点ではまだ確定していません。
全ての処理が正常に完了した場合、変更を確定(コミット)します。この時点でデータベースに永続的に反映されます。
エラーが発生した場合や意図的に取り消す場合、変更を破棄(ロールバック)します。トランザクション開始前の状態に戻ります。
-- テーブル作成
CREATE TABLE 顧客 (
顧客ID INT PRIMARY KEY,
氏名 VARCHAR(100) NOT NULL,
電話番号 VARCHAR(20),
メールアドレス VARCHAR(100) UNIQUE,
登録日 DATE DEFAULT CURRENT_DATE
);
-- テーブル変更
ALTER TABLE 顧客
ADD 住所 VARCHAR(200),
MODIFY 電話番号 VARCHAR(30);
-- インデックス作成
CREATE INDEX idx_顧客_氏名 ON 顧客(氏名);
CREATE UNIQUE INDEX idx_顧客_メール ON 顧客(メールアドレス);
-- テーブル削除
DROP TABLE 古い顧客;
権限付与の基本構文: GRANT 権限 ON テーブル TO ユーザー;
権限剥奪の基本構文: REVOKE 権限 ON テーブル FROM ユーザー;
すべての属性が原子的(分割できない)であること。繰り返しグループがないこと。例えば、1つのセルに複数の電話番号を格納するのではなく、別々のレコードに分割します。
1NFを満たし、さらに部分関数従属性がないこと。つまり、非キー属性が複合主キーの一部だけに依存しないようにします。
2NFを満たし、さらに推移的関数従属性がないこと。非キー属性が他の非キー属性に依存しないようにします。
EXPLAIN コマンド(または同等のもの)を使用して、クエリがどのように処理されるかを確認します。インデックスの使用状況や、テーブルのスキャン方法などが分かります。
頻繁に検索・結合・ソートに使用される列にはインデックスを作成します。ただし、更新が多いテーブルでは過剰なインデックスが逆効果になることもあります。
必要な列だけを選択し、不要な結合や副問い合わせを避けます。大量のデータを扱う場合は、LIMIT句などを活用して結果セットを制限します。
定期的に実行する処理をスクリプト化し、効率的なデータ処理を実現します。
一括更新・挿入を活用し、トランザクション内でコミット頻度を調整して処理効率を向上させます。
エラー発生時のロールバック戦略や再試行メカニズムを実装し、データの整合性を確保します。
処理状況や結果を記録するログテーブルを設計し、バッチ処理の監視と問題追跡を容易にします。
CREATE PROCEDURE 注文処理(
IN 顧客ID INT,
IN 商品ID INT,
IN 数量 INT
)
BEGIN
DECLARE 在庫数 INT;
-- 在庫確認
SELECT 在庫 INTO 在庫数
FROM 商品 WHERE ID = 商品ID;
IF 在庫数 >= 数量 THEN
-- 注文登録
INSERT INTO 注文(顧客ID, 注文日)
VALUES(顧客ID, CURRENT_DATE);
-- 在庫更新
UPDATE 商品
SET 在庫 = 在庫 - 数量
WHERE ID = 商品ID;
END IF;
END;
CREATE FUNCTION 消費税計算(
価格 DECIMAL(10,2)
) RETURNS DECIMAL(10,2)
BEGIN
RETURN 価格 * 0.1;
END;
-- 関数の使用例
SELECT 商品名, 価格,
消費税計算(価格) AS 消費税,
価格 + 消費税計算(価格) AS 税込価格
FROM 商品;
-- 更新日時を自動記録するトリガー
CREATE TRIGGER 顧客更新記録
AFTER UPDATE ON 顧客
FOR EACH ROW
BEGIN
INSERT INTO 変更履歴 (
テーブル名, レコードID, 変更日時, 変更内容
) VALUES (
'顧客', NEW.顧客ID, NOW(),
CONCAT('更新: ', OLD.氏名, ' → ', NEW.氏名)
);
END;
-- 整合性確認トリガー
CREATE TRIGGER 在庫チェック
BEFORE INSERT ON 注文明細
FOR EACH ROW
BEGIN
DECLARE 在庫数 INT;
SELECT 在庫 INTO 在庫数
FROM 商品 WHERE 商品ID = NEW.商品ID;
IF 在庫数 < NEW.数量 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '在庫不足エラー';
END IF;
END;
// PHP例(危険なコード)
$username = $_POST['username'];
$query = "SELECT * FROM users
WHERE username = '$username'";
// ユーザーが username に
// ' OR 1=1 -- を入力すると全ユーザー情報が漏洩
// PHP例(安全なコード)
$username = $_POST['username'];
$stmt = $db->prepare(
"SELECT * FROM users
WHERE username = ?"
);
$stmt->bind_param("s", $username);
$stmt->execute();
SQLインジェクションを防ぐ主な対策:
1. プレースホルダ(パラメータ化クエリ)を使用する
2. データベース権限を適切に制限する
3. エラーメッセージを詳細に表示しない
4. 入力値のバリデーションを実施する
大きなテーブルを論理的に分割し、特定範囲のデータにアクセスを効率化します。日付・ID範囲・ハッシュ値などでデータを分割できます。
データを複数の物理サーバーに分散させる手法です。水平スケーリングが可能になり、負荷分散と可用性向上が実現できます。
大量データ処理では、インデックス活用・不要な結合回避・LIMIT句の使用などが重要です。また、一度に処理するデータ量を制限する分割処理も有効です。
負荷の少ない時間帯にバッチ処理を実行し、ユーザー体験への影響を最小化します。また、長時間実行クエリを短い処理に分割することも効果的です。
デッドロック回避のベストプラクティス:
1. 常に同じ順序でテーブルにアクセスする
2. トランザクションを短く保つ
3. 適切な分離レベルを選択する
4. 行レベルロックを活用する
データ重要度、許容可能なダウンタイム、リソース制約を考慮した計画を立てます。フルバックアップと差分/増分バックアップを組み合わせるのが一般的です。
スケジュールに従ってバックアップを実行します。重要なシステムでは数時間ごと、変更の少ないシステムでは日次や週次が一般的です。
バックアップの整合性を定期的に検証します。実際にリストアできることをテスト環境で確認することが重要です。
迅速に復旧できるよう、明確なリストア手順を文書化し、定期的に訓練を行います。リカバリ時間も測定しておきましょう。
UTF-8を使用することで、ほぼすべての言語の文字を扱えます。データベース作成時に適切な文字コードを指定しましょう。
例: CREATE DATABASE multilingual CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
文字列の比較や並べ替えルールを定義します。言語によって異なる照合順序が必要になることがあります。
例: ALTER TABLE customer MODIFY name VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_japanese_ci;
翻訳テキストは別テーブルに保存し、言語コードとともに管理すると、多言語対応が容易になります。
適用領域:金融システム、ERP、予約システムなど、データの整合性が重視されるシステム
適用領域:SNS、ログ管理、IoTデータ、リアルタイム分析など、スケーラビリティが重視されるシステム
Amazon Web Servicesが提供するリレーショナルデータベースサービス。MySQL、PostgreSQL、Oracle、SQL Serverなど多数のエンジンをサポート。自動バックアップやスケーリングが容易です。
Microsoftのクラウドデータベースサービス。SQL Serverと高い互換性があり、Microsoft製品との統合が強み。サーバーレスオプションも提供しています。
Google Cloud Platformのマネージドデータベースサービス。MySQL、PostgreSQL、SQL Serverをサポート。BigQueryとの連携が容易で、分析ワークロードに強みがあります。



現代のSQLは従来の機能を超えて進化しています。JSON型によりNoSQLライクな柔軟なデータ構造をリレーショナルDBでも扱えるようになりました。Window関数は集計と非集計を組み合わせた高度な分析が可能です。Common Table Expressions (CTE)は複雑なクエリを読みやすく書けるようにする強力なツールです。
-- 問題1: 以下のテーブルから、部門ごとの平均給与と
-- 全社平均との差額を計算するクエリを作成してください
-- 社員(社員ID, 氏名, 部門ID, 給与)
-- 部門(部門ID, 部門名)
-- 解答例:
WITH 全社平均 AS (
SELECT AVG(給与) AS 平均給与
FROM 社員
)
SELECT
d.部門名,
AVG(s.給与) AS 部門平均給与,
AVG(s.給与) - (SELECT 平均給与 FROM 全社平均) AS 全社平均との差額
FROM
社員 s
JOIN 部門 d ON s.部門ID = d.部門ID
GROUP BY
d.部門ID, d.部門名
ORDER BY
全社平均との差額 DESC;
ある大手ECサイトでは、商品検索と注文処理の遅延が問題となっていました。調査の結果、検索クエリでの不適切なインデックス使用と、注文テーブルの肥大化が原因でした。
対策として、検索頻度の高い列にインデックスを追加し、注文テーブルをアーカイブ用と最新データ用に分割しました。さらに、定期的なメンテナンス処理を自動化。これにより検索速度が5倍、注文処理が3倍高速化しました。
ある銀行のシステムでは、口座間送金処理中にエラーが発生すると、片方の口座だけが更新される問題が起きていました。
解決策として、トランザクション管理を強化し、送金処理を原子的に行うストアドプロシージャを実装。送金履歴テーブルも追加して、不整合発生時に迅速に発見・修正できる仕組みを導入しました。これにより99.999%のデータ整合性を達成しました。
販売データ分析レポートの生成に数時間かかっていた事例です。多数のJOIN操作と集計関数が非効率に使用されていました。
マテリアライズドビュー(事前計算された結果を保存するビュー)を導入し、夜間バッチで更新するように変更。また、集計テーブルを作成して定期的に更新する仕組みも実装しました。結果として、レポート生成時間が数分に短縮されました。
適切なインデックスの作成が最も効果的です。頻繁に検索条件やJOIN条件、ソート条件に使われる列にインデックスを作成しましょう。ただし、更新が多いテーブルでは過剰なインデックスがパフォーマンスを低下させることもあるため注意が必要です。
一般的には、JOINの方がパフォーマンスが良いことが多いです。特にデータ量が多い場合はJOINを優先してください。ただし、EXISTS/NOT EXISTSを使ったサブクエリが効率的なケースもあります。実行計画を確認して決定することをお勧めします。
トランザクションのサイズを適切に管理し、一度に処理するレコード数を制限することが重要です。例えば、100万件のデータを更新する場合、1万件ずつ処理してコミットする方が安全です。また、処理中のロック範囲を最小限に抑えるよう設計しましょう。
パラメータ化クエリ(プリペアドステートメント)を使用することが最も効果的です。ユーザー入力を直接SQLに埋め込まず、プレースホルダを使用してください。また、最小権限の原則に従い、データベースユーザーに必要最小限の権限のみを付与することも重要です。
特定分野での深い専門知識の習得
自主的なプロジェクトで経験を積む
最新技術のキャッチアップ
本研修では、SQLの基礎から実践的なスキルまでを幅広く学びました。今後のステップとして、特定のデータベース製品に特化した学習や、大規模データ分析、データモデリングなどの関連分野の探求をお勧めします。また、実際のプロジェクトでの経験を積むことで、理論と実践を結びつけることができます。
SQL詳細研修:完全ガイド