1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【初心者向け】SQL 基礎から実践まで — JOIN とサブクエリの感覚を掴む2時間完全講座

1
Posted at

SQLは書けない人と書ける人で業務の生産性が10倍変わる分野だ。この記事では、初学者がつまずきやすい JOIN とサブクエリを軸に、2時間で実務レベルの感覚を掴むことを目標にする。PostgreSQLを前提にするが、MySQLでもほぼそのまま動く。

1. 準備 — サンプルテーブル

まず題材となる2つのテーブルを作る。

CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  name TEXT NOT NULL,
  created_at TIMESTAMP DEFAULT now()
);

CREATE TABLE orders (
  id SERIAL PRIMARY KEY,
  user_id INT NOT NULL REFERENCES users(id),
  amount INT NOT NULL,
  created_at TIMESTAMP DEFAULT now()
);

INSERT INTO users (name) VALUES ('Alice'), ('Bob'), ('Carol');
INSERT INTO orders (user_id, amount) VALUES (1, 1000), (1, 2000), (2, 1500);

Carolは注文を持たない。この前提で話を進める。

2. SELECTの基本

SQLは「どう取るか」ではなく「何が欲しいか」を書く言語だ。

SELECT id, name FROM users WHERE id = 1;

WHERE は行の絞り込み、SELECT は列の選択。この役割分担を最初にしっかり掴む。

3. INNER JOIN — 両方にある行だけ

INNER JOIN は両方のテーブルにマッチする行だけを返す。注文があるユーザーだけを知りたいときはこれ。

SELECT u.name, o.amount
FROM users u
INNER JOIN orders o ON o.user_id = u.id;

結果にCarolは出てこない。

4. LEFT JOIN — 左を必ず残す

注文がないユーザーも含めたい場合は LEFT JOIN を使う。右側に対応がなければNULLが埋まる。

SELECT u.name, o.amount
FROM users u
LEFT JOIN orders o ON o.user_id = u.id;

Carolの行が amount = NULL で出てくる。「未購入ユーザーを洗い出す」みたいな要件はこの形が定石だ。

5. GROUP BYと集計関数

ユーザーごとの購入総額を出す。

SELECT u.name, COALESCE(SUM(o.amount), 0) AS total
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.id, u.name
ORDER BY total DESC;

GROUP BY 句には SELECT に出てくる非集計列を全部並べる。COALESCE でNULLを0に変換するのは頻出パターン。

6. サブクエリ — 一度の問い合わせで済ませる

平均より多く買っているユーザーを出す例。

SELECT name
FROM users
WHERE id IN (
  SELECT user_id
  FROM orders
  GROUP BY user_id
  HAVING SUM(amount) > (SELECT AVG(amount) FROM orders)
);

サブクエリは「結果セットを別のクエリの中で使う」と考える。慣れるとCTE(WITH句)の方が読みやすい場合も多い。

WITH user_totals AS (
  SELECT user_id, SUM(amount) AS total FROM orders GROUP BY user_id
),
overall AS (
  SELECT AVG(amount) AS avg_amount FROM orders
)
SELECT u.name, ut.total
FROM user_totals ut
JOIN users u ON u.id = ut.user_id, overall
WHERE ut.total > overall.avg_amount;

7. インデックスの基礎

検索性能を上げたい列にはインデックスを張る。ただし書き込みが遅くなるトレードオフがある。

CREATE INDEX idx_orders_user_id ON orders(user_id);

EXPLAIN ANALYZE で実行計画を確認しながら張る位置を決めるのが正解。

次のステップ

  • ウィンドウ関数(ROW_NUMBERLAG)を覚える
  • 実行計画を読めるようになる
  • N+1問題をアプリ側から解消するSQLを書けるようにする

SQLはアプリ開発の地力そのものだ。ここを固めるとバックエンドの選択肢が一気に広がる。

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?