SELECT
p.product_name,
s.size_name
FROM (
SELECT ‘Tシャツ’ AS product_name UNION ALL
SELECT ‘パーカー’ UNION ALL
SELECT ‘ジーンズ’
) p
CROSS JOIN (
SELECT ‘S’ AS size_name UNION ALL
SELECT ‘M’ UNION ALL
SELECT ‘L’ UNION ALL
SELECT ‘XL’
) s
ORDER BY p.product_name, s.size_name;
クエリの解説:
UNION ALL → 複数の行を縦に結合(テストデータの作成)
p、s → サブクエリに別名をつける
CROSS JOIN → 全組み合わせを作成
結果: 3商品 × 4サイズ = 12行
結果イメージ:
product_name
size_name
Tシャツ
L
Tシャツ
M
Tシャツ
S
Tシャツ
XL
ジーンズ
L
…
(計12行)
2-3. 例2:カレンダーの作成
やりたいこと:2024年と2025年の全月(24ヶ月分)のリストを作る
※横にスクロールできます
SELECT
y.year,
m.month,
y.year || ‘-‘ || printf(‘%02d’, m.month) AS year_month
FROM (
SELECT 2024 AS year UNION ALL
SELECT 2025
) y
CROSS JOIN (
SELECT 1 AS month UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL
SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL
SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL
SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12
) m
ORDER BY y.year, m.month;
WITH date_range AS (
SELECT DATE(‘now’, ‘-‘ || (n – 1) || ‘ days’) AS date
FROM (
SELECT 1 AS n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL
SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7
)
)
解説:
DATE('now', '-0 days') → 今日
DATE('now', '-1 days') → 昨日
DATE('now', '-6 days') → 6日前
結果: 過去7日間の日付リスト
ステップ2:日付 × 商品の全組み合わせを作る
※横にスクロールできます
all_combinations AS (
SELECT
d.date,
p.product_id,
p.product_name
FROM date_range d
CROSS JOIN products p
)
ステップ3:実際の売上データをLEFT JOINで結合
※横にスクロールできます
SELECT
ac.date,
ac.product_name,
COALESCE(SUM(o.total_amount), 0) AS sales
FROM all_combinations ac
LEFT JOIN orders o
ON DATE(o.order_date) = ac.date
AND o.product_id = ac.product_id
GROUP BY ac.date, ac.product_id, ac.product_name
解説:
LEFT JOIN → 全組み合わせを維持しつつ、売上データを結合
COALESCE(..., 0) → NULLの場合は0に置き換え
完成コード:
※横にスクロールできます
WITH date_range AS (
SELECT DATE(‘now’, ‘-‘ || (n – 1) || ‘ days’) AS date
FROM (
SELECT 1 AS n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL
SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7
)
),
all_combinations AS (
SELECT
d.date,
p.product_id,
p.product_name
FROM date_range d
CROSS JOIN products p
)
SELECT
ac.date AS 日付,
ac.product_name AS 商品名,
COALESCE(SUM(o.total_amount), 0) AS 売上
FROM all_combinations ac
LEFT JOIN orders o
ON DATE(o.order_date) = ac.date
AND o.product_id = ac.product_id
GROUP BY ac.date, ac.product_id, ac.product_name
ORDER BY ac.date DESC, ac.product_name
LIMIT 20;
SELECT
t1.列名 AS 別名1,
t2.列名 AS 別名2
FROM テーブル AS t1
JOIN テーブル AS t2 ON t1.関連列 = t2.キー列;
ポイント:
AS t1、AS t2 → 同じテーブルに別名をつけて区別
別名は必須(つけないとエラーになる)
t1.列名、t2.列名 → どちらのテーブルの列か明示
👥 5. 自己結合の基本例
5-1. 例1:社員と上司の関係を表示
やりたいこと:各社員の名前と、その上司の名前を表示する
※横にスクロールできます
SELECT
e1.employee_name AS 社員名,
e2.employee_name AS 上司名
FROM employees e1
LEFT JOIN employees e2 ON e1.manager_id = e2.employee_id
ORDER BY e1.employee_name;
SELECT
p1.product_name AS 商品1,
p1.price AS 価格1,
p2.product_name AS 商品2,
p2.price AS 価格2,
p1.category AS カテゴリ
FROM products p1
JOIN products p2
ON p1.category = p2.category
AND p1.price < p2.price
ORDER BY p1.category, p1.product_name
LIMIT 10;
クエリの解説:
p1.category = p2.category → 同じカテゴリの商品同士を結合
p1.price < p2.price → p1より高いp2を見つける
結果: 「この商品より高い商品はこれ」というペアのリスト
結果イメージ:
商品1
価格1
商品2
価格2
カテゴリ
バナナ
120
りんご
150
果物
バナナ
120
ぶどう
500
果物
りんご
150
ぶどう
500
果物
バナナ(120円)より高い果物は「りんご」と「ぶどう」の2つあります。
🔢 6. 重複を避ける方法
6-1. 問題:同じペアが2回出てくる
自己結合で「商品のペア」を作ると、同じペアが2回出てくることがあります。
問題のあるクエリ:
※横にスクロールできます
-- 重複が発生する
SELECT
p1.product_name AS 商品1,
p2.product_name AS 商品2
FROM products p1
JOIN products p2
ON p1.category = p2.category
AND p1.product_id != p2.product_id; -- 自分自身は除外
結果:
商品1
商品2
りんご
バナナ
バナナ
りんご
「りんご-バナナ」と「バナナ-りんご」は実質同じペアなのに、両方出てきます。
6-2. 解決策:ID の大小で一方向のみにする
✅ 重複を避けるクエリ:
※横にスクロールできます
SELECT
p1.product_name AS 商品1,
p2.product_name AS 商品2
FROM products p1
JOIN products p2
ON p1.category = p2.category
AND p1.product_id < p2.product_id; -- 小さいID → 大きいID のみ
結果:
商品1
商品2
りんご
バナナ
重複なく1つのペアだけが表示されます!
なぜ < を使うのか:
!= (等しくない) → 両方向のペアが出る(A-B、B-A)
< (より小さい) → 一方向のみ(A-B だけ)
> (より大きい) → 一方向のみ(B-A だけ)
< または > を使うことで、重複なくユニークなペアだけを取得できます。
6-3. 例:同じ都道府県の顧客ペア
やりたいこと:同じ都道府県に住む顧客のペアを表示する(重複なし)
※横にスクロールできます
SELECT
c1.customer_name AS 顧客1,
c2.customer_name AS 顧客2,
c1.prefecture AS 都道府県
FROM customers c1
JOIN customers c2
ON c1.prefecture = c2.prefecture
AND c1.customer_id < c2.customer_id
ORDER BY c1.prefecture, c1.customer_name
LIMIT 20;
SELECT
e1.employee_name AS 社員,
e2.employee_name AS 上司,
e3.employee_name AS 上司の上司
FROM employees e1
LEFT JOIN employees e2 ON e1.manager_id = e2.employee_id
LEFT JOIN employees e3 ON e2.manager_id = e3.employee_id
ORDER BY e1.employee_name;
クエリの解説:
e1 → 社員本人
e2 → 社員の上司(e1.manager_id → e2.employee_id)
e3 → 上司の上司(e2.manager_id → e3.employee_id)
LEFT JOINを2回使って3階層を表現
結果イメージ:
社員
上司
上司の上司
佐藤部長
NULL
NULL
山田課長
佐藤部長
NULL
鈴木主任
山田課長
佐藤部長
田中
鈴木主任
山田課長
7-3. カテゴリの親子関係
やりたいこと:カテゴリと親カテゴリの名前を表示する
カテゴリテーブル(categories)の構造:
category_id
category_name
parent_id
1
食品
NULL(ルート)
2
果物
1(食品)
3
野菜
1(食品)
4
りんご
2(果物)
5
キャベツ
3(野菜)
※横にスクロールできます
SELECT
c1.category_name AS カテゴリ,
c2.category_name AS 親カテゴリ
FROM categories c1
LEFT JOIN categories c2 ON c1.parent_id = c2.category_id
ORDER BY c1.category_id;
結果イメージ:
カテゴリ
親カテゴリ
食品
NULL
果物
食品
野菜
食品
りんご
果物
キャベツ
野菜
7-4. パス(フルパス)の生成
やりたいこと:「食品 > 果物 > りんご」のようなフルパスを作る
※横にスクロールできます
SELECT
c1.category_name,
COALESCE(c3.category_name || ' > ', '') ||
COALESCE(c2.category_name || ' > ', '') ||
c1.category_name AS full_path
FROM categories c1
LEFT JOIN categories c2 ON c1.parent_id = c2.category_id
LEFT JOIN categories c3 ON c2.parent_id = c3.category_id
ORDER BY c1.category_id;
SELECT
p1.product_name AS 商品1,
p1.price AS 価格1,
p2.product_name AS 商品2,
p2.price AS 価格2,
p1.price + p2.price AS セット価格
FROM products p1
CROSS JOIN products p2
WHERE p1.product_id < p2.product_id
ORDER BY セット価格 DESC
LIMIT 10;
クエリの解説:
CROSS JOIN → 全商品の組み合わせを作成
p1.product_id < p2.product_id → 重複を避ける & 自分自身を除外
p1.price + p2.price → 2つの商品の合計価格
結果イメージ(セット価格の高い順):
商品1
価格1
商品2
価格2
セット価格
ぶどう
500
いちご
800
1,300
ぶどう
500
メロン
1,000
1,500
8-2. 数字の組み合わせ
やりたいこと:1から5までの数字で、異なる2つの数字の組み合わせを作る
※横にスクロールできます
SELECT
n1.num AS 数字1,
n2.num AS 数字2,
n1.num + n2.num AS 合計,
n1.num * n2.num AS 積
FROM (
SELECT 1 AS num UNION ALL SELECT 2 UNION ALL SELECT 3
UNION ALL SELECT 4 UNION ALL SELECT 5
) n1
CROSS JOIN (
SELECT 1 AS num UNION ALL SELECT 2 UNION ALL SELECT 3
UNION ALL SELECT 4 UNION ALL SELECT 5
) n2
WHERE n1.num < n2.num
ORDER BY n1.num, n2.num;
結果イメージ:
数字1
数字2
合計
積
1
2
3
2
1
3
4
3
1
4
5
4
...
...
...
(計10組)
5個から2個選ぶ組み合わせは 5C2 = 10通りです。
💼 9. 実務での活用例
9-1. 共通購入商品の分析(協調フィルタリングの基礎)
やりたいこと:同じ商品を買った顧客のペアと、共通商品数を計算する
これは「この商品を買った人はこの商品も買っています」という推薦システムの基礎になります。
※横にスクロールできます
SELECT
c1.customer_name AS 顧客1,
c2.customer_name AS 顧客2,
COUNT(DISTINCT o1.product_id) AS 共通商品数
FROM orders o1
JOIN orders o2
ON o1.product_id = o2.product_id
AND o1.customer_id < o2.customer_id
JOIN customers c1 ON o1.customer_id = c1.customer_id
JOIN customers c2 ON o2.customer_id = c2.customer_id
GROUP BY c1.customer_id, c2.customer_id, c1.customer_name, c2.customer_name
HAVING COUNT(DISTINCT o1.product_id) >= 2
ORDER BY 共通商品数 DESC
LIMIT 10;
クエリの解説:
o1.product_id = o2.product_id → 同じ商品を買った注文を結合
o1.customer_id < o2.customer_id → 顧客ペアの重複を避ける
COUNT(DISTINCT o1.product_id) → 共通して買った商品の数
HAVING ... >= 2 → 共通商品が2つ以上のペアだけ表示
結果イメージ:
顧客1
顧客2
共通商品数
山田太郎
鈴木花子
5
田中次郎
佐藤美咲
4
共通商品が多い顧客は、購買傾向が似ている可能性があります。
9-2. 月次ランキングの比較
やりたいこと:今月と先月の売上ランキングを比較し、順位変動を計算する
※横にスクロールできます
WITH monthly_ranking AS (
SELECT
strftime('%Y-%m', order_date) AS month,
product_id,
SUM(total_amount) AS sales,
RANK() OVER (
PARTITION BY strftime('%Y-%m', order_date)
ORDER BY SUM(total_amount) DESC
) AS ranking
FROM orders
GROUP BY strftime('%Y-%m', order_date), product_id
)
SELECT
p.product_name AS 商品名,
m1.sales AS 今月売上,
m1.ranking AS 今月ランク,
m2.sales AS 先月売上,
m2.ranking AS 先月ランク,
COALESCE(m2.ranking, 0) - COALESCE(m1.ranking, 0) AS ランク変動
FROM products p
LEFT JOIN monthly_ranking m1
ON p.product_id = m1.product_id
AND m1.month = '2024-02'
LEFT JOIN monthly_ranking m2
ON p.product_id = m2.product_id
AND m2.month = '2024-01'
WHERE m1.ranking IS NOT NULL OR m2.ranking IS NOT NULL
ORDER BY m1.ranking;
SELECT
e.employee_name AS 社員,
r.room_name AS 会議室,
t.time_slot AS 時間帯
FROM employees e
CROSS JOIN (
SELECT '会議室A' AS room_name UNION ALL
SELECT '会議室B' UNION ALL
SELECT '会議室C'
) r
CROSS JOIN (
SELECT '09:00-10:00' AS time_slot UNION ALL
SELECT '10:00-11:00' UNION ALL
SELECT '11:00-12:00' UNION ALL
SELECT '13:00-14:00' UNION ALL
SELECT '14:00-15:00'
) t
ORDER BY e.employee_name, t.time_slot, r.room_name
LIMIT 30;
SELECT
p.product_name AS 商品名,
c.color AS カラー
FROM products p
CROSS JOIN (
SELECT '赤' AS color UNION ALL
SELECT '青' UNION ALL
SELECT '緑'
) c
ORDER BY p.product_name, c.color
LIMIT 15;
解説:
CROSS JOINで全商品 × 全カラーの組み合わせを作成
商品数 × 3色 = 結果行数
問題 2基本
自己結合の基本
商品テーブルを自己結合して、価格が同じ商品のペアを表示してください。(同じ商品同士は除外)
※横にスクロールできます
SELECT
p1.product_name AS 商品1,
p2.product_name AS 商品2,
p1.price AS 価格
FROM products p1
JOIN products p2
ON p1.price = p2.price
AND p1.product_id < p2.product_id
ORDER BY p1.price, p1.product_name;
解説:
p1.price = p2.price → 同じ価格の商品を結合
p1.product_id < p2.product_id → 重複を避ける
問題 3基本
カテゴリが同じ商品
同じカテゴリに属する商品のペアを表示してください。(自分自身は除外、重複なし)
※横にスクロールできます
SELECT
p1.product_name AS 商品1,
p2.product_name AS 商品2,
p1.category AS カテゴリ
FROM products p1
JOIN products p2
ON p1.category = p2.category
AND p1.product_id < p2.product_id
ORDER BY p1.category, p1.product_name
LIMIT 20;
解説:
同じカテゴリで結合
< で重複を避ける
問題 4応用
価格差の計算
同じカテゴリ内で、価格が高い商品と安い商品のペアを作り、価格差を表示してください。
※横にスクロールできます
SELECT
p1.product_name AS 安い商品,
p1.price AS 安い価格,
p2.product_name AS 高い商品,
p2.price AS 高い価格,
p2.price - p1.price AS 価格差
FROM products p1
JOIN products p2
ON p1.category = p2.category
AND p1.price < p2.price
ORDER BY p1.category, 価格差 DESC
LIMIT 10;
SELECT
p1.product_name AS 商品1,
p1.price AS 価格1,
p2.product_name AS 商品2,
p2.price AS 価格2,
p1.price + p2.price AS セット価格
FROM products p1
CROSS JOIN products p2
WHERE p1.product_id < p2.product_id
ORDER BY セット価格 DESC
LIMIT 10;
解説:
CROSS JOINで全組み合わせを作成
WHEREで重複を避ける
価格を合計してセット価格を計算
問題 6応用
顧客の都道府県別ペア
同じ都道府県に住む顧客のペアを表示してください。(重複なし)
※横にスクロールできます
SELECT
c1.customer_name AS 顧客1,
c2.customer_name AS 顧客2,
c1.prefecture AS 都道府県
FROM customers c1
JOIN customers c2
ON c1.prefecture = c2.prefecture
AND c1.customer_id < c2.customer_id
ORDER BY c1.prefecture, c1.customer_name
LIMIT 20;
解説:
同じ都道府県で結合
customer_id < で重複を避ける
問題 7チャレンジ
数字の組み合わせ
1から5までの数字で、異なる2つの数字の組み合わせと、その合計を表示してください。
※横にスクロールできます
SELECT
n1.num AS 数字1,
n2.num AS 数字2,
n1.num + n2.num AS 合計
FROM (
SELECT 1 AS num UNION ALL SELECT 2 UNION ALL SELECT 3
UNION ALL SELECT 4 UNION ALL SELECT 5
) n1
CROSS JOIN (
SELECT 1 AS num UNION ALL SELECT 2 UNION ALL SELECT 3
UNION ALL SELECT 4 UNION ALL SELECT 5
) n2
WHERE n1.num < n2.num
ORDER BY n1.num, n2.num;
解説:
UNION ALLで数字のテーブルを作成
CROSS JOINで組み合わせ
n1.num < n2.num で重複を避ける
問題 8チャレンジ
7日間のカレンダー作成
2024年1月1日から7日間の日付リストを作成してください。
※横にスクロールできます
SELECT DATE('2024-01-01', '+' || (n - 1) || ' days') AS 日付
FROM (
SELECT 1 AS n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL
SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7
) numbers
ORDER BY 日付;
SELECT
c1.customer_name AS 顧客1,
c2.customer_name AS 顧客2,
COUNT(DISTINCT o1.product_id) AS 共通商品数
FROM orders o1
JOIN orders o2
ON o1.product_id = o2.product_id
AND o1.customer_id < o2.customer_id
JOIN customers c1 ON o1.customer_id = c1.customer_id
JOIN customers c2 ON o2.customer_id = c2.customer_id
GROUP BY c1.customer_id, c2.customer_id, c1.customer_name, c2.customer_name
HAVING COUNT(DISTINCT o1.product_id) >= 2
ORDER BY 共通商品数 DESC
LIMIT 10;
WITH date_range AS (
SELECT DATE('now', '-' || (n - 1) || ' days') AS date
FROM (
SELECT 1 AS n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL
SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7
)
)
SELECT
d.date AS 日付,
p.product_name AS 商品名,
COALESCE(SUM(o.total_amount), 0) AS 売上
FROM date_range d
CROSS JOIN products p
LEFT JOIN orders o
ON DATE(o.order_date) = d.date
AND o.product_id = p.product_id
GROUP BY d.date, p.product_id, p.product_name
ORDER BY d.date DESC, p.product_name
LIMIT 30;
解説:
CTEで日付リストを作成
CROSS JOINで日付 × 商品の全組み合わせ
LEFT JOINで実際の売上を結合
COALESCEでNULLを0に変換
📝 Step 10 のまとめ
✅ 学んだこと
CROSS JOIN:2つのテーブルの全組み合わせを作る
自己結合:同じテーブルを自分自身と結合
別名(AS):自己結合では必須(t1, t2 など)
重複を避ける:t1.id < t2.id で一方向のみ
階層構造:組織図、カテゴリツリーの表現
欠損データの補完:CROSS JOIN + LEFT JOIN
📌 重要な構文パターン
※横にスクロールできます
-- CROSS JOIN: 全組み合わせ
SELECT * FROM テーブル1 CROSS JOIN テーブル2;
-- 結果行数 = テーブル1の行数 × テーブル2の行数
-- 自己結合: 同じテーブルを2回使う
SELECT *
FROM テーブル AS t1
JOIN テーブル AS t2 ON t1.列 = t2.列;
-- 別名(AS)は必須!
-- 重複を避ける
WHERE t1.id < t2.id -- 一方向のみに限定
-- 階層構造(親子関係)
SELECT 子.名前, 親.名前 AS 親の名前
FROM テーブル AS 子
LEFT JOIN テーブル AS 親 ON 子.親ID = 親.ID;
-- 欠損データの補完
SELECT * FROM マスター
CROSS JOIN 日付リスト
LEFT JOIN 実データ ON ...