【SQL】ランダムにソートしてデータを取得する方法|DBMS別構文・高速化・実践パターン完全ガイド

【SQL】ランダムにソートしてデータを取得する方法 SQL

SQLでランダムにデータを取得する場面は意外と多くあります。抽選システム・クイズのランダム出題・A/Bテストのサンプリング・テストデータの生成など、業務でもよく登場する要件です。

本記事では、MySQL・PostgreSQL・Oracle・SQL Serverそれぞれの構文から、大規模テーブルでも遅くならない高速化テクニック、シード付き再現可能ランダム、TABLESAMPLEによる統計サンプリングまで網羅します。

この記事でわかること

  • DBMS別のランダムソート構文(ORDER BY RAND / RANDOM / NEWID / DBMS_RANDOM)
  • ORDER BY RAND()がなぜ遅いのか、内部動作の仕組み
  • 100万件超のテーブルでも高速なランダム取得パターン
  • TABLESAMPLEによる統計サンプリング(PostgreSQL / SQL Server / Oracle)
  • シード付きランダムで再現性のあるランダム取得
  • 抽選・クイズ・A/Bテスト・データ品質チェックの実践パターン
スポンサーリンク

DBMS別の基本構文

ランダムソートの書き方はDBMSによって異なります。まず各DBMSの構文をまとめます。

DBMS ランダム関数 基本構文 戻り値の範囲
MySQL / MariaDB RAND() ORDER BY RAND() 0以上1未満の浮動小数
PostgreSQL RANDOM() ORDER BY RANDOM() 0以上1未満の浮動小数
Oracle DBMS_RANDOM.VALUE ORDER BY DBMS_RANDOM.VALUE 0以上1未満の浮動小数
SQL Server NEWID() ORDER BY NEWID() ランダムなGUID
MySQL / MariaDB
-- 全件をランダム順に取得
SELECT * FROM products
ORDER BY RAND();

-- ランダムに5件取得
SELECT * FROM products
ORDER BY RAND()
LIMIT 5;
PostgreSQL
-- 全件をランダム順に取得
SELECT * FROM products
ORDER BY RANDOM();

-- ランダムに5件取得
SELECT * FROM products
ORDER BY RANDOM()
LIMIT 5;
Oracle
-- ランダムに5件取得
SELECT * FROM products
ORDER BY DBMS_RANDOM.VALUE
FETCH FIRST 5 ROWS ONLY;

-- ROWNUMを使う古い書き方
SELECT * FROM (
    SELECT * FROM products
    ORDER BY DBMS_RANDOM.VALUE
)
WHERE ROWNUM <= 5;
SQL Server
-- ランダムに5件取得
SELECT TOP 5 *
FROM products
ORDER BY NEWID();
NEWID()はGUIDでのランダム
SQL ServerのNEWID()は数値乱数ではなくGUID(グローバル一意識別子)を生成し、そのバイト列でソートします。統計的な均一性は問題ありませんが、シード指定はできません。

ORDER BY RAND()の仕組みとパフォーマンス問題

ORDER BY RAND()はシンプルで直感的ですが、テーブルが大きくなると極端に遅くなるという致命的な問題があります。まずその理由を理解しましょう。

内部処理の流れ

  1. 対象テーブルの全行を読み込む(フルテーブルスキャン)
  2. 各行に乱数値を付与する
  3. 乱数値で全行をソートする(O(N log N))
  4. 先頭のN件を返す
行数が増えるほど指数的に遅くなる
1万件 → 数十ms、10万件 → 数百ms、100万件 → 数秒〜数十秒。インデックスは一切使われず、毎回フルスキャンが走ります。データが少ない場合は問題ありませんが、本番環境の大規模テーブルには使わないようにしましょう。

EXPLAIN で確認する

MySQL – EXPLAIN で確認
EXPLAIN SELECT * FROM products ORDER BY RAND() LIMIT 5;
-- type: ALL(フルスキャン)
-- Extra: Using temporary; Using filesort
-- ← 一時テーブル作成 + 全行ソートが走る

大規模テーブルでの高速化テクニック

テーブルが大きい場合はORDER BY RAND()を避け、以下のパターンを使います。

パターン1:IDにオフセットを使う方式(MySQL)

IDが連番の場合に有効な高速パターンです。RAND() * MAX(id)でランダムなIDを生成し、それ以上のレコードを取得します。

MySQL – ID乱数方式(高速)
-- ランダムな1件を高速取得
SELECT * FROM products
WHERE id >= FLOOR(1 + RAND() * (SELECT MAX(id) FROM products))
LIMIT 1;

-- ランダムな5件を取得(重複の可能性あり)
SELECT p.*
FROM products p
JOIN (
    SELECT FLOOR(1 + RAND() * (SELECT MAX(id) FROM products)) AS rand_id
    FROM (SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
          UNION ALL SELECT 4 UNION ALL SELECT 5) AS n
) AS r ON p.id >= r.rand_id
GROUP BY r.rand_id
HAVING COUNT(*) > 0;
ID欠番がある場合の注意点
IDが連番でない場合(削除で欠番が発生している場合)、特定のIDに偏りが生じることがあります。厳密な均一性が必要な場合はサブクエリでROW_NUMBERを振り直すか、後述のTABLESAMPLE方式を検討してください。

パターン2:ROW_NUMBER()でランダムIDを指定する方式

IDに欠番がある場合でも均一なランダム取得が可能です。ウィンドウ関数のROW_NUMBER()を使い、連番を振り直してからランダムに選択します。

MySQL 8.0+ / PostgreSQL / SQL Server
WITH numbered AS (
    SELECT *,
           ROW_NUMBER() OVER (ORDER BY id) AS rn,
           COUNT(*) OVER ()                AS total
    FROM products
),
random_picks AS (
    SELECT FLOOR(1 + RAND() * total) AS pick
    FROM (SELECT DISTINCT total FROM numbered) t
    -- 5件分の乱数を生成
    CROSS JOIN (SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
                UNION ALL SELECT 4 UNION ALL SELECT 5) n
)
SELECT n.*
FROM numbered n
JOIN random_picks rp ON n.rn = rp.pick;

パターン3:TABLESAMPLEによるサンプリング(PostgreSQL / SQL Server / Oracle)

TABLESAMPLEは行単位ではなくページ(ブロック)単位でサンプリングするため、フルスキャンを回避でき高速です。

MySQLはTABLESAMPLE非対応
TABLESAMPLEはMySQL(MariaDB含む)では使えません。MySQLで大規模テーブルのランダム取得を高速化する場合は、前述のID乱数方式またはROW_NUMBER()方式を使ってください。
PostgreSQL – TABLESAMPLE
-- SYSTEM: ブロック単位サンプリング(高速・非均一)
-- 全行の約10%をサンプリング
SELECT * FROM products TABLESAMPLE SYSTEM (10);

-- BERNOULLI: 行単位サンプリング(均一・やや低速)
SELECT * FROM products TABLESAMPLE BERNOULLI (5)
ORDER BY RANDOM()
LIMIT 10;

-- REPEATABLE(seed) で再現可能なサンプリング
SELECT * FROM products
TABLESAMPLE SYSTEM (10) REPEATABLE (42);
SQL Server – TABLESAMPLE
-- 全行の約10%をサンプリング
SELECT * FROM products TABLESAMPLE (10 PERCENT);

-- 行数で指定する方法
SELECT * FROM products TABLESAMPLE (100 ROWS);

-- REPEATABLE(seed) で再現可能
SELECT * FROM products
TABLESAMPLE (10 PERCENT) REPEATABLE (12345);
Oracle – SAMPLE句
-- BLOCK SAMPLE: ブロック単位(高速)
SELECT * FROM products SAMPLE BLOCK (10);

-- ROW SAMPLE: 行単位(均一)
SELECT * FROM products SAMPLE (5);

-- シード付きサンプリング
SELECT * FROM products SAMPLE (10) SEED (42);
方式 DBMS 均一性 速度 再現性
SYSTEM PostgreSQL △(ブロック依存) ○(REPEATABLE)
BERNOULLI PostgreSQL ○(REPEATABLE)
TABLESAMPLE PERCENT SQL Server △(ブロック依存) ○(REPEATABLE)
SAMPLE BLOCK Oracle △(ブロック依存) ○(SEED)
SAMPLE(行) Oracle ○(SEED)
ORDER BY RAND() 全DBMS ×(N増で激遅) ○(シード付き)
TABLESAMPLEの適用シーン
TABLESAMPLEはデータ分析・統計サンプリングに向いています。ただし指定した割合は「目安」であり、毎回ぴったり同じ件数が返ってくるわけではありません。「ランダムにN件ちょうど取得したい」という要件にはORDER BY RAND() LIMIT Nか高速化パターンを使いましょう。

シード付きランダム(再現可能なランダム取得)

同じシード値を与えると毎回同じランダム順序が再現されます。テストやデバッグで「再現性のあるランダム」が必要な場合に使います。

MySQL – RAND(seed)
-- シード42を固定すると毎回同じ順序
SELECT * FROM products
ORDER BY RAND(42)
LIMIT 10;

-- 日付をシードにすることで「今日だけ固定」にする
SELECT * FROM products
ORDER BY RAND(UNIX_TIMESTAMP(CURDATE()))
LIMIT 5;
PostgreSQL – SETSEED()
-- SELECTの前にSETSEED()でシードを設定
-- SETSEED(-1.0 ~ 1.0 の範囲で指定)
SELECT SETSEED(0.42);
SELECT * FROM products
ORDER BY RANDOM()
LIMIT 10;
Oracle – DBMS_RANDOM.SEED
-- セッションレベルでシードを設定
BEGIN
    DBMS_RANDOM.SEED(42);
END;
/

SELECT * FROM products
ORDER BY DBMS_RANDOM.VALUE
FETCH FIRST 10 ROWS ONLY;
SQL Server – RAND(seed)
-- 整数シードを指定(0 ~ 2^31-1)
-- ただしNEWID()にシード指定はできないため
-- CHECKSUM(RAND(42), col) を使う
SELECT TOP 10 *
FROM products
ORDER BY CHECKSUM(RAND(42), id);

実践パターン集

パターン1:抽選システム(当選者をランダム選出)

抽選 – ランダムに当選者1名を選出
-- エントリーテーブルから1名をランダム抽選
SELECT user_id, user_name, entry_at
FROM lottery_entries
WHERE campaign_id = 100
  AND is_valid = 1
ORDER BY RAND()
LIMIT 1;

-- 当選者をトランザクション内で確定させる
BEGIN;
UPDATE lottery_entries
SET is_winner = 1
WHERE entry_id = (
    SELECT entry_id
    FROM (
        SELECT entry_id FROM lottery_entries
        WHERE campaign_id = 100 AND is_valid = 1
        ORDER BY RAND() LIMIT 1
    ) t
);
COMMIT;

パターン2:クイズのランダム出題

クイズ – カテゴリ別にランダム出題
-- カテゴリ「SQL基礎」から難易度別に3問ずつランダム出題
WITH ranked AS (
    SELECT *,
           ROW_NUMBER() OVER (
               PARTITION BY difficulty
               ORDER BY RAND()
           ) AS rn
    FROM quiz_questions
    WHERE category = 'SQL基礎'
      AND is_active = 1
)
SELECT question_id, difficulty, question_text
FROM ranked
WHERE rn <= 3
ORDER BY difficulty, rn;

パターン3:A/Bテストのユーザー振り分け

同じユーザーIDに対して常に同じグループが返るよう、IDベースのハッシュ関数を使います。純粋なRAND()と違い、「セッションをまたいでも同じグループ」を保証できます。

A/Bテスト – ユーザーIDベースの安定した振り分け
-- MySQL: MOD(CRC32(user_id), 2) で0=A群 / 1=B群
SELECT
    user_id,
    CASE MOD(CRC32(user_id), 2)
        WHEN 0 THEN 'A'
        ELSE 'B'
    END AS ab_group
FROM users;

-- PostgreSQL: MD5 + ハッシュで3グループ均等分割
SELECT
    user_id,
    CASE MOD(ABS(hashtext(user_id::text)), 3)
        WHEN 0 THEN 'A'
        WHEN 1 THEN 'B'
        ELSE 'C'
    END AS ab_group
FROM users;

パターン4:統計サンプリング(大規模データの品質チェック)

データ品質チェック用サンプリング
-- PostgreSQL: 100万件テーブルから1%をサンプリング
SELECT
    COUNT(*) AS sample_count,
    COUNT(email) AS email_filled,
    COUNT(phone) AS phone_filled,
    ROUND(100.0 * COUNT(email) / COUNT(*), 1) AS email_rate,
    ROUND(100.0 * COUNT(phone) / COUNT(*), 1) AS phone_rate
FROM customers
TABLESAMPLE BERNOULLI (1);

-- Oracle: 5%をサンプリングして年代別件数を確認
SELECT
    TRUNC(age / 10) * 10 AS age_group,
    COUNT(*)              AS cnt
FROM customers SAMPLE (5)
GROUP BY TRUNC(age / 10) * 10
ORDER BY age_group;

パターン5:ランダムなテストデータ生成

MySQL – ランダムなテストデータを一括生成
-- 名前・金額・日付をランダムに生成して100件INSERT
INSERT INTO orders (customer_name, amount, order_date)
SELECT
    CONCAT('Customer_', n),
    ROUND(1000 + RAND() * 99000),   -- 1,000〜100,000
    DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 365) DAY)  -- 過去1年
FROM (
    SELECT a.n + b.n * 10 AS n
    FROM (SELECT 1 n UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5
          UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 0) a
    CROSS JOIN
         (SELECT 0 n UNION SELECT 1 UNION SELECT 2 UNION SELECT 3
          UNION SELECT 4 UNION SELECT 5 UNION SELECT 6
          UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) b
    WHERE a.n + b.n * 10 BETWEEN 1 AND 100
) nums;
PostgreSQL – generate_series() でランダムデータ生成
INSERT INTO orders (customer_name, amount, order_date)
SELECT
    'Customer_' || n,
    ROUND((1000 + RANDOM() * 99000)::numeric, 0),
    CURRENT_DATE - (RANDOM() * 365)::int
FROM generate_series(1, 100) AS n;

各手法のパフォーマンス比較

手法 件数目安 均一性 DBMS 適用条件
ORDER BY RAND() LIMIT N 〜10万件 全DBMS 条件なし
WHERE id >= FLOOR(RAND() * MAX) 100万件〜 MySQL中心 IDが連番に近い
ROW_NUMBER() + 乱数JOIN 10万〜100万件 全DBMS ウィンドウ関数が使える
TABLESAMPLE SYSTEM 100万件〜 △(ブロック依存) PG / SS / Ora 割合指定でOK
TABLESAMPLE BERNOULLI 〜100万件 PostgreSQL 割合指定でOK
SAMPLE句 100万件〜 Oracle 割合指定でOK
使い分けのポイント
小〜中規模(10万件未満)はORDER BY RAND()で十分。大規模テーブルかつ件数ちょうどN件が必要な場合はROW_NUMBER()方式。割合でよければTABLESAMPLE/SAMPLE句が最速です。

よくある質問

QORDER BY RAND()は毎回違う結果になりますか?
Aはい、シードを指定しない場合は毎回異なる順序でデータが返ります。同じ結果を再現したい場合は、MySQLならORDER BY RAND(42)のようにシード値を指定してください。PostgreSQLは事前にSELECT SETSEED(0.42)を実行します。
QNULLがあるカラムでORDER BY RAND()を使っても問題ないですか?
A問題ありません。ORDER BY RAND()は各行に独立した乱数を付与するため、カラムの値がNULLでも影響を受けません。
QWHERE条件を組み合わせてランダムに取得できますか?
Aできます。WHERE category = 'electronics' ORDER BY RAND() LIMIT 10のように、WHERE句でフィルタしてからランダムソートできます。フィルタ後の件数が少なければパフォーマンスも問題になりにくいです。
Q重複なしで複数件をランダムに取得するには?
AORDER BY RAND() LIMIT Nは本来重複なしで取得します(異なる行が返る)。ただしIDベースの高速化パターンでは、JOINの仕方によっては重複が発生することがあります。その場合はGROUP BYDISTINCTで重複を除去するか、ROW_NUMBER()方式に切り替えましょう。
QOracleでDBMS_RANDOMを使う際の注意点は?
ADBMS_RANDOMはOracle標準パッケージです。通常のSQLでORDER BY DBMS_RANDOM.VALUEと書くだけで使えます。詳細な数値範囲指定やシード設定・文字列生成などはOracle DBMS_RANDOM完全ガイドをご参照ください。
QTABLESAMPLEで指定した割合ちょうどのデータが取れないのはなぜ?
ATABLESAMPLE SYSTEMはデータページ(ブロック)単位でサンプリングするため、取得件数はブロックサイズや実際のデータ分布によってばらつきます。例えばTABLESAMPLE (10 PERCENT)でも8%〜12%程度の幅が出ることがあります。厳密にN件取得したい場合は、TABLESAMPLEの後にORDER BY RANDOM() LIMIT Nを付けるか、ORDER BY RAND()系の方法を使ってください。

まとめ

SQLでランダムにデータを取得する方法は、要件と規模によって最適な手法が変わります。

  • 小〜中規模(10万件未満)ORDER BY RAND() LIMIT Nが最もシンプル
  • 大規模テーブルでN件ちょうど:ROW_NUMBER()方式でフルスキャンを回避
  • 大規模テーブルで割合指定:TABLESAMPLE / SAMPLE句が高速
  • 再現性が必要:シード付きRAND() / SETSEED() / TABLESAMPLE REPEATABLE
  • A/Bテストの安定した振り分け:CRC32 / hashtext などのハッシュ関数を使う

関連記事:ORDER BY完全ガイド(ソート順・NULL順・パフォーマンス)LIMIT句の使い方(OFFSET・ページネーション)WITH句(CTE)完全ガイドOracle DBMS_RANDOM完全ガイド