ORDER BYとLIMIT|テーブル・SELECT・条件抽出
このページはプロモーション(広告)を含みます。
テーブル・SELECT・条件抽出(初級・読む20分+演習30分)
この授業のゴール
- ORDER BY で昇順・降順・複数キーの並べ替えを指定できる
- LIMIT で上位N件を取り出すとき、同順位(同額・同日)が境界にあると結果が揺れることを説明し、タイブレーク列で一意にできる
- ORDER BY がないと結果の順序が保証されないことを理解し、「最新」「最大」を LIMIT だけで求めない
- NULLの並び位置と、数値を文字列で持つ列の並び順の落とし穴を避けられる
仕事でどう使うか
「売上上位10件」「直近の注文」「最も古い未対応チケット」といった依頼は、ほぼ並べ替え+件数制限で表せます。ところが、並べ方を書かない、同額の扱いを決めない、NULLの位置を確認しない、という3つの理由で、実行のたびに違う結果が出たり、誤った行が「1位」になったりします。
| 路線 | この授業の使いどころ |
|---|---|
| DS(データサイエンティスト) | 上位N件の抽出、最新レコードの取得。同順位の扱いを決めて、再実行しても同じ結果にする |
| ML(機械学習エンジニア) | 時系列データを日付順に並べて、過去から未来の順で学習・評価に分ける。順序が不定だと再現できない |
| DX(DX推進・AI企画) | ダッシュボードの「上位5件」の定義(同額の扱い・NULLの扱い)を要件に書く |
| RS(研究者) | 解析の再現性のため、並べ替えの基準を明記し、サンプル抽出の順序を固定する |
「同じSQLを2回実行して結果が違う」は、レポートでは致命的です。この授業のゴールは、いつ実行しても同じ順序・同じ上位N件になるSQLを書くことです。
前提
次の言葉を使います。不安があれば先に戻ってください。
- SELECT と WHERE → SELECTと列、WHEREと条件
- NULL(値がない印)→ NULLと3値論理 で詳しく扱います。この授業では、並べ替えでのNULLの位置だけ確認します。
- 昇順/降順(小さい順/大きい順)と順位 → 平均・合計・中央値を手計算
この授業では、これまでと同じ注文表 orders(8行、1行=注文1件)を使います。
身近な状況:「金額上位5件」で同額が2件あったら?
営業部から「注文金額の上位5件の注文IDを出してください」と頼まれました。注文表の金額を大きい順に並べると、次のようになります。
| 順位 | order_id | amount |
|---|---|---|
| 1 | 8 | 4000 |
| 2 | 1 | 3000 |
| 3 | 3 | 2500 |
| 4 | 5 | 1800 |
| 5 | 2 または 7 | 1200 |
| 6 | 7 または 2 | 1200 |
| 7 | 4 | 0 |
| 8 | 6 | NULL |
5位と6位が同額(1200円)です。上位5件に入るのは、注文2と注文7のどちらか1件だけで、どちらにするかをSQLで決めていないと、データベースの内部事情で決まります。実行環境やデータの追加で結果が変わるかもしれません。
この授業では、並べ方を明記し、同額のときの決め方(タイブレーク)も書いて、「いつ実行しても同じ結果」にする方法を学びます。
-- db: none
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL,
order_date TEXT NOT NULL,
status TEXT NOT NULL CHECK(status IN ('paid','cancelled','refunded')),
channel TEXT NOT NULL,
amount INTEGER,
coupon_code TEXT
);
INSERT INTO orders VALUES
(1,101,'2025-03-31','paid','web',3000,NULL),
(2,102,'2025-04-01','paid','app',1200,'SPRING'),
(3,101,'2025-04-01','cancelled','web',2500,NULL),
(4,103,'2025-04-10','paid','store',0,NULL),
(5,104,'2025-04-15','refunded','app',1800,'SPRING'),
(6,102,'2025-04-20','paid','web',NULL,NULL),
(7,105,'2025-04-30','paid','web',1200,''),
(8,103,'2025-05-01','paid','app',4000,'VIP');
SELECT order_id, amount
FROM orders
ORDER BY amount DESC, order_id
LIMIT 5;
直感的な説明:並べ替えてから、先頭から切り取る
ORDER BY と LIMIT は、2つの別の作業です。
- ORDER BY:行を指定の基準で並べる。行は減らない。
- LIMIT n:並んだ行の先頭から n 行だけを残す。
トランプのたとえで言えば、ORDER BY は「大きい順にカードを並べる」、LIMIT は「上から5枚だけ配る」です。並べ方を決めずに配ると、どのカードが配られるかは偶然です。
もう1つ大事なのは、データベースの表には、もともと順序の約束がないことです。表は名簿ではなく、行の集合です。入力した順に見えることが多くても、それは保証ではありません。順序がほしいときは ORDER BY を必ず書く。これがこの授業の最初のルールです。
正確な定義:ORDER BY・LIMIT・OFFSET・NULLの位置
基本形:
SELECT 列 FROM 表
WHERE 条件
ORDER BY 列1 [ASC|DESC], 列2 [ASC|DESC]
LIMIT n [OFFSET m];
- ASC:昇順(小→大)。省略すると ASC。DESC:降順(大→小)。
- 複数の列:
ORDER BY a DESC, bは、まず a の降順で並べ、a が同じ行の中で b の昇順に並べる。後ろの列が同順位の決め方(タイブレーク)になる。 - LIMIT n:先頭 n 行を残す。n は整数。
- OFFSET m:先頭 m 行を飛ばしてから n 行を取る(ページ送り用)。
- 文字列の並び:文字の順(辞書順)。
'100'は'20'より小さい(先頭の1が2より小さいため)。 - 日付文字列:
'YYYY-MM-DD'の形式なら、文字列の辞書順が日付順と一致する。 - NULLの位置:SQLite・MySQL では、昇順(ASC)で NULLが先頭、降順(DESC)で末尾。PostgreSQL・Oracle では逆(昇順で末尾)。方言で違うので、位置が結果に影響するときは明示する。
評価の順序は、おおよそ FROM → WHERE → SELECT → ORDER BY → LIMIT です。WHERE で絞ってから並べ、並べた後に切り取ります。
$$\text{結果} = \text{先頭}\ n\ \text{行}\big(\text{並べ替え}(\text{絞り込み}(\text{表}))\big)$$
実行して、金額の昇順・降順で、NULLがどこに来るかを確かめます。
-- db: none
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL,
order_date TEXT NOT NULL,
status TEXT NOT NULL CHECK(status IN ('paid','cancelled','refunded')),
channel TEXT NOT NULL,
amount INTEGER,
coupon_code TEXT
);
INSERT INTO orders VALUES
(1,101,'2025-03-31','paid','web',3000,NULL),
(2,102,'2025-04-01','paid','app',1200,'SPRING'),
(3,101,'2025-04-01','cancelled','web',2500,NULL),
(4,103,'2025-04-10','paid','store',0,NULL),
(5,104,'2025-04-15','refunded','app',1800,'SPRING'),
(6,102,'2025-04-20','paid','web',NULL,NULL),
(7,105,'2025-04-30','paid','web',1200,''),
(8,103,'2025-05-01','paid','app',4000,'VIP');
SELECT order_id, amount FROM orders ORDER BY amount ASC;
SELECT order_id, amount FROM orders ORDER BY amount DESC;
昇順では注文6(NULL)が先頭、降順では末尾に来ます。「金額が最も小さい注文」を LIMIT 1 で求めると、0円の注文4ではなく、NULLの注文6が返ってしまいます(失敗3で詳しく見ます)。
段階付き例題:金額上位5件を、再実行しても同じ結果にする
問題:支払済み・取消・返金を問わず、全注文のうち金額の大きい順に上位5件の order_id と amount を出す。同額のときは order_id が小さいほうを先にする。金額が未記録(NULL)の注文は、順位の対象に入れない。
ステップ1:並べ方を決める。第1の並べ替え基準は金額の降順 amount DESC。
ステップ2:同額の決め方を決める。同額のときは order_id の昇順。主キーが重複しないので、これで順序が一意に決まる。
ステップ3:NULLを対象外にする。降順では NULL が末尾に来るので、上位5件には入らないが、「5件に満たない場合に NULL が混ざる」可能性を考え、WHERE amount IS NOT NULL を明示しておく(IS NOT NULL は「NULLではない」という条件で、NULLと3値論理 で学びます)。
ステップ4:実行する。
-- db: none
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL,
order_date TEXT NOT NULL,
status TEXT NOT NULL CHECK(status IN ('paid','cancelled','refunded')),
channel TEXT NOT NULL,
amount INTEGER,
coupon_code TEXT
);
INSERT INTO orders VALUES
(1,101,'2025-03-31','paid','web',3000,NULL),
(2,102,'2025-04-01','paid','app',1200,'SPRING'),
(3,101,'2025-04-01','cancelled','web',2500,NULL),
(4,103,'2025-04-10','paid','store',0,NULL),
(5,104,'2025-04-15','refunded','app',1800,'SPRING'),
(6,102,'2025-04-20','paid','web',NULL,NULL),
(7,105,'2025-04-30','paid','web',1200,''),
(8,103,'2025-05-01','paid','app',4000,'VIP');
SELECT order_id, amount
FROM orders
WHERE amount IS NOT NULL
ORDER BY amount DESC, order_id
LIMIT 5;
ステップ5:結果を読む。注文 8(4000)、1(3000)、3(2500)、5(1800)、2(1200)の5行です。5位は注文2と注文7が同額ですが、order_id の小さい注文2が選ばれます。
ステップ6:タイブレークの影響を確認する。order_id を降順(order_id DESC)にすると、5位は注文7に変わります。同額が境界にあるとき、タイブレークの選び方で結果が変わるので、「どちらを優先するか」を業務ルールとして決める必要があります(「古い注文を先に」「新しい注文を先に」など)。
操作して理解する:並び順の落とし穴を確かめる
次の3つを、実行する前に結果を予想してください。
-- db: none
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL,
order_date TEXT NOT NULL,
status TEXT NOT NULL CHECK(status IN ('paid','cancelled','refunded')),
channel TEXT NOT NULL,
amount INTEGER,
coupon_code TEXT
);
INSERT INTO orders VALUES
(1,101,'2025-03-31','paid','web',3000,NULL),
(2,102,'2025-04-01','paid','app',1200,'SPRING'),
(3,101,'2025-04-01','cancelled','web',2500,NULL),
(4,103,'2025-04-10','paid','store',0,NULL),
(5,104,'2025-04-15','refunded','app',1800,'SPRING'),
(6,102,'2025-04-20','paid','web',NULL,NULL),
(7,105,'2025-04-30','paid','web',1200,''),
(8,103,'2025-05-01','paid','app',4000,'VIP');CREATE TABLE codes (label TEXT, as_text TEXT, as_int INTEGER);
INSERT INTO codes VALUES ('a','100',100),('b','20',20),('c','3',3);
-- (1) 日付の新しい順に上位3件(同日は order_id の大きい順)
SELECT order_id, order_date FROM orders
ORDER BY order_date DESC, order_id DESC LIMIT 3;
-- (2) 文字列として持つ数と、整数として持つ数の並び
SELECT label, as_text FROM codes ORDER BY as_text;
SELECT label, as_int FROM codes ORDER BY as_int;
-- (3) 昇順でも NULL を末尾にする書き方
SELECT order_id, amount FROM orders ORDER BY amount IS NULL, amount;
答えと読み方:
- (1) 注文 8(5/1)、7(4/30)、6(4/20)。日付の文字列
YYYY-MM-DDは辞書順=日付順です。 - (2) 文字列だと a(100)、b(20)、c(3) の順、整数だと c(3)、b(20)、a(100) の順。
'100' < '20' < '3'は文字を左から比べるためです。数として並べたいのに文字列で持っていると、順序が狂います。 - (3)
amount IS NULLは NULLの行で 1(真)、それ以外で 0(偽)になるので、まず「NULLでない行(0)を先、NULLの行(1)を後」に並べ、次に金額の昇順で並べます。結果は 0円、1200円、…、4000円、最後に NULL。SQLite 3.30 以降と PostgreSQL ではORDER BY amount NULLS LASTとも書けます。
試してみよう:(1) の order_id DESC を外すと、同じ日付(4/1)の注文2と3の順序が不定になります。上位3件には影響しませんが、LIMIT 6 のように境界が4/1にかかると影響が出ます。
発展:OFFSET によるページ送りと、ウィンドウ関数での順位
OFFSET の落とし穴:LIMIT 20 OFFSET 40 は「41〜60件目」を返しますが、ページを移動する間に行が追加・削除されると、同じ行が2回出たり、飛ばされたりします。また OFFSET が大きいと、飛ばす行も一度は並べる必要があり、処理が遅くなります。大規模な一覧では「前ページの最後の値より後」と条件で絞る方法(キーセット方式)が使われます。
決定的な順序(deterministic order):ORDER BY の列に一意な列(主キーなど)を最後に加えると、順序が完全に決まります。再現性が必要な場面(レポート、テスト、MLデータの分割)では、必ず入れてください。
同順位の扱い:LIMIT 5 は同額を含めて「5件」で打ち切ります。「5位までの同順位をすべて含める」「同順位は同じ順位とする」場合は、RANK() や DENSE_RANK() などのウィンドウ関数(「OVERとPARTITION BY」(有料版))を使います。たとえば本文のデータでは、上位5位まで(同順位を含む)なら、1200円の注文2と7が両方入り、6件になります。
方言差:MySQL・PostgreSQL・SQLite では LIMIT ですが、SQL Server は TOP n や OFFSET ... FETCH、Oracle は FETCH FIRST n ROWS ONLY を使います。標準SQLは FETCH FIRST です。この教材のSQLはSQLiteで実行します。
よくある失敗
失敗1:ORDER BY なしで LIMIT 1 を「最新」とみなす
-- db: none
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL,
order_date TEXT NOT NULL,
status TEXT NOT NULL CHECK(status IN ('paid','cancelled','refunded')),
channel TEXT NOT NULL,
amount INTEGER,
coupon_code TEXT
);
INSERT INTO orders VALUES
(1,101,'2025-03-31','paid','web',3000,NULL),
(2,102,'2025-04-01','paid','app',1200,'SPRING'),
(3,101,'2025-04-01','cancelled','web',2500,NULL),
(4,103,'2025-04-10','paid','store',0,NULL),
(5,104,'2025-04-15','refunded','app',1800,'SPRING'),
(6,102,'2025-04-20','paid','web',NULL,NULL),
(7,105,'2025-04-30','paid','web',1200,''),
(8,103,'2025-05-01','paid','app',4000,'VIP');
-- 誤り:並び順を指定していないので「最新」ではない
SELECT order_id, order_date FROM orders LIMIT 1;
-- 正しい:日付の新しい順に並べてから1件
SELECT order_id, order_date FROM orders ORDER BY order_date DESC, order_id DESC LIMIT 1;
誤りのほうは、この環境では注文1(3月31日)が返りますが、これは保証された動作ではありません。たまたま登録順に近い順で読まれているだけです。正しいほうは注文8(5月1日)です。直し方:最新・最大・最小を求めるときは、必ず ORDER BY で基準を書き、同日(同額)がありうるなら一意になる列をタイブレークに入れる。
失敗2:同額が境界にあるのにタイブレークを決めない
-- db: none
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL,
order_date TEXT NOT NULL,
status TEXT NOT NULL CHECK(status IN ('paid','cancelled','refunded')),
channel TEXT NOT NULL,
amount INTEGER,
coupon_code TEXT
);
INSERT INTO orders VALUES
(1,101,'2025-03-31','paid','web',3000,NULL),
(2,102,'2025-04-01','paid','app',1200,'SPRING'),
(3,101,'2025-04-01','cancelled','web',2500,NULL),
(4,103,'2025-04-10','paid','store',0,NULL),
(5,104,'2025-04-15','refunded','app',1800,'SPRING'),
(6,102,'2025-04-20','paid','web',NULL,NULL),
(7,105,'2025-04-30','paid','web',1200,''),
(8,103,'2025-05-01','paid','app',4000,'VIP');
-- 上位5件。5位は同額(注文2と注文7)
SELECT order_id, amount FROM orders WHERE amount IS NOT NULL
ORDER BY amount DESC, order_id ASC LIMIT 5;
SELECT order_id, amount FROM orders WHERE amount IS NOT NULL
ORDER BY amount DESC, order_id DESC LIMIT 5;
1つ目の5位は注文2、2つ目の5位は注文7です。どちらも「金額の上位5件」としては正しいため、どれが正しいかは業務ルールで決めるものです。ORDER BY amount DESC だけだと、実行環境やデータ追加で5位が入れ替わりえます。直し方:タイブレークの列を ORDER BY の最後に加え、定義書に「同額は注文IDの小さい順」などと書く。
失敗3:数を文字列で持つ列や、NULLを含む列をそのまま並べる
-- db: none
CREATE TABLE orders (
order_id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL,
order_date TEXT NOT NULL,
status TEXT NOT NULL CHECK(status IN ('paid','cancelled','refunded')),
channel TEXT NOT NULL,
amount INTEGER,
coupon_code TEXT
);
INSERT INTO orders VALUES
(1,101,'2025-03-31','paid','web',3000,NULL),
(2,102,'2025-04-01','paid','app',1200,'SPRING'),
(3,101,'2025-04-01','cancelled','web',2500,NULL),
(4,103,'2025-04-10','paid','store',0,NULL),
(5,104,'2025-04-15','refunded','app',1800,'SPRING'),
(6,102,'2025-04-20','paid','web',NULL,NULL),
(7,105,'2025-04-30','paid','web',1200,''),
(8,103,'2025-05-01','paid','app',4000,'VIP');CREATE TABLE codes (label TEXT, as_text TEXT, as_int INTEGER);
INSERT INTO codes VALUES ('a','100',100),('b','20',20),('c','3',3);
-- 誤り1:文字列の数を並べる。'100' が '20' より小さくなる
SELECT label, as_text FROM codes ORDER BY as_text;
-- 直し方:整数に変換して並べる(CAST は型変換、詳しくは SQ01-L06)
SELECT label, as_text FROM codes ORDER BY CAST(as_text AS INTEGER);
-- 誤り2:金額が最も小さい注文を LIMIT 1 で求める
SELECT order_id, amount FROM orders ORDER BY amount ASC LIMIT 1;
誤り1では順序が 100、20、3 になります。誤り2では、昇順の先頭は NULL の注文6 で、0円の注文4ではありません。「最小」を求めるなら WHERE amount IS NOT NULL を付けるか、NULLを末尾に回します。直し方:列の型(数値か文字列か)を確認し、NULLの扱いを条件で明示する。
まとめ
- 表の行には順序の約束がない。順序がほしいときは必ず ORDER BY を書く。
- LIMIT n は並べた後の先頭n行。最新・最大を LIMIT だけで求めない。
- 同順位が境界にあるとき、一意な列をタイブレークに加え、業務ルールとして定義する。
- NULLの位置は方言で異なる。昇順でNULLが先頭に来る場合は、IS NOT NULL で除く。
- 文字列で持つ数は辞書順に並ぶ。数として並べたいなら型を変換する。
用語
- ORDER BY(ORDER BY)
結果の行を指定の列で並べる句。ASCは昇順、DESCは降順。
- LIMIT(LIMIT)
並べた結果の先頭から指定した件数だけを残す句。
- タイブレーク(tie-break)
同順位(同額・同日)のときに順序を決めるための、2番目以降の並べ替え基準。
- 決定的な順序(deterministic order)
何度実行しても同じになる並び順。一意な列を ORDER BY の最後に加えると得られる。
よくある質問
LIMIT と TOP の違いは?
どちらも「先頭n行」を取る機能です。LIMIT は MySQL・PostgreSQL・SQLite、TOP は SQL Server、FETCH FIRST は標準SQL・Oracleなど、データベースによって書き方が違います。考え方(並べてから切り取る)は同じです。
ORDER BY は処理が重いですか?
行数が多い表では、並べ替えに時間とメモリがかかります。LIMIT と組み合わせると、上位だけを保持して効率化される場合があります。大きな表では、必要な列と行だけに絞ってから並べるのが基本です。
ORDER BY 列の番号(ORDER BY 1)を使ってよいですか?
動きますが、列を追加・並べ替えたときに意味が変わる危険があります。可読性のため、列名または別名で書くのがおすすめです。
「最新の1件」と「最大の1件」で、同日・同額がありうるときは?
ORDER BY の最後に主キーなど一意な列を加え、結果を一意にします。業務で「同日なら注文IDが大きいほうを最新とする」と決まっているなら、それに合わせます。ルールは定義書に残します。
NULLを先頭にしたいか末尾にしたいかは、どう決めますか?
業務の意味で決めます。「未到着の入荷を先に確認したい」なら先頭、「到着済みの早い順」なら除外か末尾です。方言差があるため、位置が重要なら明示的に書きます。
ランダムに数件取り出したいときは?
SQLiteでは ORDER BY RANDOM() LIMIT n が使えます(MySQLは RAND())。ただし実行のたびに結果が変わり、再現できません。機械学習やレポートでは、再現性のため固定した方法(乱数の種を決めた抽出)を別の手段で用意します。
確認問題
問1 ORDER BY を書かずに SELECT ... LIMIT 1 を実行した。得られる行について正しい説明はどれか。
- ① 必ず最新の行になる
- ② 必ず最も古い行になる
- ③ どの行が返るか保証されない
- ④ 主キーが最小の行になる
解答と解説
正解:③
表の行には順序の約束がありません。順序が必要なら ORDER BY を必ず書きます。
問2 ORDER BY amount DESC, order_id の order_id の役割はどれか。
- ① amount が同じ行の順序を決める(タイブレーク)
- ② NULLを除外する
- ③ 行数を制限する
- ④ 金額を計算する
解答と解説
正解:①
後ろに書いた列は、前の列が同じ行の並びを決めます。
問3 SQLiteで ORDER BY amount ASC LIMIT 1 を実行したとき、amount に NULL を含む表ではどうなるか。
- ① 0以上の最小値の行が返る
- ② エラーになる
- ③ NULLの行は自動で除かれる
- ④ NULLが先頭に来て、NULLの行が返る
解答と解説
正解:④
SQLiteは昇順でNULLを先頭に置きます。最小値がほしいなら IS NOT NULL で除きます。
問4 文字列として保存された '100'、'20'、'3' を昇順に並べたときの順序はどれか。
- ① '3'、'20'、'100'
- ② '100'、'20'、'3'
- ③ '20'、'3'、'100'
- ④ '100'、'3'、'20'
解答と解説
正解:②
文字列は左から1文字ずつ比べるため、'1' < '2' < '3' の順になります。
問5 全8件の注文を金額の降順に並べ、上位5件を取った。5位と6位が同額のとき、最も適切な対処はどれか。
- ① LIMIT を 6 にする
- ② DISTINCT を付ける
- ③ そのまま LIMIT 5 を使う
- ④ ORDER BY に一意な列をタイブレークとして加え、同額の扱いを定義書に書く
解答と解説
正解:④
境界に同額があると、どちらが5位になるかが不定です。決め方を明示します。
問6 ORDER BY、WHERE、LIMIT の処理順として、おおよそ正しいものはどれか。
- ① WHERE → ORDER BY → LIMIT
- ② LIMIT → WHERE → ORDER BY
- ③ WHERE → LIMIT → ORDER BY
- ④ ORDER BY → WHERE → LIMIT
解答と解説
正解:①
絞り込み、並べ替え、切り取りの順です。LIMIT が先だと、並べる前に切り取られてしまいます。
次に学ぶこと
次は NULLと3値論理 で、NULLと3値論理(真・偽・不明)を学びます。この授業で出てきた IS NOT NULL の意味も整理します。
並べ替えと集計を組み合わせる場面は 「COUNT・SUM・AVG」(有料版) 以降で扱います。この授業の確認問題だけで習得とはしません。モジュール テーブル・SELECT・条件抽出 の総合テストと自力課題で判定します。
キャリア学習:データサイエンティスト・機械学習エンジニア・DX推進・研究者を目指す人の学習アプリ。164モジュール・984授業を、ブラウザで動くSQLとPythonの演習で。基礎と各分野の最初のモジュールは無料。全部は2,980円の買い切り。
アプリを無料で開く有料版と無料版の違い