TS-Tech
DB/SQL

サブクエリと EXISTS で条件を組み立てる

3
  • SQL

サブクエリと EXISTS で条件を組み立てる

JOIN だけでも多くのことはできますが、「一覧のうち、注文がある人だけ」「平均より高い商品」などはサブクエリの方が読みやすいことがあります。products · customers · orders を用意して進めます。所要は 25〜35 分。


目次

  1. 準備
  2. スカラーサブクエリ
  3. IN サブクエリ
  4. EXISTS
  5. JOIN との使い分け
  6. よくあるつまずき

準備

cd $DB_SQL/PostgreSQL_setupdocker compose up -ddocker compose exec db psql -U practice -d practice_db
CREATE TABLE IF NOT EXISTS products (  id SERIAL PRIMARY KEY,  name TEXT NOT NULL,  category TEXT NOT NULL,  price INT NOT NULL);CREATE TABLE IF NOT EXISTS customers (  id SERIAL PRIMARY KEY,  name TEXT NOT NULL,  email TEXT NOT NULL UNIQUE);CREATE TABLE IF NOT EXISTS orders (  id SERIAL PRIMARY KEY,  customer_id INT NOT NULL REFERENCES customers(id),  product_id INT NOT NULL REFERENCES products(id),  qty INT NOT NULL CHECK (qty > 0),  ordered_at DATE NOT NULL DEFAULT CURRENT_DATE);TRUNCATE orders, customers, products RESTART IDENTITY;INSERT INTO products (name, category, price) VALUES  ('ノート A5', 'stationery', 300),  ('ボールペン', 'stationery', 120),  ('USB-C ケーブル', 'gadget', 980),  ('ワイヤレスマウス', 'gadget', 2500);INSERT INTO customers (name, email) VALUES  ('山田', 'yamada@example.com'),  ('佐藤', 'sato@example.com'),  ('鈴木', 'suzuki@example.com');INSERT INTO orders (customer_id, product_id, qty, ordered_at) VALUES  (1, 1, 2, '2026-08-01'),  (1, 3, 1, '2026-08-02'),  (2, 2, 5, '2026-08-03'),  (2, 4, 1, '2026-08-03');

確認:

SELECT COUNT(*) FROM orders;SELECT AVG(price) FROM products;

スカラーサブクエリ

1 行 1 列を返すサブクエリです。平均より高い商品:

SELECT name, priceFROM productsWHERE price > (SELECT AVG(price) FROM products)ORDER BY price;

括弧の中が先に評価され、その結果と比較されます。複数行を返す書き方をここに置くとエラーになります。


IN サブクエリ

注文されたことがある商品:

SELECT name, priceFROM productsWHERE id IN (SELECT product_id FROM orders);

注文したことがある顧客:

SELECT name, emailFROM customersWHERE id IN (SELECT customer_id FROM orders);

否定は NOT IN ですが、サブクエリ側に NULL が混ざると結果が空になりやすいので、実務では NOT EXISTS の方が安全なことが多いです。よくあるのは NOT IN で全部空になり、原因が NULL だと気づくまで遠回りすることです。


EXISTS

「関連行が1件でもあるか」だけ見ます。中の SELECT リストは何でもよく、慣習で SELECT 1 と書くことが多いです。

SELECT c.nameFROM customers cWHERE EXISTS (  SELECT 1  FROM orders o  WHERE o.customer_id = c.id);

注文が無い顧客(鈴木さん):

SELECT c.nameFROM customers cWHERE NOT EXISTS (  SELECT 1  FROM orders o  WHERE o.customer_id = c.id);

外側の行ごとに内側を相関させるので、相関サブクエリと呼ばれます。LEFT JOIN ... WHERE o.id IS NULL と同じ結果を、EXISTS でも書けます。


JOIN との使い分け

書き方 向いていること
JOIN 両表の列を結果に出したい
IN / EXISTS 「いる/いない」のフィルタが主目的
スカラーサブクエリ 平均・最大など単一値との比較

同じ結果でも、計画(EXPLAIN)は変わることがあります。まずは読みやすさで選び、遅くなったら EXPLAIN で計画を見る、で十分です。

JOIN で同じ「注文あり顧客」:

SELECT DISTINCT c.nameFROM customers cJOIN orders o ON o.customer_id = c.id;

よくあるつまずき

症状 対処
more than one row returned by a subquery スカラー用の場所に複数行。IN / EXISTS に変える
NOT IN が常に空 サブクエリに NULL。NOT EXISTS
結果が遅い 相関が重い可能性。JOIN 化や索引を検討

EXISTS と JOIN で同じ絞り込みが書ければ十分です。遅さが気になったら EXPLAIN で計画を見てみてください。

シェア