データベースと SQL 基礎

表形式でデータを管理する考え方を学び、SELECT・WHERE・JOIN・集計という SQL の基本操作を実際に読み書きできるようになる。

この単元の目標
  • リレーショナルモデルにおけるテーブル・行・列・主キーの役割を説明できる
  • SELECT / WHERE / ORDER BY / GROUP BY を組み合わせた問い合わせを書ける
  • 正規化の目的と、JOIN による複数テーブルの結合を説明できる
📑 目次
  1. リレーショナルモデル——表で世界を表す
  2. SQL——データに語りかける言葉
  3. 正規化——重複を排除する設計

リレーショナルモデル——表で世界を表す

リレーショナルデータベース(RDB)は、データを行(レコード)と列(カラム)からなるテーブルで管理します。「学生テーブル」と「受講テーブル」を分け、必要なときに結合するのが基本設計です。行を一意に識別する候補キーから選んだものが主キー(primary key)で、複数列でも構成できます。外部キー(foreign key)は主キーや一意制約のあるキーを参照する列または列の組です。同じテーブルを参照する場合もあります。

用語意味例
テーブル(関係)同じ種類のデータの集合students
行(タプル)1件分のデータ「1番, 田中, 2年」
列(属性)データの項目id, name, grade
主キー行を一意に決める列または列の組(重複・NULL 禁止)id
外部キー主キーや一意制約のあるキーを参照する列または列の組student_id

SQL——データに語りかける言葉

SQL(Structured Query Language)はデータベースへの命令文です。読み取りは SELECT … FROM … WHERE が基本形で、並べ替えは ORDER BY、集計は GROUP BY と COUNT・AVG などの集約関数を使います。宣言型言語であることが特徴で、「どう取得するか」ではなく「何がほしいか」を書くと、実行計画はデータベース側が最適化します。

SELECT→FROM→WHERE→GROUP BY→HAVING→ORDER BY\text{SELECT} \to \text{FROM} \to \text{WHERE} \to \text{GROUP BY} \to \text{HAVING} \to \text{ORDER BY}

上は句を書く順序です。基本的な問い合わせの論理的な処理は FROM / JOIN → WHERE → GROUP BY → HAVING → SELECT → ORDER BY と考えます(実際の実行計画とは別)。WHERE は集計前の行、HAVING は集計後のグループを絞ります。GROUP BY がなくても COUNT(*) や AVG(point) で全体を1グループとして集計できます。

例題

students(id, name, grade) と scores(student_id, subject, point) から「2 年生の科目別平均点」を求める SQL を書け。

解答・解説

**SELECT s.subject, AVG(s.point) FROM scores s JOIN students st ON st.id = s.student_id WHERE st.grade = 2 GROUP BY s.subject;**(WHERE で行を絞り、GROUP BY で科目ごとにまとめて平均を取る)

🔍 JOIN の結果の行数——なぜ結合キーで行数が決まるのか

  1. Step 1: 素朴な組み合わせ(クロス結合)
    ∣A×B∣=∣A∣⋅∣B∣|A \times B| = |A| \cdot |B|

    mm 行と nn 行のテーブルを無条件に組み合わせると全ペア mnm n 行になります。

  2. Step 2: 条件で絞り込む(内部結合)
    ∣A⋈B∣=∑a∈Adeg⁡B(a.key)|A \bowtie B| = \sum_{a \in A} \deg_B(a.key)

    内部結合は「キーが一致するペアだけ」を残します。各行ごとの一致数(度数)の総和が結果の行数です。

  3. Step 3: 主キー結合なら高々 m 行
    B.key is PK  ⟹  ∣A⋈B∣≤∣A∣B.key \text{ is PK} \implies |A \bowtie B| \leq |A|

    主キーは重複しないので、A の各行に対する一致は最大 1 個。行数が爆発しないのが主キー設計の効能です。

  4. Step 4: 実務上の教訓
    nested loop:O(mn),indexed lookup:O(mlog⁡n+k)\text{nested loop}: O(mn), \quad \text{indexed lookup}: O(m \log n + k)

    単純な二重ループ結合では全ペアを比較します。B木索引を使って各行から探す方式は、一致結果 k 行の出力も含めて目安 O(m log n + k)。索引なしでも等値結合ではハッシュ結合を使い、適切な条件下で期待 O(m + n + k) にできるので、索引の有無だけで速度は決まりません。

正規化——重複を排除する設計

1枚の巨大な表にすべてを書き込むと、同じ情報が繰り返され、更新時の矛盾(更新異常)が起きます。正規化は関数従属性(X の値が決まると Y の値も決まる関係 X → Y)などを調べ、冗長性を抑える設計です。単に分割すればよいのではなく、結合で元の情報を正しく復元できることも確認します。主キーだけでなく、行を一意に識別できる最小の列の組である候補キーすべてを考えます。

  • 第1正規形(1NF): 1つのセルに1つの値。繰り返し列を禁止
  • 第2正規形(2NF): 1NF を満たし、どの候補キーにも含まれない属性が、候補キーの一部分だけに依存しない
  • 第3正規形(3NF): 非自明な関数従属性 X → A ごとに、X がスーパーキー(行を一意に決める列の組)であるか、A がいずれかの候補キーに含まれる。典型的には非キー属性への推移的な依存を分離する
操作系 SQL役割例
SELECT読み取りSELECT * FROM t WHERE id = 1
INSERT追加INSERT INTO t VALUES (...);
UPDATE更新UPDATE t SET name = 'Suzuki' WHERE id = 2
DELETE削除DELETE FROM t WHERE id = 2
WHERE 句を忘れた UPDATE や DELETE はテーブル全体が対象になります。まず同じ条件の SELECT で対象を確認し、バックアップ・権限・トランザクション対応を確認します。トランザクション内で件数などを検証し、問題があれば COMMIT 前に ROLLBACK します。BEGIN だけで事故が防げるわけではなく、DB 製品や設定による違いにも注意します。
練習問題
  1. scores(subject, point) から point が 80 以上の行だけを取り出す SQL を書け。

    ヒント

    SELECT … FROM … WHERE。

    解答・解説

    **SELECT * FROM scores WHERE point >= 80;**

  2. students テーブルの行数を数える SQL を書け。

    解答・解説

    **SELECT COUNT(*) FROM students;**

  3. 「受講履歴」を students に埋め込むと更新異常が起きる理由を述べよ。

    ヒント

    同じ学生名が何行も繰り返される状況を想像する。

    解答・解説

    氏名などの属性が受講件数だけ繰り返し保存され、変更時に全行を直さず矛盾が残る(更新異常)。学生情報と受講記録を**テーブル分割(正規化)+ 外部キー**で分離すれば防げる

  4. 1000 行 × 1000 行のテーブルを、すべてのペアを調べる単純な二重ループで JOIN する場合、結合条件の評価回数はいくつか。

    解答・解説

    1000×1000=1061000 \times 1000=10^6 回。これは指定した方式の回数であり、索引がない JOIN が必ずこの方式になるわけではない。ハッシュ結合など別の実行計画もある。

確認クイズ(DB・SQL)

問1. テーブルの行を一意に特定する列はどれか。

問2. 科目ごとなど、値が同じ行をグループに分けて集計する句はどれか。

問3. 候補キーが学生IDだけの表で、学生ID → 学科ID → 学科名という依存がある。第3正規形にする典型的な対応は?

📝 メモこの端末のみに保存
メモ欄はページ読み込み後に使えるようになります。