SQL基本コマンド集
結合・サブクエリ編
5. テーブル結合(JOIN)
INNER JOIN(内部結合)
両方のテーブルに一致するデータのみを取得
-- ユーザーとその注文を取得
SELECT users.name, orders.amount, orders.order_date
FROM users
INNER JOIN orders ON users.id = orders.user_id;
-- テーブル別名を使う(推奨)
SELECT u.name, o.amount, o.order_date
FROM users AS u
INNER JOIN orders AS o ON u.id = o.user_id;
-- JOINとだけ書いてもINNER JOINと同じ
SELECT u.name, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id; LEFT JOIN(左外部結合)
左テーブルの全データと、右テーブルの一致するデータを取得(一致しない場合はNULL)
-- 全ユーザーとその注文(注文がないユーザーも含む)
SELECT u.name, o.amount, o.order_date
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;
-- 注文していないユーザーを抜き出す
SELECT u.name
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.id IS NULL;
-- LEFT OUTER JOINと書いても同じ
SELECT u.name, o.amount
FROM users u
LEFT OUTER JOIN orders o ON u.id = o.user_id; RIGHT JOIN(右外部結合)
右テーブルの全データと、左テーブルの一致するデータを取得
-- 全注文とそのユーザー情報
SELECT u.name, o.amount, o.order_date
FROM users u
RIGHT JOIN orders o ON u.id = o.user_id;
-- 注: LEFT JOINでテーブル順を逆にしても同じ結果
SELECT u.name, o.amount, o.order_date
FROM orders o
LEFT JOIN users u ON o.user_id = u.id; FULL OUTER JOIN(完全外部結合)
両方のテーブルの全データを取得(PostgreSQL、SQL Serverで対応、MySQLは未対応)
-- PostgreSQLの場合
SELECT u.name, o.amount
FROM users u
FULL OUTER JOIN orders o ON u.id = o.user_id;
-- MySQLで代替手段(UNIONを使用)
SELECT u.name, o.amount FROM users u LEFT JOIN orders o ON u.id = o.user_id
UNION
SELECT u.name, o.amount FROM users u RIGHT JOIN orders o ON u.id = o.user_id; CROSS JOIN(クロス結合)
両方のテーブルの全組み合わせ(直積)
-- 全ユーザーと全商品の組み合わせ
SELECT u.name, p.name AS product_name
FROM users u
CROSS JOIN products p;
-- 注意: 結果行数 = usersの行数 × productsの行数 自己結合(Self Join)
同じテーブルを結合する
-- 例: 社員とその上司を表示する
SELECT
e1.name AS employee_name,
e2.name AS manager_name
FROM employees e1
LEFT JOIN employees e2 ON e1.manager_id = e2.id; 複数テーブルの結合
-- 3つのテーブルを結合
SELECT
u.name,
o.order_date,
p.name AS product_name,
p.price
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id; 6. サブクエリ
WHERE句でのサブクエリ
-- 平均価格以上の商品
SELECT * FROM products
WHERE price >= (SELECT AVG(price) FROM products);
-- 注文があるユーザー
SELECT * FROM users
WHERE id IN (SELECT DISTINCT user_id FROM orders);
-- 注文がないユーザー
SELECT * FROM users
WHERE id NOT IN (SELECT DISTINCT user_id FROM orders WHERE user_id IS NOT NULL); FROM句でのサブクエリ(派生テーブル)
-- ユーザー別の注文合計を取得し、それを並び替え
SELECT *
FROM (
SELECT user_id, SUM(amount) AS total_amount
FROM orders
GROUP BY user_id
) AS user_totals
WHERE total_amount >= 10000
ORDER BY total_amount DESC;
-- 必ず別名(AS)が必要 SELECT句でのサブクエリ
-- 各ユーザーの注文合計を並べて表示
SELECT
u.name,
u.email,
(SELECT SUM(amount) FROM orders WHERE user_id = u.id) AS total_amount
FROM users u;
-- 注意: パフォーマンスが悪い場合がJOINを使う方が良い EXISTS / NOT EXISTS
-- 注文があるユーザー
SELECT * FROM users u
WHERE EXISTS (
SELECT 1 FROM orders o WHERE o.user_id = u.id
);
-- 注文がないユーザー
SELECT * FROM users u
WHERE NOT EXISTS (
SELECT 1 FROM orders o WHERE o.user_id = u.id
);
-- INよりEXISTSの方が高速な場合が多い 相関サブクエリ
-- 各カテゴリ内で最高価格の商品
SELECT p1.*
FROM products p1
WHERE p1.price = (
SELECT MAX(p2.price)
FROM products p2
WHERE p2.category = p1.category
); 7. 集合演算
UNION(和集合)
複数のSELECT結果を結合(重複を除く)
-- 2つのテーブルのユーザーを統合
SELECT name, email FROM active_users
UNION
SELECT name, email FROM inactive_users;
-- 注意:
-- - カラム数と型が一致している必要がある
-- - 自動的に重複が除かれる
-- - ORDER BYは最後のクエリの後にのみ記述可能 UNION ALL
重複を含めて結合
-- 重複も含めて全て取得
SELECT name FROM users
UNION ALL
SELECT name FROM customers;
-- UNIONより高速(重複チェックがないため) INTERSECT(積集合)
両方に共通するデータ(MySQLは未対応、PostgreSQL、SQL Serverで対応)
-- PostgreSQLの場合
SELECT email FROM users
INTERSECT
SELECT email FROM customers;
-- MySQLでの代替手段
SELECT DISTINCT u.email
FROM users u
INNER JOIN customers c ON u.email = c.email; EXCEPT / MINUS(差集合)
最初のクエリにはあるが、2番目にはないデータ(MySQLは未対応)
-- PostgreSQLの場合(EXCEPT)
SELECT email FROM users
EXCEPT
SELECT email FROM customers;
-- Oracleの場合(MINUS)
SELECT email FROM users
MINUS
SELECT email FROM customers;
-- MySQLでの代替手段
SELECT u.email
FROM users u
LEFT JOIN customers c ON u.email = c.email
WHERE c.email IS NULL;