回答:
単純なものからギャップ、ギャップのある不均一なものまで、いくつかのケースを処理する優れたポスト。
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リストにギャップがある可能性があることを想定しています。より高度な例については、記事を参照してください
mysqli_fetch_assoc($result)
ますか?それとも、それらの10の結果は必ずしも区別できないのですか?
SELECT column FROM table
ORDER BY RAND()
LIMIT 10
効率的なソリューションではありませんが、機能します
ORDER BY RAND()
比較的遅い
SELECT words, transcription, translation, sound FROM vocabulary WHERE menu_id=$menuId ORDER BY RAND() LIMIT 10
0.0010かかり、LIMIT 10なしでは0.0012(そのテーブルでは3500ワード)かかりました。
優れたパフォーマンスを持ち、ギャップと連携する単純なクエリ:
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
遅い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);
?>
ORDER BY RAND()
FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler%';
それを見るために使用します。
ORDER BY RAND()
も優れている点は、行全体ではなくIDのみをソートするため、一時テーブルは小さくなりますが、すべてをソートする必要があることです。
本から:
オフセットを使用してランダムな行を選択する
前述の代替方法で見つかった問題を回避するさらに別の手法は、データセット内の行をカウントし、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();
このソリューションは、連続するキー値を想定できず、各行が選択される可能性があることを確認する必要がある場合に使用します。
SELECT count(*)
遅くなります。
テーブルからランダムな行を選択する方法:
ここから: MySQLでランダムな行を選択します
「テーブルスキャン」をすばやく改善するには、インデックスを使用してランダムなIDを取得します。
SELECT *
FROM random, (
SELECT id AS sid
FROM random
ORDER BY RAND( )
LIMIT 10
) tmp
WHERE random.id = tmp.sid;
PRIMARY KEY
)。
キーにギャップがなく、すべて数値である場合は、乱数を計算してそれらの行を選択できます。しかし、これはおそらく当てはまりません。
したがって、1つの解決策は次のようになります。
SELECT * FROM table WHERE key >= FLOOR(RAND()*MAX(id)) LIMIT 1
これは基本的に、キーの範囲で乱数を取得し、次に大きい方を選択することを保証します。これを10回行う必要があります。
ただし、キーが均等に配布されない可能性が高いため、これは実際にはランダムではありません。
これは本当に大きな問題であり、すべての要件を満たすのは簡単ではありません。MySQLのrand()は、本当に10個のランダムな行が必要な場合に取得できる最良の方法です。
ただし、別の解決策としては高速ですが、ランダム性に関してはトレードオフがありますが、より適切な解決策があります。ここでそれを読んでください: MySQLのORDER BY RAND()関数をどのように最適化できますか?
問題は、どれほどランダムにする必要があるかです。
もう少し詳しく説明してもらえますか?
たとえば、私が一緒に働いた会社には、非常に高速な絶対ランダム性を必要とするソリューションがありました。最終的に、降順で選択されたランダム値をデータベースに事前に入力し、その後、別のランダム値に再度設定しました。
更新することがほとんどない場合は、増分IDを入力してギャップをなくし、ランダムなキーを計算してから選択することができます...ユースケースによって異なります!
Id
と、すべてのあなたのランダムなクエリがあなたに1つを返しますId
。
FLOOR(RAND()*MAX(id))
より大きなIDを返す傾向があります。
かなり大きなテーブルから多数のランダムな行を返すクエリが必要でした。これが私が思いついたものです。最初に最大レコード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);
すべての最良の回答は既に投稿されています(主にリンクhttp://jan.kneschke.de/projects/mysql/order-by-rand/を参照する回答)。
別の高速化の可能性、つまりキャッシュを特定したいと思います。ランダムな行を取得する必要がある理由を考えてください。おそらくあなたはウェブサイトにランダムな投稿やランダムな広告を表示したいでしょう。100 req / sを取得している場合、各ビジターがランダムな行を取得することが本当に必要ですか?通常、これらのX個のランダムな行を1秒間(または10秒間)キャッシュしても問題ありません。同じ1秒間に100人のユニークビジターが同じランダムな投稿を取得するかどうかは関係ありません。次の1秒で別の100人のビジターが異なる投稿のセットを取得するためです。
このキャッシングを使用する場合、ランダムデータを取得するための遅いソリューションのいくつかを使用することもできます。ランダムデータは、リクエスト/秒に関係なく、MySQLから1秒に1回だけフェッチされるためです。
@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)
何が起こっているのかを解き放ちましょう。
@max := (SELECT MAX(id) FROM table)
MAX(id)
場合、行が必要になるたびに計算するためのわずかなオーバーヘッドがありますSELECT FLOOR(rand() * @max) + 1 as rand)
SELECT id FROM table INNER JOIN (...) on id > rand LIMIT 1
ユニオンを実行すると、すべてを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
LIMIT 1
する必要がありLIMIT 30
ます
LIMIT 1
するLIMIT 30
と、テーブル内のランダムなポイントから行に30レコードが取得されます。代わり(SELECT id FROM ....
に、真ん中に部品の30コピーが必要です。
Riedsio
答えるより効率的ではないようです。私は、centos 7でPHP 7.0.22とMariaDBを使用して、ページへの毎秒500ヒットで試してみましたRiedsio
。
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 filesort
とUsing where
元のクエリとは対照的に、サブクエリのための(インデックスなし)。
これは、多くの人にとって役立つかもしれないゲームチェンジャーです。
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.8418693542479
ms0.241041183472
ms0.216960906982
msこの結果に基づいて、注文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側のすべての操作を含む)。
LIMIT
少し増やすことをお勧めします-重複する可能性があります。
これは非常に高速で、ギャップがあっても100%ランダムです。
x
利用可能な行数を数えるSELECT COUNT(*) as rows FROM TABLE
a_1,a_2,...,a_10
0からまでの10個の異なる乱数を選びますx
SELECT * FROM TABLE LIMIT 1 offset a_i
for i = 1、...、10私は本の中で、このハックを発見したSQLアンチパターンからビルKarwin。
SELECT column FROM table ORDER BY RAND() LIMIT 10
はO(nlog(n))にあります。したがって、はい、これは断食されたソリューションであり、IDの任意のディストリビューションで機能します。
x
。これは10行のランダムな生成ではないと主張します。私の答えでは、ステップ3でクエリを10回実行する必要があります。つまり、実行ごとに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;
バージョン:テーブルをtmp_randorder
永続的に保持し、datatable_idlistと呼ぶことができます。穴も空くので、特定の間隔(日、時間)でそのテーブルを再作成します。テーブルが本当に大きくなった場合は、穴を補充することもできます
datatable_idlistからl.data_id全体を選択しますl左結合データテーブルdtをdt.id = l.data_idに追加します。ここで、dt.idはnullです。
バージョン:データセットに直接、または永続的な追加のテーブルに、random_sortorder列をデータセットに与えますdatatable_sortorder
。その列にインデックスを付けます。アプリケーションでランダム値を生成します(これをと呼びます$rand
)。
select l.*
from datatable l
order by abs(random_sortorder - $rand) desc
limit 1;
このソリューションは、random_sortorderが最高と最低の「エッジ行」を区別するため、間隔を空けて(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;
自動生成された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に書き込むシステムがバッチで行のセットを作成する場合、期待したようなランダムな結果が得られない可能性があります。
1つのランダムなレコードが必要な場合(ID間にギャップがあるかどうかに関係なく):
PREPARE stmt FROM 'SELECT * FROM `table_name` LIMIT 1 OFFSET ?';
SET @count = (SELECT
FLOOR(RAND() * COUNT(*))
FROM `table_name`);
EXECUTE stmt USING @count;
ソース:https : //www.warpconduit.net/2011/03/23/selecting-a-random-record-using-mysql-benchmark-results/#comment-1266
私はすべての答えを調べましたが、誰もこの可能性についてまったく言及していないと思います。その理由はわかりません。
わずかなコストで最大限の単純さと速度が必要な場合、私にとっては、DBの各行に対して乱数を格納することは理にかなっているようです。追加の列を作成し、random_number
それをデフォルトに設定しRAND()
ます。この列にインデックスを作成します。
次に、行を取得する場合、コード(PHP、Perlなど)で乱数を生成し、それを列と比較します。
SELECT FROM tbl WHERE random_number >= :random LIMIT 1
単一の行には非常にきちんとしていると思いますが、OPのように10行では、10回に分けて呼び出す必要がある(または、すぐに脱出できる巧妙な調整が必要になる)と尋ねられました。
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;
私はこのクエリを使用します:
select floor(RAND() * (SELECT MAX(key) FROM table)) from table limit 10
クエリ時間:0.016s
以下の簡単なクエリを使用して、テーブルからランダムなデータを取得します。
SELECT user_firstname ,
COUNT(DISTINCT usr_fk_id) cnt
FROM userdetails
GROUP BY usr_fk_id
ORDER BY cnt ASC
LIMIT 10
これが最善の方法だと思います。
SELECT id, id * RAND( ) AS random_no, first_name, last_name
FROM user
ORDER BY random_no