2-1 SELECT文を極める - 条件指定と並べ替え
SELECT文を極める - 条件指定と並べ替え¶
SELECT文は最も使うSQL。複数条件、パターンマッチング、並べ替え、ページングをマスターすれば、実務で必要なデータ検索の大半をカバーできる。
なぜこれを学ぶのか¶
前回(1-3)でSELECT文の基本を学んだ。WHERE age >= 30 のように、1つの条件でデータを絞り込めるようになった。
しかし、実務では単純な条件だけでは足りない。
- 「30歳以上かつ東京在住のユーザー」
- 「名前に“田”を含むユーザー」
- 「年齢が20〜30の範囲のユーザー」
- 「年齢順に並べ替えて表示」
- 「10件ずつページ送り」
こうした要件に応えるため、SELECT文の条件指定と並べ替えを深く学ぶ必要がある。
使用するテーブル¶
この記事では、前回より少し充実したサンプルデータを使う。DB Fiddle で「PostgreSQL 15」を選択し、以下のSQLを「Schema SQL」に貼り付けてほしい。
-- usersテーブルを作成
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL,
age INTEGER NOT NULL,
department VARCHAR(50),
created_at DATE NOT NULL
);
-- サンプルデータを投入
INSERT INTO users (name, email, age, department, created_at) VALUES
('田中太郎', 'tanaka@example.com', 28, '営業部', '2023-04-01'),
('山田花子', 'yamada@example.com', 35, '開発部', '2022-08-15'),
('佐藤一郎', 'sato@example.com', 42, '開発部', '2021-01-10'),
('鈴木次郎', 'suzuki@example.com', 25, '営業部', '2024-01-05'),
('高橋美咲', 'takahashi@example.com', 31, '人事部', '2023-06-20'),
('田村健太', 'tamura@example.com', 29, '開発部', '2023-09-01'),
('中村由美', 'nakamura@example.com', 38, NULL, '2020-03-15'),
('小林誠', 'kobayashi@example.com', 45, '営業部', '2019-11-01');
作成されるテーブル:
| id | name | age | department | created_at | |
|---|---|---|---|---|---|
| 1 | 田中太郎 | tanaka@example.com | 28 | 営業部 | 2023-04-01 |
| 2 | 山田花子 | yamada@example.com | 35 | 開発部 | 2022-08-15 |
| 3 | 佐藤一郎 | sato@example.com | 42 | 開発部 | 2021-01-10 |
| 4 | 鈴木次郎 | suzuki@example.com | 25 | 営業部 | 2024-01-05 |
| 5 | 高橋美咲 | takahashi@example.com | 31 | 人事部 | 2023-06-20 |
| 6 | 田村健太 | tamura@example.com | 29 | 開発部 | 2023-09-01 |
| 7 | 中村由美 | nakamura@example.com | 38 | NULL | 2020-03-15 |
| 8 | 小林誠 | kobayashi@example.com | 45 | 営業部 | 2019-11-01 |
注目してほしいのは、id=7の中村由美さんのdepartmentがNULLになっている点。NULLの扱いについても後述する。
複数条件を組み合わせる(AND / OR / NOT)¶
実務では「AかつB」「AまたはB」「A以外」のように、複数の条件を組み合わせることが多い。
AND:両方を満たす¶
SELECT * FROM users WHERE age >= 30 AND department = '開発部';
「30歳以上」かつ「開発部」の両方を満たすユーザーを取得する。
結果:
| id | name | age | department | created_at | |
|---|---|---|---|---|---|
| 2 | 山田花子 | yamada@example.com | 35 | 開発部 | 2022-08-15 |
| 3 | 佐藤一郎 | sato@example.com | 42 | 開発部 | 2021-01-10 |
OR:どちらかを満たす¶
SELECT * FROM users WHERE department = '営業部' OR department = '人事部';
「営業部」または「人事部」のどちらかに所属するユーザーを取得する。
結果:
| id | name | age | department | created_at | |
|---|---|---|---|---|---|
| 1 | 田中太郎 | tanaka@example.com | 28 | 営業部 | 2023-04-01 |
| 4 | 鈴木次郎 | suzuki@example.com | 25 | 営業部 | 2024-01-05 |
| 5 | 高橋美咲 | takahashi@example.com | 31 | 人事部 | 2023-06-20 |
| 8 | 小林誠 | kobayashi@example.com | 45 | 営業部 | 2019-11-01 |
NOT:否定¶
SELECT * FROM users WHERE NOT department = '開発部';
「開発部ではない」ユーザーを取得する。
注意: NULLの行は結果に含まれない。NOT (NULL = '開発部') はNULLを返すため、条件を満たさない扱いになる。
結果:
| id | name | age | department | created_at | |
|---|---|---|---|---|---|
| 1 | 田中太郎 | tanaka@example.com | 28 | 営業部 | 2023-04-01 |
| 4 | 鈴木次郎 | suzuki@example.com | 25 | 営業部 | 2024-01-05 |
| 5 | 高橋美咲 | takahashi@example.com | 31 | 人事部 | 2023-06-20 |
| 8 | 小林誠 | kobayashi@example.com | 45 | 営業部 | 2019-11-01 |
中村由美さん(department=NULL)は結果に含まれていない点に注意。
括弧で優先順位を制御¶
ANDはORより優先される。数学の掛け算が足し算より優先されるのと同じ。
-- 意図しない結果になる可能性
SELECT * FROM users WHERE department = '営業部' OR department = '開発部' AND age >= 35;
上記は department = '営業部' OR (department = '開発部' AND age >= 35) と解釈される。
意図を明確にするため、括弧を使う。
-- 営業部または開発部で、かつ35歳以上
SELECT * FROM users WHERE (department = '営業部' OR department = '開発部') AND age >= 35;
結果:
| id | name | age | department | created_at | |
|---|---|---|---|---|---|
| 2 | 山田花子 | yamada@example.com | 35 | 開発部 | 2022-08-15 |
| 3 | 佐藤一郎 | sato@example.com | 42 | 開発部 | 2021-01-10 |
| 8 | 小林誠 | kobayashi@example.com | 45 | 営業部 | 2019-11-01 |
ベストプラクティス: 複雑な条件では、意図を明確にするために括弧を使う。読みやすさも向上する。
LIKEでパターンマッチング¶
LIKEは、文字列の部分一致検索に使う。検索フォームの実装でよく使われる。
基本構文¶
SELECT * FROM テーブル名 WHERE 列名 LIKE 'パターン';
ワイルドカード¶
| 記号 | 意味 | 例 |
|---|---|---|
% |
任意の文字列(0文字以上) | '田%' → 田で始まる |
_ |
任意の1文字 | '田_' → 田+1文字 |
前方一致¶
-- 「田」で始まる名前
SELECT * FROM users WHERE name LIKE '田%';
結果:
| id | name | age | department | created_at | |
|---|---|---|---|---|---|
| 1 | 田中太郎 | tanaka@example.com | 28 | 営業部 | 2023-04-01 |
| 6 | 田村健太 | tamura@example.com | 29 | 開発部 | 2023-09-01 |
後方一致¶
-- 「郎」で終わる名前
SELECT * FROM users WHERE name LIKE '%郎';
結果:
| id | name | age | department | created_at | |
|---|---|---|---|---|---|
| 1 | 田中太郎 | tanaka@example.com | 28 | 営業部 | 2023-04-01 |
| 3 | 佐藤一郎 | sato@example.com | 42 | 開発部 | 2021-01-10 |
| 4 | 鈴木次郎 | suzuki@example.com | 25 | 営業部 | 2024-01-05 |
部分一致¶
-- 「村」を含む名前
SELECT * FROM users WHERE name LIKE '%村%';
結果:
| id | name | age | department | created_at | |
|---|---|---|---|---|---|
| 6 | 田村健太 | tamura@example.com | 29 | 開発部 | 2023-09-01 |
| 7 | 中村由美 | nakamura@example.com | 38 | NULL | 2020-03-15 |
アンダースコアで1文字指定¶
-- 2文字の部署名
SELECT DISTINCT department FROM users WHERE department LIKE '__部';
_ は1文字を意味するので、'__部' は「2文字+部」にマッチする。
結果:
| department |
|---|
| 営業部 |
| 開発部 |
| 人事部 |
大文字小文字の扱い¶
PostgreSQLのLIKEは大文字小文字を区別する。区別しない検索にはILIKEを使う。
-- 大文字小文字を区別しない検索(PostgreSQL固有)
SELECT * FROM users WHERE email ILIKE '%TANAKA%';
MySQLのLIKEはデフォルトで大文字小文字を区別しない。
パフォーマンスの注意¶
LIKEのパターンによって、検索速度が大きく変わる。
前方一致はインデックスが使える
-- 速い(インデックスが効く)
SELECT * FROM users WHERE name LIKE '田%';
後方一致・部分一致はインデックスが使えない
-- 遅い(全件スキャン)
SELECT * FROM users WHERE name LIKE '%郎';
SELECT * FROM users WHERE name LIKE '%村%';
なぜこの違いが生まれるのか?
インデックスは「本の索引」のようなもの。索引は「あ」「い」「う」…と先頭の文字でソートされている。
- 「田」で始まる名前 → 索引の「た」のページを開けば見つかる
- 「郎」で終わる名前 → 索引を最初から最後まで全部見る必要がある
速度の目安
| パターン | 例 | インデックス | 100万件での速度 |
|---|---|---|---|
| 前方一致 | '田%' |
使える | 数ミリ秒 |
| 後方一致 | '%郎' |
使えない | 数秒〜 |
| 部分一致 | '%村%' |
使えない | 数秒〜 |
部分一致が必要な場合の対策
大量データで部分一致検索が必要な場合、以下の方法を検討する:
- 全文検索機能を使う: PostgreSQLの
tsvector/tsquery、MySQLのFULLTEXT INDEX - 専用の検索エンジンを使う: Elasticsearch、Algolia など
- pg_trgm拡張: PostgreSQLでトライグラムインデックスを使う
これらは本シリーズの上級編(Part 3以降)で扱う。今は「部分一致は遅くなる可能性がある」と覚えておけばよい。
INで複数値を指定¶
INは、指定した複数の値のいずれかに一致するかを判定する。
基本構文¶
SELECT * FROM テーブル名 WHERE 列名 IN (値1, 値2, 値3);
ORの代わりにINを使う¶
-- ORで書く場合
SELECT * FROM users WHERE department = '営業部' OR department = '開発部' OR department = '人事部';
-- INで書く場合(同じ意味)
SELECT * FROM users WHERE department IN ('営業部', '開発部', '人事部');
INを使うと、条件がすっきりして読みやすくなる。
結果:
| id | name | age | department | created_at | |
|---|---|---|---|---|---|
| 1 | 田中太郎 | tanaka@example.com | 28 | 営業部 | 2023-04-01 |
| 2 | 山田花子 | yamada@example.com | 35 | 開発部 | 2022-08-15 |
| 3 | 佐藤一郎 | sato@example.com | 42 | 開発部 | 2021-01-10 |
| 4 | 鈴木次郎 | suzuki@example.com | 25 | 営業部 | 2024-01-05 |
| 5 | 高橋美咲 | takahashi@example.com | 31 | 人事部 | 2023-06-20 |
| 6 | 田村健太 | tamura@example.com | 29 | 開発部 | 2023-09-01 |
| 8 | 小林誠 | kobayashi@example.com | 45 | 営業部 | 2019-11-01 |
NOT IN¶
-- 営業部と開発部以外
SELECT * FROM users WHERE department NOT IN ('営業部', '開発部');
結果:
| id | name | age | department | created_at | |
|---|---|---|---|---|---|
| 5 | 高橋美咲 | takahashi@example.com | 31 | 人事部 | 2023-06-20 |
注意: NOT INもNULLを含む行を返さない。中村由美さん(department=NULL)は結果に含まれない。
BETWEENで範囲指定¶
BETWEENは、値が指定した範囲内にあるかを判定する。
基本構文¶
SELECT * FROM テーブル名 WHERE 列名 BETWEEN 下限 AND 上限;
BETWEEN は「下限以上、上限以下」(境界値を含む)である。
数値の範囲指定¶
-- 25歳以上35歳以下
SELECT * FROM users WHERE age BETWEEN 25 AND 35;
これは age >= 25 AND age <= 35 と同じ意味。
結果:
| id | name | age | department | created_at | |
|---|---|---|---|---|---|
| 1 | 田中太郎 | tanaka@example.com | 28 | 営業部 | 2023-04-01 |
| 2 | 山田花子 | yamada@example.com | 35 | 開発部 | 2022-08-15 |
| 4 | 鈴木次郎 | suzuki@example.com | 25 | 営業部 | 2024-01-05 |
| 5 | 高橋美咲 | takahashi@example.com | 31 | 人事部 | 2023-06-20 |
| 6 | 田村健太 | tamura@example.com | 29 | 開発部 | 2023-09-01 |
日付の範囲指定¶
-- 2023年に入社したユーザー
SELECT * FROM users WHERE created_at BETWEEN '2023-01-01' AND '2023-12-31';
結果:
| id | name | age | department | created_at | |
|---|---|---|---|---|---|
| 1 | 田中太郎 | tanaka@example.com | 28 | 営業部 | 2023-04-01 |
| 5 | 高橋美咲 | takahashi@example.com | 31 | 人事部 | 2023-06-20 |
| 6 | 田村健太 | tamura@example.com | 29 | 開発部 | 2023-09-01 |
NOT BETWEEN¶
-- 30〜40歳の範囲外
SELECT * FROM users WHERE age NOT BETWEEN 30 AND 40;
結果:
| id | name | age | department | created_at | |
|---|---|---|---|---|---|
| 1 | 田中太郎 | tanaka@example.com | 28 | 営業部 | 2023-04-01 |
| 3 | 佐藤一郎 | sato@example.com | 42 | 開発部 | 2021-01-10 |
| 4 | 鈴木次郎 | suzuki@example.com | 25 | 営業部 | 2024-01-05 |
| 6 | 田村健太 | tamura@example.com | 29 | 開発部 | 2023-09-01 |
| 8 | 小林誠 | kobayashi@example.com | 45 | 営業部 | 2019-11-01 |
NULLの扱い¶
NULLは「値がない」ことを表す特殊な値。SQLで最も混乱しやすいトピックの1つであり、バグの原因にもなりやすい。
NULLとは何か¶
NULLは「空文字」や「ゼロ」とは異なる。「不明(わからない)」を意味する。
| 値 | 意味 | 例 |
|---|---|---|
''(空文字) |
値はある(長さ0の文字列) | 「名前欄に何も入力しなかった」 |
0 |
値はある(数値のゼロ) | 「在庫数が0個」 |
NULL |
値が不明 | 「在庫数を確認していない」 |
中村由美さんのdepartmentがNULLなのは、「部署がない」のではなく「部署が不明または未設定」という意味。
IS NULL / IS NOT NULL¶
NULLの判定には専用の演算子を使う。
-- 部署が未設定のユーザー
SELECT * FROM users WHERE department IS NULL;
結果:
| id | name | age | department | created_at | |
|---|---|---|---|---|---|
| 7 | 中村由美 | nakamura@example.com | 38 | NULL | 2020-03-15 |
-- 部署が設定されているユーザー
SELECT * FROM users WHERE department IS NOT NULL;
なぜ = NULL ではダメなのか¶
= NULL や <> NULL は正しく動作しない。
-- 誤り: 結果は常に空
SELECT * FROM users WHERE department = NULL;
-- 正しい
SELECT * FROM users WHERE department IS NULL;
これはSQLの「3値論理」という仕様に基づく。通常のプログラミング言語は TRUE / FALSE の2値だが、SQLは TRUE / FALSE / UNKNOWN(不明) の3値で判定する。
NULLは「不明」なので、何と比較しても結果は「不明」になる。
WHERE句はTRUEの行だけを返すため、UNKNOWNの行は除外される。
3値論理の真理値表¶
AND / OR / NOT とNULLの組み合わせは複雑になる。
AND(両方TRUEのときだけTRUE)
| A | B | A AND B |
|---|---|---|
| TRUE | NULL | UNKNOWN |
| FALSE | NULL | FALSE |
| NULL | NULL | UNKNOWN |
OR(どちらかTRUEならTRUE)
| A | B | A OR B |
|---|---|---|
| TRUE | NULL | TRUE |
| FALSE | NULL | UNKNOWN |
| NULL | NULL | UNKNOWN |
NOT
| A | NOT A |
|---|---|
| TRUE | FALSE |
| FALSE | TRUE |
| NULL | UNKNOWN |
NULLが引き起こす典型的なバグ¶
パターン1: NOT INとNULL
-- 「開発部でない人」を取得したい
SELECT * FROM users WHERE department NOT IN ('開発部');
期待: 営業部、人事部、NULLの人が返る
実際: NULLの人は返らない
NOT INは内部的に department <> '開発部' と展開される。NULLとの比較はUNKNOWNになるため、中村由美さんは結果に含まれない。
パターン2: 集計関数とNULL
-- 部署ごとの人数をカウント
SELECT department, COUNT(*) FROM users GROUP BY department;
NULLも1つのグループとしてカウントされる。ただし COUNT(department) はNULLを除外する。
パターン3: 計算とNULL
-- NULLを含む計算は結果もNULLになる
SELECT 10 + NULL; -- 結果: NULL
SELECT NULL || 'abc'; -- 結果: NULL(文字列連結)
NULLを安全に扱う方法¶
COALESCE関数: NULLを別の値に置き換える
-- NULLを「未所属」として表示
SELECT name, COALESCE(department, '未所属') AS department FROM users;
結果:
| name | department |
|---|---|
| 田中太郎 | 営業部 |
| … | … |
| 中村由美 | 未所属 |
NULLを含めて検索する場合
-- 開発部でない人(NULLも含む)
SELECT * FROM users
WHERE department <> '開発部' OR department IS NULL;
ORDER BYで並べ替え¶
ORDER BYは、結果を指定した順序で並べ替える。
基本構文¶
SELECT * FROM テーブル名 ORDER BY 列名 [ASC|DESC];
| 略語 | 英語 | 意味 | 並び順 |
|---|---|---|---|
| ASC | Ascending | 上昇する | 昇順(小さい→大きい、古い→新しい) |
| DESC | Descending | 下降する | 降順(大きい→小さい、新しい→古い) |
覚え方: 「山を登る(Ascend)と数字が増えていく」とイメージする。ASCは省略可能(デフォルト)。
昇順(ASC)¶
-- 年齢の昇順で並べ替え
SELECT * FROM users ORDER BY age ASC;
結果:
| id | name | age | department | created_at | |
|---|---|---|---|---|---|
| 4 | 鈴木次郎 | suzuki@example.com | 25 | 営業部 | 2024-01-05 |
| 1 | 田中太郎 | tanaka@example.com | 28 | 営業部 | 2023-04-01 |
| 6 | 田村健太 | tamura@example.com | 29 | 開発部 | 2023-09-01 |
| 5 | 高橋美咲 | takahashi@example.com | 31 | 人事部 | 2023-06-20 |
| 2 | 山田花子 | yamada@example.com | 35 | 開発部 | 2022-08-15 |
| 7 | 中村由美 | nakamura@example.com | 38 | NULL | 2020-03-15 |
| 3 | 佐藤一郎 | sato@example.com | 42 | 開発部 | 2021-01-10 |
| 8 | 小林誠 | kobayashi@example.com | 45 | 営業部 | 2019-11-01 |
降順(DESC)¶
-- 入社日の降順(新しい順)
SELECT * FROM users ORDER BY created_at DESC;
結果:
| id | name | age | department | created_at | |
|---|---|---|---|---|---|
| 4 | 鈴木次郎 | suzuki@example.com | 25 | 営業部 | 2024-01-05 |
| 6 | 田村健太 | tamura@example.com | 29 | 開発部 | 2023-09-01 |
| 5 | 高橋美咲 | takahashi@example.com | 31 | 人事部 | 2023-06-20 |
| 1 | 田中太郎 | tanaka@example.com | 28 | 営業部 | 2023-04-01 |
| 2 | 山田花子 | yamada@example.com | 35 | 開発部 | 2022-08-15 |
| 3 | 佐藤一郎 | sato@example.com | 42 | 開発部 | 2021-01-10 |
| 7 | 中村由美 | nakamura@example.com | 38 | NULL | 2020-03-15 |
| 8 | 小林誠 | kobayashi@example.com | 45 | 営業部 | 2019-11-01 |
複数列での並べ替え¶
-- 部署名の昇順、同じ部署内では年齢の降順
SELECT * FROM users ORDER BY department ASC, age DESC;
結果:
| id | name | age | department | created_at | |
|---|---|---|---|---|---|
| 7 | 中村由美 | nakamura@example.com | 38 | NULL | 2020-03-15 |
| 5 | 高橋美咲 | takahashi@example.com | 31 | 人事部 | 2023-06-20 |
| 8 | 小林誠 | kobayashi@example.com | 45 | 営業部 | 2019-11-01 |
| 1 | 田中太郎 | tanaka@example.com | 28 | 営業部 | 2023-04-01 |
| 4 | 鈴木次郎 | suzuki@example.com | 25 | 営業部 | 2024-01-05 |
| 3 | 佐藤一郎 | sato@example.com | 42 | 開発部 | 2021-01-10 |
| 2 | 山田花子 | yamada@example.com | 35 | 開発部 | 2022-08-15 |
| 6 | 田村健太 | tamura@example.com | 29 | 開発部 | 2023-09-01 |
最初の列で同じ値がある場合、2番目の列で並べ替えられる。
NULLの並び順¶
PostgreSQLでは、NULLは昇順で最後、降順で最初に来る。これを変えるには NULLS FIRST / NULLS LAST を使う。
-- NULLを最初に持ってくる
SELECT * FROM users ORDER BY department ASC NULLS FIRST;
LIMIT / OFFSETでページング¶
LIMITは取得件数を制限し、OFFSETは先頭からスキップする件数を指定する。一覧画面のページング(ページ送り)で必須の機能。
LIMIT:取得件数を制限¶
-- 上位3件だけ取得
SELECT * FROM users ORDER BY age ASC LIMIT 3;
結果:
| id | name | age | department | created_at | |
|---|---|---|---|---|---|
| 4 | 鈴木次郎 | suzuki@example.com | 25 | 営業部 | 2024-01-05 |
| 1 | 田中太郎 | tanaka@example.com | 28 | 営業部 | 2023-04-01 |
| 6 | 田村健太 | tamura@example.com | 29 | 開発部 | 2023-09-01 |
OFFSET:スキップする¶
-- 4件目から3件取得(1〜3件目をスキップ)
SELECT * FROM users ORDER BY age ASC LIMIT 3 OFFSET 3;
結果:
| id | name | age | department | created_at | |
|---|---|---|---|---|---|
| 5 | 高橋美咲 | takahashi@example.com | 31 | 人事部 | 2023-06-20 |
| 2 | 山田花子 | yamada@example.com | 35 | 開発部 | 2022-08-15 |
| 7 | 中村由美 | nakamura@example.com | 38 | NULL | 2020-03-15 |
ページング実装の例¶
1ページに3件表示する場合:
-- 1ページ目(1〜3件目)
SELECT * FROM users ORDER BY id ASC LIMIT 3 OFFSET 0;
-- 2ページ目(4〜6件目)
SELECT * FROM users ORDER BY id ASC LIMIT 3 OFFSET 3;
-- 3ページ目(7〜9件目)
SELECT * FROM users ORDER BY id ASC LIMIT 3 OFFSET 6;
計算式: OFFSET = (ページ番号 - 1) × 1ページの件数
パフォーマンスの注意点¶
OFFSETが大きくなると、パフォーマンスが劣化する。これは初心者がハマりやすい落とし穴。
なぜ遅くなるのか?
OFFSETは「読み飛ばす」のではなく、実際には「読んでから捨てる」動作をする。
-- 10万1件目から10件取得
SELECT * FROM users ORDER BY id LIMIT 10 OFFSET 100000;
この場合、データベースは以下の処理をする:
- 10万10件のデータを読み込む
- 最初の10万件を捨てる
- 残りの10件を返す
本を読むときに「100ページ目から読みたい」と思っても、1〜99ページをめくる必要があるのと同じ。
どのくらい遅くなるか
| OFFSET | 読み込む件数 | 体感 |
|---|---|---|
| 0 | 10件 | 一瞬 |
| 1000 | 1010件 | 速い |
| 10000 | 10010件 | やや遅い |
| 100000 | 100010件 | 遅い |
| 1000000 | 1000010件 | かなり遅い |
データ量が増えるほど、後ろのページへのアクセスが遅くなる。
解決策:キーセットページング
OFFSETの代わりに、前のページの最後のIDを使ってWHEREで絞り込む方法がある。
-- 従来の方法(遅い)
SELECT * FROM users ORDER BY id LIMIT 10 OFFSET 100000;
-- キーセットページング(速い)
-- 前のページの最後のIDが100000だった場合
SELECT * FROM users WHERE id > 100000 ORDER BY id LIMIT 10;
キーセットページングでは、インデックスを使って直接目的の位置にジャンプできる。本の索引で「100ページ」を探して直接開くようなもの。
いつ気にすべきか
- 数百〜数千件のデータ:OFFSETで問題なし
- 数万件以上のデータ:後半ページでの遅延を検討
- 数十万件以上:キーセットページングを推奨
小規模なシステムでは気にしなくてよいが、「ページ送りが後半になるほど遅い」という現象に遭遇したら、OFFSETを疑うこと。
まとめ¶
- ANDは「かつ」、ORは「または」、NOTは「否定」
- 括弧で条件の優先順位を明確にする
- LIKEでパターンマッチング(%=任意の文字列、_=1文字)
- LIKEの前方一致はインデックスが効くが、部分一致は遅い
- INで複数値のいずれかに一致する条件を簡潔に書ける
- BETWEENで範囲指定(境界値を含む)
- NULLは「不明」を意味し、IS NULL / IS NOT NULLで判定
- NULLとの比較はUNKNOWNになる(3値論理)
- ORDER BYで並べ替え(ASC=昇順、DESC=降順)
- LIMITで取得件数を制限、OFFSETでスキップ
- 大量データのページングではOFFSETに注意
用語¶
| 用語 | 説明 |
|---|---|
| AND | 論理積。両方の条件を満たす場合に真 |
| OR | 論理和。どちらかの条件を満たす場合に真 |
| NOT | 論理否定。条件を反転させる |
| LIKE | 文字列のパターンマッチングを行う演算子 |
| ワイルドカード | パターンマッチングで使う特殊文字(%、_) |
| IN | 指定した複数値のいずれかに一致するかを判定 |
| BETWEEN | 値が指定範囲内にあるかを判定(境界値を含む) |
| NULL | 「不明」を表す特殊な値。空文字やゼロとは異なる |
| 3値論理 | TRUE / FALSE / UNKNOWN の3つの値で判定するSQLの仕様 |
| COALESCE | NULLを別の値に置き換える関数 |
| ORDER BY | 結果を並べ替えるための句 |
| ASC | 昇順(Ascending)。小さい順 |
| DESC | 降順(Descending)。大きい順 |
| LIMIT | 取得件数を制限する句 |
| OFFSET | 先頭からスキップする件数を指定 |
| ページング | データを複数ページに分割して表示する手法 |
| キーセットページング | OFFSETの代わりにWHERE句で絞り込むページング手法 |
| インデックス | 検索を高速化するためのデータ構造。本の索引に相当 |
| 全文検索 | 文書内の任意の文字列を高速に検索する機能 |
次の記事¶
SELECT文をさらに深く学びたい方はこちら。