0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

関係データベースにおける集合論 🍇

Last updated at Posted at 2025-08-07

はじめに

最近「達人に学ぶ SQL 徹底指南書 第 2 版」を読みました。
この本では、関係データベースや SQL の背景にある集合論や群論などにも触れられています。

学生時代に学んだ数学が関係データベースの基礎になっていることを知り爽快でした。
関係データベースについて数学的な切り口でまとめてみます ✍

1. 関係とは集合である

関係の定義は、次の式によって与えられます。

関係: $R$ , 属性(列): $A_i$ , その定義域: $D_i$

$$R \subseteq D_1 \times D_2 \times D_3 \times \cdots \times D_n$$

つまり、関係は、列の定義域の直積の部分集合です。

ex.

属性 $a_1$, $a_2$, $a_3$ がそれぞれ次の定義域を取る場合を考えます。

$$
d_1 = \{1\}, d_2 = \{'\mathrm{m}', '\mathrm{f}'\}, d_3 = \{'\mathrm{r}', '\mathrm{g}', '\mathrm{b}'\}
$$

このとき、直積は次の通り。

$a_1$ $a_2$ $a_3$
1 m r
1 m g
1 m b
1 f r
1 f g
1 f b

関係(テーブル)は、この部分集合となります。

2. 関係と代数構造

代数構造のうち、次の 3 つについて考えます。

  • 群: 加法・減法について閉じている
  • 環: 加法・減法・乗法について閉じている
  • 体: 加法・減法・乗法・除法について閉じている

関係(テーブル)は、群であり、環であり、体であると言えます。
つまり、テーブル同士を足し算、引き算、掛け算、割り算しても、その結果はテーブルになります。

ex.

加法、減法、乗法について、次のテーブルに対するクエリを例示します。

A

id name
1 太郎
2 花子
3 次郎

B

id name
1 太郎
2 花子
4 三郎

2.1 加法(UNION)

SELECT name FROM A
UNION
SELECT name FROM B;

結果

name
太郎
花子
次郎
三郎

重複を許す場合は UNION ALL を使います。
ALL オプションを付けると、重複削除のための暗黙のソートが行われないため、パフォーマンスが良いです。

2.2 減法(EXCEPT)

SELECT name FROM A
EXCEPT
SELECT name FROM B;

結果

name
次郎

2.3 乗法(CROSS JOIN)

SELECT
  A.name AS a_name,
  B.name AS b_name
FROM
  A CROSS JOIN B;

結果

a_name b_name
太郎 太郎
太郎 花子
太郎 三郎
花子 太郎
花子 花子
花子 三郎
次郎 太郎
次郎 花子
次郎 三郎

2.4 除法

演算子は定義されていないが、次の 3 通りで表現できます。

  • NOT EXISTS を入れ子にする
  • HAVING 句を使う
  • 減法で表現する

ex.

EmployeeSkill テーブルから、RequiredSkill テーブルの技術すべてに精通した社員を選択することを考えます。

RequiredSkill

skill
Oracle
UNIX
Java

EmployeeSkill

emp_id skill
1 Oracle
1 UNIX
1 Java
1 C#
2 Oracle
2 UNIX
2 Java
3 UNIX
3 Oracle
3 PHP
3 Perl
3 C++
4 Perl
5 Oracle

結果

emp_id
1
2

2.4.1 NOT EXISTS を入れ子にする

SELECT emp_id
FROM EmployeeSkill ES1
WHERE NOT EXISTS (
    SELECT skill
    FROM RequiredSkill RS
    WHERE NOT EXISTS (
        SELECT 1
        FROM EmployeeSkill ES2
        WHERE ES2.emp_id = ES1.emp_id
          AND ES2.skill = RS.skill
      )
  );

2.4.2 HAVING 句を使う

SELECT emp_id
FROM EmployeeSkill
WHERE skill IN (SELECT skill FROM RequiredSkill)
GROUP BY emp_id
HAVING
  COUNT(DISTINCT skill) = (
      SELECT COUNT(*) FROM RequiredSkill
    );

ちなみに、パフォーマンスを考慮するのであれば、IN を EXISTS や INNER JOIN に置き換えるとよいです。

IN を EXISTS で置き換える
SELECT emp_id
FROM EmployeeSkill ES
WHERE EXISTS (
    SELECT 1
    FROM RequiredSkill RS
    WHERE RS.skill = ES.skill
  )
GROUP BY emp_id
HAVING
  COUNT(DISTINCT ES.skill) = (
      SELECT COUNT(*) FROM RequiredSkill
    );

メリット

  • 結合キーでインデックスを使える可能性がある
  • 全表検索の必要がない(1 行でも条件を満たす行が存在したらそこで検索を打ち切るため)
IN を INNER JOIN で置き換える
SELECT ES.emp_id
FROM
  EmployeeSkill ES
    INNER JOIN RequiredSkill RS
      ON ES.skill = RS.skill
GROUP BY ES.emp_id
HAVING
  COUNT(DISTINCT ES.skill) = (
      SELECT COUNT(*) FROM RequiredSkill
    );

メリット

  • 結合キーでインデックスを使える可能性がある
  • 中間ビューが作られない(サブクエリがなくなるため)

2.4.3 減法で表現する

SELECT DISTINCT emp_id
FROM EmployeeSkill ES
WHERE NOT EXISTS (
    SELECT skill
    FROM RequiredSkill
    EXCEPT
    SELECT skill
    FROM EmployeeSkill ES2
    WHERE ES2.emp_id = ES.emp_id
  );

3. 結合と集合演算

ex.

各結合について、次のテーブル(再掲)に対するクエリを例示します。

A

id name
1 太郎
2 花子
3 次郎

B

id name
1 太郎
2 花子
4 三郎

3.1 交差結合(CROSS JOIN)

直積(再掲)

SELECT
  A.name AS a_name,
  B.name AS b_name
FROM
  A CROSS JOIN B;

結果

a_name b_name
太郎 太郎
太郎 花子
太郎 三郎
花子 太郎
花子 花子
花子 三郎
次郎 太郎
次郎 花子
次郎 三郎

3.2 内部結合(INNER JOIN)

論理積(INTERSECT)

SELECT
  A.name AS a_name,
  B.name AS b_name
FROM
  A INNER JOIN B
    ON A.id = B.id;

結果

a_name b_name
太郎 太郎
花子 花子

AかつB.png

3.3.1 完全外部結合(FULL OUTER JOIN)

論理和(UNION)

SELECT
  A.name AS a_name,
  B.name AS b_name
FROM
  A FULL OUTER JOIN B
    ON A.id = B.id;

結果

a_name b_name
太郎 太郎
花子 花子
次郎
三郎

AまたはB.png

排他的論理和

SELECT
  COALESCE(A.name, B.name) AS name
FROM
  A FULL OUTER JOIN B
    ON A.id = B.id
WHERE
  A.name IS NULL
  OR B.name IS NULL;

結果

name
次郎
三郎

AでないまたはBでない.png

3.3.2 左外部結合(LEFT OUTER JOIN)

A - B

SELECT
  A.name AS a_name
FROM
  A LEFT OUTER JOIN B
    ON A.id = B.id
WHERE
  B.name IS NULL;

結果

a_name
次郎

AかつBでない.png

3.3.3 右外部結合(RIGHT OUTER JOIN)

B - A

SELECT
  B.name AS B_name
FROM
  A RIGHT OUTER JOIN B
    ON A.id = B.id
WHERE
  A.name IS NULL;

結果

b_name
三郎

AでないかつB.png

おわりに

「達人に学ぶ SQL 徹底指南書 第 2 版」で、著者は次のように述べています。

Theory is Practical.

関係データベースが数学的な理論に厳密であるがゆえに実用的なのはとても美しいと感じました。(読書感想文)

余談

添付したベン図は matplotlib-venn を使って描きました 🎨

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?