MySQLは60万行からランダムに10行を高速で選択します


回答:


386

単純なものからギャップ、ギャップのある不均一なものまで、いくつかのケースを処理する優れたポスト。

http://jan.kneschke.de/projects/mysql/order-by-rand/

最も一般的なケースでは、次のようにします。

SELECT name
  FROM random AS r1 JOIN
       (SELECT CEIL(RAND() *
                     (SELECT MAX(id)
                        FROM random)) AS id)
        AS r2
 WHERE r1.id >= r2.id
 ORDER BY r1.id ASC
 LIMIT 1

これは、IDの分布が等しく、IDリストにギャップがある可能性があることを想定しています。より高度な例については、記事を参照してください


52
はい、IDに大きなギャップがある可能性がある場合、最も低いIDがランダムに選択される可能性は、高いIDよりもはるかに低くなります。実際、最大のギャップが選択された後の最初のIDが実際に最も高い確率です。したがって、これは本質的にランダムではありません。
lukeocodes 2013年

6
どのようにして10の異なるランダムな行を取得しますか?制限を10に設定してから、10回繰り返す必要がありmysqli_fetch_assoc($result)ますか?それとも、それらの10の結果は必ずしも区別できないのですか?
アダム

12
ランダムは、私の心の中で、どんな結果でも平等な機会を必要とします。;)
lukeocodes 2014年

4
記事全体では、不平等な分布や繰り返しの結果などの問題を扱います。
Bradd Szonye

1
具体的には、IDの先頭にギャップがある場合、最初のIDが選択されます(最小/最大-最小)。その場合、単純な調整はMAX()-MIN()* RAND + MIN()であり、遅くなりすぎません。
Code Abominator 2014

343
SELECT column FROM table
ORDER BY RAND()
LIMIT 10

効率的なソリューションではありませんが、機能します


139
ORDER BY RAND()比較的遅い
Mateusz Charytoniuk

7
Mateusz-プルーフpls、SELECT words, transcription, translation, sound FROM vocabulary WHERE menu_id=$menuId ORDER BY RAND() LIMIT 100.0010かかり、LIMIT 10なしでは0.0012(そのテーブルでは3500ワード)かかりました。
Arthur Kushman 2013年

26
@zeusakm 3500語はそれほど多くありません。問題は、MySQLが実際にすべてのレコードを読み取った後にすべてのレコードをソートする必要があるため、特定のポイントを超えて爆発することです。その操作がハードディスクに到達すると、違いを感じることができます。
ジャック

16
繰り返したくありませんが、これも全表スキャンです。大きなテーブルでは非常に時間とメモリを消費し、ディスク上の一時テーブルでの作成と操作が非常に遅くなる可能性があります。
マット2013年

10
2010年にFacebookにインタビューしていたとき、彼らは私に一度に、サイズが不明な巨大なファイルからランダムなレコードを選択する方法を尋ねました。アイデアを思いついたら、それを一般化して複数のレコードを選択するのは簡単です。つまり、ファイル全体を並べ替えることはばかげています。同時に、それは非常に便利です。このアプローチを使用して、1,000,000以上の行を持つテーブルからランダムに10行を選択しました。もちろん、少し待たなければなりませんでした。しかし、この表の一般的な行がどのように見えるかを考えたかったのです...
osa

27

優れたパフォーマンスを持ち、ギャップと連携する単純なクエリ:

SELECT * FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY RAND() LIMIT 10) as t2 ON t1.id=t2.id

200Kテーブルの上にこのクエリが取る0.08sと(RAND()LIMIT 10 BY tblはORDER SELECT * FROM)ノーマルバージョンを取る0.35sを私のマシン上で。

並べ替えフェーズはインデックス付きID列のみを使用するため、これは高速です。この動作は説明で確認できます。

SELECT * FROM TBL ORDER BY RAND()LIMIT 10: 簡単な説明

SELECT * FROM tbl AS t1 JOIN(SELECT id FROM tbl ORDER BY RAND()LIMIT 10)as t2 ON t1.id = t2.id ここに画像の説明を入力してください

加重バージョンhttps : //stackoverflow.com/a/41577458/893432


1
申し訳ありませんが、私はテストしました!600kレコードのパフォーマンスが遅い。
ディランB

@DylanB私はテストで答えを更新しました。
アリ

17

遅いcpu高速のクエリ(約0.5秒)を取得しています。400Kで10個のランダムな行を選択すると、MySQLデータベースの非キャッシュ2Gbサイズが登録されます。ここに私のコードを参照してください:MySQLでのランダムな行の高速選択

<?php
$time= microtime_float();

$sql='SELECT COUNT(*) FROM pages';
$rquery= BD_Ejecutar($sql);
list($num_records)=mysql_fetch_row($rquery);
mysql_free_result($rquery);

$sql="SELECT id FROM pages WHERE RAND()*$num_records<20
   ORDER BY RAND() LIMIT 0,10";
$rquery= BD_Ejecutar($sql);
while(list($id)=mysql_fetch_row($rquery)){
    if($id_in) $id_in.=",$id";
    else $id_in="$id";
}
mysql_free_result($rquery);

$sql="SELECT id,url FROM pages WHERE id IN($id_in)";
$rquery= BD_Ejecutar($sql);
while(list($id,$url)=mysql_fetch_row($rquery)){
    logger("$id, $url",1);
}
mysql_free_result($rquery);

$time= microtime_float()-$time;

logger("num_records=$num_records",1);
logger("$id_in",1);
logger("Time elapsed: <b>$time segundos</b>",1);
?>

11
私の1,400万件を超えるレコードテーブルを考えると、これは同じくらい遅いORDER BY RAND()
Fabrizio

5
@snippetsofcodeあなたの場合-400k行は単純な「ORDER BY rand()」を使用できます。3つのクエリを使用したトリックは役に立ちません。「SELECT id、url FROM pages WHERE id IN(SELECT id FROM pages ORDER BY rand()LIMIT 10)」のように書き換えることができます
Roman Podlinov

4
あなたのテクニックはまだテーブルスキャンをします。FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler%';それを見るために使用します。
リックジェームズ

4
また、200リクエスト/秒のWebページでそのクエリを実行してみてください。同時実行はあなたを殺します。
Marki555 2015

@RomanPodlinovのプレーンよりORDER BY RAND()も優れている点は、行全体ではなくIDのみをソートするため、一時テーブルは小さくなりますが、すべてをソートする必要があることです。
Marki555 2015

16

その非常にシンプルで単一行のクエリ。

SELECT * FROM Table_Name ORDER BY RAND() LIMIT 0,10;

21
FYI、order by rand()テーブルが大きい場合は非常に遅いです
evilReiko

6

インデックスが大きい場合は、テーブルに適用する必要があります。
ムハンマドアジーム

1
インデックス作成はここでは役に立ちません。インデックスは非常に具体的なものに役立ち、このクエリはそれらの1つではありません。
Andrew

13

本から:

オフセットを使用してランダムな行を選択する

前述の代替方法で見つかった問題を回避するさらに別の手法は、データセット内の行をカウントし、0からカウントまでの乱数を返すことです。次に、データセットをクエリするときに、この数値をオフセットとして使用します

<?php
$rand = "SELECT ROUND(RAND() * (SELECT COUNT(*) FROM Bugs))";
$offset = $pdo->query($rand)->fetch(PDO::FETCH_ASSOC);
$sql = "SELECT * FROM Bugs LIMIT 1 OFFSET :offset";
$stmt = $pdo->prepare($sql);
$stmt->execute( $offset );
$rand_bug = $stmt->fetch();

このソリューションは、連続するキー値を想定できず、各行が選択される可能性があることを確認する必要がある場合に使用します。


1
非常に大きなテーブルの場合、SELECT count(*)遅くなります。
ハンスZ

7

テーブルからランダムな行を選択する方法:

ここから: MySQLでランダムな行を選択します

「テーブルスキャン」をすばやく改善するには、インデックスを使用してランダムなIDを取得します。

SELECT *
FROM random, (
        SELECT id AS sid
        FROM random
        ORDER BY RAND( )
        LIMIT 10
    ) tmp
WHERE random.id = tmp.sid;

1
MyISAMには役立ちますが、InnoDBには役立ちません(idがclusteredであると想定PRIMARY KEY)。
リックジェームズ

7

キーにギャップがなく、すべて数値である場合は、乱数を計算してそれらの行を選択できます。しかし、これはおそらく当てはまりません。

したがって、1つの解決策は次のようになります。

SELECT * FROM table WHERE key >= FLOOR(RAND()*MAX(id)) LIMIT 1

これは基本的に、キーの範囲で乱数を取得し、次に大きい方を選択することを保証します。これを10回行う必要があります。

ただし、キーが均等に配布されない可能性が高いため、これは実際にはランダムではありません。

これは本当に大きな問題であり、すべての要件を満たすのは簡単ではありません。MySQLのrand()は、本当に10個のランダムな行が必要な場合に取得できる最良の方法です。

ただし、別の解決策としては高速ですが、ランダム性に関してはトレードオフがありますが、より適切な解決策があります。ここでそれを読んでください: MySQLのORDER BY RAND()関数をどのように最適化できますか?

問題は、どれほどランダムにする必要があるかです。

もう少し詳しく説明してもらえますか?

たとえば、私が一緒に働いた会社には、非常に高速な絶対ランダム性を必要とするソリューションがありました。最終的に、降順で選択されたランダム値をデータベースに事前に入力し、その後、別のランダム値に再度設定しました。

更新することがほとんどない場合は、増分IDを入力してギャップをなくし、ランダムなキーを計算してから選択することができます...ユースケースによって異なります!


こんにちはジョー。この特定のケースでは、キーにギャップがあってはなりませんが、時間が経つと変更される可能性があります。そして、あなたの答えが機能している間、それは連続したランダムな10行を生成します(私が制限10を書いた場合)、そしていわばもっとランダム性を望みました。:) ありがとうございました。
Francisc

10が必要な場合は、なんらかの和集合を使用して10の一意の行を生成します。
johno

私が言ったことを これを10回実行する必要があります。ユニオンユニオンと組み合わせると、1つのクエリに含めることができます。2分前の私の補遺を参照してください。
Surrican

1
@TheSurrican、このソリューションはクールに見えますが、非常に欠陥があります。挿入してみちょうど1非常に大きいId、すべてのあなたのランダムなクエリがあなたに1つを返しますId
Pacerier 2015年

1
FLOOR(RAND()*MAX(id))より大きなIDを返す傾向があります。
リックジェームズ

3

かなり大きなテーブルから多数のランダムな行を返すクエリが必要でした。これが私が思いついたものです。最初に最大レコードIDを取得します。

SELECT MAX(id) FROM table_name;

次に、その値を次のように置き換えます。

SELECT * FROM table_name WHERE id > FLOOR(RAND() * max) LIMIT n;

ここで、maxはテーブルの最大レコードIDで、nは結果セットに含める行数です。レコードIDにギャップがないことが前提となっていますが、存在していても(まだ試していませんが)結果に影響することはないと思います。また、このストアドプロシージャをより一般的なものとして作成しました。返されるテーブル名と行数を渡します。Windows 2008、32GB、デュアル3GHz E5450でMySQL 5.5.38を実行していますが、17,361,264行のテーブルでは、1,000,000行を返すために〜.03秒/〜11秒でかなり一貫しています。(時間はMySQL Workbench 6.1のものです。好みに応じて、2番目のselectステートメントでFLOORの代わりにCEILを使用することもできます)

DELIMITER $$

USE [schema name] $$

DROP PROCEDURE IF EXISTS `random_rows` $$

CREATE PROCEDURE `random_rows`(IN tab_name VARCHAR(64), IN num_rows INT)
BEGIN

SET @t = CONCAT('SET @max=(SELECT MAX(id) FROM ',tab_name,')');
PREPARE stmt FROM @t;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @t = CONCAT(
    'SELECT * FROM ',
    tab_name,
    ' WHERE id>FLOOR(RAND()*@max) LIMIT ',
    num_rows);

PREPARE stmt FROM @t;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
$$

その後

CALL [schema name].random_rows([table name], n);

3

すべての最良の回答は既に投稿されています(主にリンクhttp://jan.kneschke.de/projects/mysql/order-by-rand/を参照する回答)。

別の高速化の可能性、つまりキャッシュを特定したいと思います。ランダムな行を取得する必要がある理由を考えてください。おそらくあなたはウェブサイトにランダムな投稿やランダムな広告を表示したいでしょう。100 req / sを取得している場合、各ビジターがランダムな行を取得することが本当に必要ですか?通常、これらのX個のランダムな行を1秒間(または10秒間)キャッシュしても問題ありません。同じ1秒間に100人のユニークビジターが同じランダムな投稿を取得するかどうかは関係ありません。次の1秒で別の100人のビジターが異なる投稿のセットを取得するためです。

このキャッシングを使用する場合、ランダムデータを取得するための遅いソリューションのいくつかを使用することもできます。ランダムデータは、リクエスト/秒に関係なく、MySQLから1秒に1回だけフェッチされるためです。


3

@Riedsioの答えを改善しました。これは、ギャップがあり、均一に分散された大きなテーブル見つけることができる最も効率的なクエリです(行が2.6Bを超えるテーブルから1000個のランダムな行を取得することでテストされています)。

(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max := (SELECT MAX(id) FROM table)) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1)

何が起こっているのかを解き放ちましょう。

  1. @max := (SELECT MAX(id) FROM table)
    • 最大を計算して保存しています。非常に大きなテーブルのMAX(id)場合、行が必要になるたびに計算するためのわずかなオーバーヘッドがあります
  2. SELECT FLOOR(rand() * @max) + 1 as rand)
    • ランダムなIDを取得します
  3. SELECT id FROM table INNER JOIN (...) on id > rand LIMIT 1
    • これはギャップを埋めます。基本的に、ギャップの数値をランダムに選択すると、次のIDが選択されます。ギャップが均一に分布していると仮定すると、これは問題になりません。

ユニオンを実行すると、すべてを1つのクエリに収めることができるため、複数のクエリを実行する必要がなくなります。また、計算のオーバーヘッドを節約できますMAX(id)。アプリケーションによっては、これは非常に重要な場合と非常に重要な場合があります。

これはIDのみを取得し、ランダムな順序で取得することに注意してください。もっと高度なことをしたい場合は、次のようにすることをお勧めします。

SELECT t.id, t.name -- etc, etc
FROM table t
INNER JOIN (
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max := (SELECT MAX(id) FROM table)) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1)
) x ON x.id = t.id
ORDER BY t.id

30個のランダムなレコードが必要なので、クエリのすべての場所に変更LIMIT 1する必要がありLIMIT 30ます
Hassaan 2017

@Hassaanすべきではありません。これに変更LIMIT 1するLIMIT 30と、テーブル内のランダムなポイントから行に30レコードが取得されます。代わり(SELECT id FROM ....に、真ん中に部品の30コピーが必要です。
Hans Z

私は試しましたが、Riedsio答えるより効率的ではないようです。私は、centos 7でPHP 7.0.22とMariaDBを使用して、ページへの毎秒500ヒットで試してみましたRiedsio
Hassaan 2017

1
@Hassaan riedsioの答えは1行を与え、これはn行を与え、クエリのためのI / Oオーバーヘッドを削減します。行をより速く取得できる可能性がありますが、システムの負荷が高くなります。
ハンスZ

3

Riedsioが投稿したhttp://jan.kneschke.de/projects/mysql/order-by-rand/を使用しました(1つ以上のランダムな値を返すストアドプロシージャの場合を使用しました):

   DROP TEMPORARY TABLE IF EXISTS rands;
   CREATE TEMPORARY TABLE rands ( rand_id INT );

    loop_me: LOOP
        IF cnt < 1 THEN
          LEAVE loop_me;
        END IF;

        INSERT INTO rands
           SELECT r1.id
             FROM random AS r1 JOIN
                  (SELECT (RAND() *
                                (SELECT MAX(id)
                                   FROM random)) AS id)
                   AS r2
            WHERE r1.id >= r2.id
            ORDER BY r1.id ASC
            LIMIT 1;

        SET cnt = cnt - 1;
      END LOOP loop_me;

この記事では、(トリガーなどを使用して)テーブルを維持することにより、ID のギャップそれほどランダムではない結果を引き起こす問題を解決しています。テーブルに別の列を追加することで問題を解決し、1から始まる連続した番号を入力します(編集:この列は、実行時にサブクエリによって作成された一時テーブルに追加されますが、永続テーブルには影響しません)。

   DROP TEMPORARY TABLE IF EXISTS rands;
   CREATE TEMPORARY TABLE rands ( rand_id INT );

    loop_me: LOOP
        IF cnt < 1 THEN
          LEAVE loop_me;
        END IF;

        SET @no_gaps_id := 0;

        INSERT INTO rands
           SELECT r1.id
             FROM (SELECT id, @no_gaps_id := @no_gaps_id + 1 AS no_gaps_id FROM random) AS r1 JOIN
                  (SELECT (RAND() *
                                (SELECT COUNT(*)
                                   FROM random)) AS id)
                   AS r2
            WHERE r1.no_gaps_id >= r2.id
            ORDER BY r1.no_gaps_id ASC
            LIMIT 1;

        SET cnt = cnt - 1;
      END LOOP loop_me;

記事では、彼がコードを最適化するために多大な努力をしたことがわかります。私の変更がパフォーマンスにどの程度影響するかはわかりませんが、私にとっては非常にうまく機能します。


「自分の変更がパフォーマンスにどの程度影響するのか、またはどれだけ影響があるのか​​、私にはアイデアがありません」-かなりの量です。以下のために@no_gaps_idなしインデックスを使用することができますあなたが見れば、そのEXPLAINクエリのため、あなたが持っているUsing filesortUsing where元のクエリとは対照的に、サブクエリのための(インデックスなし)。
Fabian Schmengler、2015

2

これは、多くの人にとって役立つかもしれないゲームチェンジャーです。

200k行のテーブルがあり、シーケンシャルIDがありN個のランダムな行を選択する必要があったので、テーブル内の最大のIDに基づいてランダムな値を生成することにしました。このスクリプトを作成して、どちらが最速の操作であるかを調べました。

logTime();
query("SELECT COUNT(id) FROM tbl");
logTime();
query("SELECT MAX(id) FROM tbl");
logTime();
query("SELECT id FROM tbl ORDER BY id DESC LIMIT 1");
logTime();

結果は次のとおりです。

  • カウント:36.8418693542479ms
  • 最大:0.241041183472ms
  • 次数:0.216960906982ms

この結果に基づいて、注文descは最大IDを取得する最速の操作
です。

SELECT GROUP_CONCAT(n SEPARATOR ',') g FROM (
    SELECT FLOOR(RAND() * (
        SELECT id FROM tbl ORDER BY id DESC LIMIT 1
    )) n FROM tbl LIMIT 10) a

...
SELECT * FROM tbl WHERE id IN ($result);

参考:200kテーブルから10個のランダムな行を取得するには、1.78 ミリ秒 かかりました(php側のすべての操作を含む)。


3
LIMIT少し増やすことをお勧めします-重複する可能性があります。
リックジェームズ

2

これは非常に高速で、ギャップがあっても100%ランダムです。

  1. x利用可能な行数を数えるSELECT COUNT(*) as rows FROM TABLE
  2. a_1,a_2,...,a_100からまでの10個の異なる乱数を選びますx
  3. 次のように行をクエリします:SELECT * FROM TABLE LIMIT 1 offset a_ifor i = 1、...、10

私は本の中で、このハックを発見したSQLアンチパターンからビルKarwin


私は同じ解決策について考えていました、教えてください、他の方法よりも速いですか?
G. Adnane

@ G.Adnaneは、受け入れられた回答よりも速くも遅くもありませんが、受け入れられた回答は、IDの均等な分配を前提としています。これが保証されるシナリオを想像することはできません。このソリューションはO(1)にあり、ソリューションSELECT column FROM table ORDER BY RAND() LIMIT 10はO(nlog(n))にあります。したがって、はい、これは断食されたソリューションであり、IDの任意のディストリビューションで機能します。
Adam

いいえ、受け入れられた解決策について投稿されたリンクには他の方法があります。この解決策が他の方法よりも速いかどうかを知りたいのですが、他の方法で、他の方法を見つけようとすることができます。あなたの答えのために。私は同じものを使用していました
G. Adnane

行数xを取得したいが、オフセットがテーブルの最後に行き、<x行または1行のみを返す場合があります。私が投稿する前に私はあなたの答えを見ませんでしたが、ここでそれをより明確にしましたstackoverflow.com/a/59981772/10387008
ZOLDIK

@ZOLDIKオフセット後の最初の10行を選択しているようですx。これは10行のランダムな生成ではないと主張します。私の答えでは、ステップ3でクエリを10回実行する必要があります。つまり、実行ごとに1行だけを取得し、オフセットがテーブルの最後にあるかどうかを心配する必要はありません。
アダム

1

読み取り要求が1つしかない場合

@redsioの回答を一時テーブルと組み合わせます(600Kはそれほど多くありません)。

DROP TEMPORARY TABLE IF EXISTS tmp_randorder;
CREATE TABLE tmp_randorder (id int(11) not null auto_increment primary key, data_id int(11));
INSERT INTO tmp_randorder (data_id) select id from datatable;

そして、@ redsios Answerのバージョンを取得します。

SELECT dt.*
FROM
       (SELECT (RAND() *
                     (SELECT MAX(id)
                        FROM tmp_randorder)) AS id)
        AS rnd
 INNER JOIN tmp_randorder rndo on rndo.id between rnd.id - 10 and rnd.id + 10
 INNER JOIN datatable AS dt on dt.id = rndo.data_id
 ORDER BY abs(rndo.id - rnd.id)
 LIMIT 1;

テーブルが大きい場合は、最初の部分をふるいにかけることができます。

INSERT INTO tmp_randorder (data_id) select id from datatable where rand() < 0.01;

読み取り要求が多い場合

  1. バージョン:テーブルをtmp_randorder永続的に保持し、datatable_idlistと呼ぶことができます。穴も空くので、特定の間隔(日、時間)でそのテーブルを再作成します。テーブルが本当に大きくなった場合は、穴を補充することもできます

    datatable_idlistからl.data_id全体を選択しますl左結合データテーブルdtをdt.id = l.data_idに追加します。ここで、dt.idはnullです。

  2. バージョン:データセットに直接、または永続的な追加のテーブルに、random_sortorder列をデータセットに与えますdatatable_sortorder。その列にインデックスを付けます。アプリケーションでランダム値を生成します(これをと呼びます$rand)。

    select l.*
    from datatable l 
    order by abs(random_sortorder - $rand) desc 
    limit 1;

このソリューションは、random_sortorderが最高と最低の「エッジ行」を区別するため、間隔を空けて(1日1回)並べ替えます。


1

別の簡単な解決策は、行をランク付けしてそれらの1つをランダムにフェッチすることです。この解決策では、テーブルに「Id」ベースの列を含める必要はありません。

SELECT d.* FROM (
SELECT  t.*,  @rownum := @rownum + 1 AS rank
FROM mytable AS t,
    (SELECT @rownum := 0) AS r,
    (SELECT @cnt := (SELECT RAND() * (SELECT COUNT(*) FROM mytable))) AS n
) d WHERE rank >= @cnt LIMIT 10;

必要に応じて行にアクセスする必要に応じて制限値を変更できますが、ほとんどの場合は連続した値になります。

ただし、連続したランダムな値が必要ない場合は、より大きなサンプルをフェッチして、そこからランダムに選択できます。何かのようなもの ...

SELECT * FROM (
SELECT d.* FROM (
    SELECT  c.*,  @rownum := @rownum + 1 AS rank
    FROM buildbrain.`commits` AS c,
        (SELECT @rownum := 0) AS r,
        (SELECT @cnt := (SELECT RAND() * (SELECT COUNT(*) FROM buildbrain.`commits`))) AS rnd
) d 
WHERE rank >= @cnt LIMIT 10000 
) t ORDER BY RAND() LIMIT 10;

1

自動生成されたIDがある場合にかなり良い方法の1つは、モジュロ演算子 '%'を使用することです。たとえば、70,000のうち10,000のランダムなレコードが必要な場合、7行ごとに1行が必要であると言うことでこれを単純化できます。これは、このクエリで簡略化できます。

SELECT * FROM 
    table 
WHERE 
    id % 
    FLOOR(
        (SELECT count(1) FROM table) 
        / 10000
    ) = 0;

利用可能な合計でターゲット行を除算した結果が整数でない場合、要求したものよりも余分な行があるため、次のようにLIMIT句を追加して、結果セットをトリムできるようにする必要があります。

SELECT * FROM 
    table 
WHERE 
    id % 
    FLOOR(
        (SELECT count(1) FROM table) 
        / 10000
    ) = 0
LIMIT 10000;

これにはフルスキャンが必要ですが、ORDER BY RANDよりも高速で、私の意見では、このスレッドで言及されている他のオプションよりも理解が簡単です。また、DBに書き込むシステムがバッチで行のセットを作成する場合、期待したようなランダムな結果が得られない可能性があります。


2
今私はそう思うので、呼び出すたびにランダムな行が必要な場合、これは役に立ちません。いくつかの調査を行うために、セットからランダムな行を取得する必要性についてのみ考えていました。それでも、モジュロは他の場合に役立つと思います。モジュロを最初のパスフィルターとして使用して、ORDER BY RAND操作のコストを下げることができます。
Nicolas Cohen


1

私はすべての答えを調べましたが、誰もこの可能性についてまったく言及していないと思います。その理由はわかりません。

わずかなコストで最大限の単純さと速度が必要な場合、私にとっては、DBの各行に対して乱数を格納することは理にかなっているようです。追加の列を作成し、random_numberそれをデフォルトに設定しRAND()ます。この列にインデックスを作成します。

次に、行を取得する場合、コード(PHP、Perlなど)で乱数を生成し、それを列と比較します。

SELECT FROM tbl WHERE random_number >= :random LIMIT 1

単一の行には非常にきちんとしていると思いますが、OPのように10行では、10回に分けて呼び出す必要がある(または、すぐに脱出できる巧妙な調整が必要になる)と尋ねられました。


これは実際には非常に優れた効率的なアプローチです。唯一の欠点は、速度と引き換えにスペースを交換したという事実です。これは私の意見では公正な取引のようです。
Tochukwu Nkemdilim

ありがとう。ランダムな行が必要なメインテーブルに500万行と非常に多くの結合があるというシナリオがあり、この質問でほとんどのアプローチを試した後、これは私が解決した問題でした。もう1つの列は、私にとって非常に価値のあるトレードオフでした。
Codemonkey

0

以下は高速で、偏りがなく、ID列から独立している必要があります。ただし、返される行数が要求された行数と一致することは保証されません。

SELECT *
FROM t
WHERE RAND() < (SELECT 10 / COUNT(*) FROM t)

説明:100行のうち10行が必要だとすると、各行はSELECTされる確率が1/10になりWHERE RAND() < 0.1ます。このアプローチは10行を保証するものではありません。ただし、クエリが十分な回数実行されると、実行ごとの平均行数は約10になり、テーブルの各行が均等に選択されます。


0

制限付きのランダムオフセットを簡単に使用できます

PREPARE stm from 'select * from table limit 10 offset ?';
SET @total = (select count(*) from table);
SET @_offset = FLOOR(RAND() * @total);
EXECUTE stm using @_offset;

次のようにwhere句を適用することもできます

PREPARE stm from 'select * from table where available=true limit 10 offset ?';
SET @total = (select count(*) from table where available=true);
SET @_offset = FLOOR(RAND() * @total);
EXECUTE stm using @_offset;

600,000行(700MB)でテストすると、テーブルクエリの実行に約0.016秒かかりましたHDDドライブ-

編集
   -オフセットは、テーブルの最後に近い値をとる可能性があり、その結果、selectステートメントが返す行が少なくなります(おそらく1つだけ)行)、これを回避するためoffsetに、宣言後に再度チェックすることができます。

SET @rows_count = 10;
PREPARE stm from "select * from table where available=true limit ? offset ?";
SET @total = (select count(*) from table where available=true);
SET @_offset = FLOOR(RAND() * @total);
SET @_offset = (SELECT IF(@total-@_offset<@rows_count,@_offset-@rows_count,@_offset));
SET @_offset = (SELECT IF(@_offset<0,0,@_offset));
EXECUTE stm using @rows_count,@_offset;

-1

私はこのクエリを使用します:

select floor(RAND() * (SELECT MAX(key) FROM table)) from table limit 10

クエリ時間:0.016s


1、2、9、15のようなPKを持つ。上記のクエリでは、4、7、14、11のような行では不十分です。
Junaid Atari 2017

-2

これが私のやり方です:

select * 
from table_with_600k_rows
where rand() < 10/600000
limit 10

他のテーブルを必要とせず、簡単に記述でき、実行が非常に速いため、私はそれが好きです。


5
これは全表スキャンであり、インデックスを使用しません。大きなテーブルと忙しい環境では大したことはありません。
マット

-2

以下の簡単なクエリを使用して、テーブルからランダムなデータを取得します。

SELECT user_firstname ,
COUNT(DISTINCT usr_fk_id) cnt
FROM userdetails 
GROUP BY usr_fk_id 
ORDER BY cnt ASC  
LIMIT 10

結合ステートメントとフィルターを使用する場合は、使用できます。
MANOJ 2015

3
クエリのどの部分からランダムネスを取得しますか?
Marki555 2015

-4

これが最善の方法だと思います。

SELECT id, id * RAND( ) AS random_no, first_name, last_name
FROM user
ORDER BY random_no

8
地獄いいえ、それはテーブルからランダムな行を取得するための最悪の方法の1つです。これは、フルテーブルスキャン+ filesort + tmpテーブル=パフォーマンスの低下です。
マット

1
パフォーマンス以外にも、完全にランダムというわけではありません。単に乱数で並べるのではなく、IDと乱数の積で並べ替えます。つまり、IDが小さい行は、結果セットの最初の方に表示されるようにバイアスされます。
マークアメリー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.