< 前の記事
楽観ロック・悲観ロック
[MariaDB]テーブル結合(CROSS JOIN、INNER JOIN、LEFT JOIN)
公開日:2026-01-18
更新日:2026-01-18
更新日:2026-01-18
1. 概要
テーブル結合(CROSS JOIN、INNER JOIN、LEFT JOIN)についてです。
記事テーブル、タグテーブル、記事とタグの関連付けを行う中間テーブルを使って、テーブル結合を行います。
記事テーブル、タグテーブル、記事とタグの関連付けを行う中間テーブルを使って、テーブル結合を行います。
2. 動作確認
2.1 テストデータの作成
データベースとテーブルの作成
SQL
CREATE DATABASE test_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
# 記事テーブル
CREATE TABLE articles (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL
);
# タグテーブル
CREATE TABLE tags (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL
);
# 中間テーブル(記事とタグの関連付け)
CREATE TABLE article_tag (
article_id BIGINT NOT NULL,
tag_id BIGINT NOT NULL,
PRIMARY KEY (article_id, tag_id)
);
# 記事
insert into articles (id, title, content) values
(1, 'Linux1', '内容1'),
(2, 'Linux2', '内容2'),
(3, 'Linux3', '内容3'),
(4, 'PHP1', '内容4'),
(5, 'PHP2', '内容5');
# タグ
insert into tags (id, name) values
(1, 'IT'),
(2, 'Linux'),
(3, 'PHP');
# 中間テーブル(記事とタグの関連付け)
insert into article_tag (article_id, tag_id) values
(1, 1), # Linux1 - IT
(1, 2), # Linux1 - Linux
(2, 1), # Linux2 - IT
(2, 2), # Linux2 - Linux
(3, 1), # Linux3 - IT
(3, 2), # Linux3 - Linux
(4, 1), # PHP1 - IT
(4, 3), # PHP1 - PHP
(5, 1), # PHP2 - IT
(5, 3); # PHP2 - PHP2.2 クロス結合
テーブル1の全てのレコードの集合と、テーブル2の全てのレコードの集合の、全ての組み合わせで結合することをクロス結合と呼び、結果のことをデカルト積と呼びます。
単純にレコード同士を組み合わせるため、整合性が取れないデータも含まれます。
ちなみに、クロス結合に対して検索条件を指定して、結果がデカルト積にならない場合は、クロス結合とは呼びません。実行自体は可能です。
単純にレコード同士を組み合わせるため、整合性が取れないデータも含まれます。
ちなみに、クロス結合に対して検索条件を指定して、結果がデカルト積にならない場合は、クロス結合とは呼びません。実行自体は可能です。
SQL
SELECT a.*, at.* FROM articles a CROSS JOIN article_tag at;
または
SELECT a.*, at.* FROM articles a, article_tag at;
実行結果
+----+--------+---------+------------+--------+
| id | title | content | article_id | tag_id |
+----+--------+---------+------------+--------+
| 1 | Linux1 | 内容1 | 1 | 1 |
| 2 | Linux2 | 内容2 | 1 | 1 |
| 3 | Linux3 | 内容3 | 1 | 1 |
| 4 | PHP1 | 内容4 | 1 | 1 |
| 5 | PHP2 | 内容5 | 1 | 1 |
| 1 | Linux1 | 内容1 | 1 | 2 |
| 2 | Linux2 | 内容2 | 1 | 2 |
| 3 | Linux3 | 内容3 | 1 | 2 |
| 4 | PHP1 | 内容4 | 1 | 2 |
| 5 | PHP2 | 内容5 | 1 | 2 |
| 1 | Linux1 | 内容1 | 2 | 1 |
| 2 | Linux2 | 内容2 | 2 | 1 |
| 3 | Linux3 | 内容3 | 2 | 1 |
| 4 | PHP1 | 内容4 | 2 | 1 |
| 5 | PHP2 | 内容5 | 2 | 1 |
| 1 | Linux1 | 内容1 | 2 | 2 |
| 2 | Linux2 | 内容2 | 2 | 2 |
| 3 | Linux3 | 内容3 | 2 | 2 |
| 4 | PHP1 | 内容4 | 2 | 2 |
| 5 | PHP2 | 内容5 | 2 | 2 |
| 1 | Linux1 | 内容1 | 3 | 1 |
| 2 | Linux2 | 内容2 | 3 | 1 |
| 3 | Linux3 | 内容3 | 3 | 1 |
| 4 | PHP1 | 内容4 | 3 | 1 |
| 5 | PHP2 | 内容5 | 3 | 1 |
| 1 | Linux1 | 内容1 | 3 | 2 |
| 2 | Linux2 | 内容2 | 3 | 2 |
| 3 | Linux3 | 内容3 | 3 | 2 |
| 4 | PHP1 | 内容4 | 3 | 2 |
| 5 | PHP2 | 内容5 | 3 | 2 |
| 1 | Linux1 | 内容1 | 4 | 1 |
| 2 | Linux2 | 内容2 | 4 | 1 |
| 3 | Linux3 | 内容3 | 4 | 1 |
| 4 | PHP1 | 内容4 | 4 | 1 |
| 5 | PHP2 | 内容5 | 4 | 1 |
| 1 | Linux1 | 内容1 | 4 | 3 |
| 2 | Linux2 | 内容2 | 4 | 3 |
| 3 | Linux3 | 内容3 | 4 | 3 |
| 4 | PHP1 | 内容4 | 4 | 3 |
| 5 | PHP2 | 内容5 | 4 | 3 |
| 1 | Linux1 | 内容1 | 5 | 1 |
| 2 | Linux2 | 内容2 | 5 | 1 |
| 3 | Linux3 | 内容3 | 5 | 1 |
| 4 | PHP1 | 内容4 | 5 | 1 |
| 5 | PHP2 | 内容5 | 5 | 1 |
| 1 | Linux1 | 内容1 | 5 | 3 |
| 2 | Linux2 | 内容2 | 5 | 3 |
| 3 | Linux3 | 内容3 | 5 | 3 |
| 4 | PHP1 | 内容4 | 5 | 3 |
| 5 | PHP2 | 内容5 | 5 | 3 |
+----+--------+---------+------------+--------+
単純な組み合わせのため、articles の id と article_tag が article_id が異なる行が存在します。2.3 INNER JOIN
イメージ的には、クロス結合の結果に対して、ON の後ろの結合条件でフィルタリングしたものが INNER JOIN です。
実際にはクロス結合は行われず、最適化して処理されます。
articles と article_tag を、id と article_id で結合する。
→articles と article_tag をクロス結合した結果から、id と article_id が同じ行を取得する。
ON の後ろの結合条件の「キー = キー」は、構文ではなく、あくまでもフィルタリングの条件のため、次のような条件を書くこともできます。
同様に、下記のように WHERE にテーブル結合の条件を書くこともできますが、結合条件なのか検索条件なのかかがわかりづらくなるため、ON と WHERE を使い分けて書きます。
実際にはクロス結合は行われず、最適化して処理されます。
articles と article_tag を、id と article_id で結合する。
→articles と article_tag をクロス結合した結果から、id と article_id が同じ行を取得する。
SQL
SELECT a.*, at.*
FROM articles a
INNER JOIN article_tag at ON a.id = at.article_id;
実行結果
+----+--------+---------+------------+--------+
| id | title | content | article_id | tag_id |
+----+--------+---------+------------+--------+
| 1 | Linux1 | 内容1 | 1 | 1 |
| 1 | Linux1 | 内容1 | 1 | 2 |
| 2 | Linux2 | 内容2 | 2 | 1 |
| 2 | Linux2 | 内容2 | 2 | 2 |
| 3 | Linux3 | 内容3 | 3 | 1 |
| 3 | Linux3 | 内容3 | 3 | 2 |
| 4 | PHP1 | 内容4 | 4 | 1 |
| 4 | PHP1 | 内容4 | 4 | 3 |
| 5 | PHP2 | 内容5 | 5 | 1 |
| 5 | PHP2 | 内容5 | 5 | 3 |
+----+--------+---------+------------+--------+
ON の後ろの結合条件の「キー = キー」は、構文ではなく、あくまでもフィルタリングの条件のため、次のような条件を書くこともできます。
SQL
SELECT a.*, at.*
FROM articles a
INNER JOIN article_tag at ON a.id = at.article_id AND a.id >= 3;
実行結果
+----+--------+---------+------------+--------+
| id | title | content | article_id | tag_id |
+----+--------+---------+------------+--------+
| 3 | Linux3 | 内容3 | 3 | 1 |
| 3 | Linux3 | 内容3 | 3 | 2 |
| 4 | PHP1 | 内容4 | 4 | 1 |
| 4 | PHP1 | 内容4 | 4 | 3 |
| 5 | PHP2 | 内容5 | 5 | 1 |
| 5 | PHP2 | 内容5 | 5 | 3 |
+----+--------+---------+------------+--------+
一応上記のように書くこともできますが、通常は、テーブル結合の条件は、ON の後ろに書き、検索条件は WHERE に書きます。同様に、下記のように WHERE にテーブル結合の条件を書くこともできますが、結合条件なのか検索条件なのかかがわかりづらくなるため、ON と WHERE を使い分けて書きます。
SQL
SELECT a.*, at.*
FROM articles a, article_tag at
WHERE a.id = at.article_id AND a.id >= 3;2.4 タグが「PHP」の記事一覧の取得
記事テーブルを中間テーブルに結合して、
記事に割り当てられているタグIDを参照できるようにする。
次に、この結果とタグテーブルを結合して、タグ名を参照できるようにする。
最後に、タグ名を検索条件に追加すると、タグが「PHP」の記事一覧が取得できる。
また、IN を使って書くこともできます。
わかりやすいが、INNER JOIN に比べるとレスポンスが悪い。データ量が少ない場合は問題ない。
記事に割り当てられているタグIDを参照できるようにする。
SQL
SELECT a.*, at.tag_id
FROM articles a
INNER JOIN article_tag at ON a.id = at.article_id;
実行結果
+----+--------+---------+--------+
| id | title | content | tag_id |
+----+--------+---------+--------+
| 1 | Linux1 | 内容1 | 1 |
| 1 | Linux1 | 内容1 | 2 |
| 2 | Linux2 | 内容2 | 1 |
| 2 | Linux2 | 内容2 | 2 |
| 3 | Linux3 | 内容3 | 1 |
| 3 | Linux3 | 内容3 | 2 |
| 4 | PHP1 | 内容4 | 1 |
| 4 | PHP1 | 内容4 | 3 |
| 5 | PHP2 | 内容5 | 1 |
| 5 | PHP2 | 内容5 | 3 |
+----+--------+---------+--------+
次に、この結果とタグテーブルを結合して、タグ名を参照できるようにする。
SQL
SELECT a.*, at.tag_id, t.name
FROM articles a
INNER JOIN article_tag at ON a.id = at.article_id
INNER JOIN tags t ON t.id = at.tag_id;
実行結果
+----+--------+---------+--------+-------+
| id | title | content | tag_id | name |
+----+--------+---------+--------+-------+
| 1 | Linux1 | 内容1 | 1 | IT |
| 1 | Linux1 | 内容1 | 2 | Linux |
| 2 | Linux2 | 内容2 | 1 | IT |
| 2 | Linux2 | 内容2 | 2 | Linux |
| 3 | Linux3 | 内容3 | 1 | IT |
| 3 | Linux3 | 内容3 | 2 | Linux |
| 4 | PHP1 | 内容4 | 1 | IT |
| 4 | PHP1 | 内容4 | 3 | PHP |
| 5 | PHP2 | 内容5 | 1 | IT |
| 5 | PHP2 | 内容5 | 3 | PHP |
+----+--------+---------+--------+-------+
最後に、タグ名を検索条件に追加すると、タグが「PHP」の記事一覧が取得できる。
SQL
SELECT a.*, at.tag_id, t.name
FROM articles a
INNER JOIN article_tag at ON a.id = at.article_id
INNER JOIN tags t ON t.id = at.tag_id
WHERE t.name = 'PHP';
実行結果
+----+-------+---------+--------+------+
| id | title | content | tag_id | name |
+----+-------+---------+--------+------+
| 4 | PHP1 | 内容4 | 3 | PHP |
| 5 | PHP2 | 内容5 | 3 | PHP |
+----+-------+---------+--------+------+
また、IN を使って書くこともできます。
わかりやすいが、INNER JOIN に比べるとレスポンスが悪い。データ量が少ない場合は問題ない。
SQL
SELECT *
FROM articles
WHERE articles.id IN (
SELECT article_id
FROM article_tag
WHERE tag_id IN (
SELECT tags.id
FROM tags
WHERE NAME='PHP'
)
);
実行結果
+----+-------+---------+
| id | title | content |
+----+-------+---------+
| 4 | PHP1 | 内容4 |
| 5 | PHP2 | 内容5 |
+----+-------+---------+2.5 LEFT JOIN
中間テーブルのレコードがない articles のレコードの追加します。
この状態で articles に article_tag を、id と tag_id を結合条件にして LEFT JOIN すると、articles に対応する article_tag がない行も取得される。
INNER JOIN の場合は、articles に対応する article_tag がない場合は取得されない。
また、articles がなくて、article_tag だけがある行も取得されない。articles が LEFT JOIN で結合されるテーブルとなるため。
SQL
insert into articles (id, title, content) values (6, 'タグなし', '内容6');
この状態で articles に article_tag を、id と tag_id を結合条件にして LEFT JOIN すると、articles に対応する article_tag がない行も取得される。
INNER JOIN の場合は、articles に対応する article_tag がない場合は取得されない。
また、articles がなくて、article_tag だけがある行も取得されない。articles が LEFT JOIN で結合されるテーブルとなるため。
SQL
SELECT a.*, at.tag_id
FROM articles a
LEFT JOIN article_tag at ON a.id = at.article_id;
実行結果
+----+--------------+---------+--------+
| id | title | content | tag_id |
+----+--------------+---------+--------+
| 1 | Linux1 | 内容1 | 1 |
| 1 | Linux1 | 内容1 | 2 |
| 2 | Linux2 | 内容2 | 1 |
| 2 | Linux2 | 内容2 | 2 |
| 3 | Linux3 | 内容3 | 1 |
| 3 | Linux3 | 内容3 | 2 |
| 4 | PHP1 | 内容4 | 1 |
| 4 | PHP1 | 内容4 | 3 |
| 5 | PHP2 | 内容5 | 1 |
| 5 | PHP2 | 内容5 | 3 |
| 6 | タグなし | 内容6 | NULL |
+----+--------------+---------+--------+
< 前の記事
楽観ロック・悲観ロック

