純粋な SQL でランダムな行 (または可能な限り真にランダムに近い行) を要求するにはどうすればよいですか?
30 に答える
この投稿を参照してください: SQL to Select a random row from a database table . MySQL、PostgreSQL、Microsoft SQL Server、IBM DB2、および Oracle でこれを行うためのメソッドを使用します (以下はそのリンクからコピーされます)。
MySQL でランダムな行を選択します。
SELECT column FROM table
ORDER BY RAND()
LIMIT 1
PostgreSQL でランダムな行を選択します。
SELECT column FROM table
ORDER BY RANDOM()
LIMIT 1
Microsoft SQL Server でランダムな行を選択します。
SELECT TOP 1 column FROM table
ORDER BY NEWID()
IBM DB2 でランダムな行を選択する
SELECT column, RAND() as IDX
FROM table
ORDER BY IDX FETCH FIRST 1 ROWS ONLY
Oracle でランダムなレコードを選択します。
SELECT column FROM
( SELECT column FROM table
ORDER BY dbms_random.value )
WHERE rownum = 1
ジェレミーのようなソリューション:
SELECT * FROM table ORDER BY RAND() LIMIT 1
動作しますが、すべてのテーブルの順次スキャンが必要です (各行に関連付けられたランダム値を計算する必要があるため、最小のものを決定できるようにするため)。これは、中規模のテーブルでも非常に遅くなる可能性があります。私の推奨は、ある種のインデックス付きの数値列を使用し (多くのテーブルではこれらが主キーとして使用されます)、次のように記述します。
SELECT * FROM table WHERE num_value >= RAND() *
( SELECT MAX (num_value ) FROM table )
ORDER BY num_value LIMIT 1
num_value
これは、インデックスが作成されている場合、テーブルのサイズに関係なく、対数時間で機能します。num_value
1 つの注意点: これは、が範囲内で均等に分布していることを前提としています0..MAX(num_value)
。データセットがこの仮定から大きく外れている場合、歪んだ結果が得られます (一部の行が他の行よりも頻繁に表示されます)。
これがどれほど効率的かはわかりませんが、以前に使用したことがあります。
SELECT TOP 1 * FROM MyTable ORDER BY newid()
GUID はかなりランダムであるため、順序はランダムな行を取得することを意味します。
ORDER BY NEWID()
かかります7.4 milliseconds
WHERE num_value >= RAND() * (SELECT MAX(num_value) FROM table)
かかります0.0065 milliseconds
!
私は間違いなく後者の方法で行きます。
使用しているサーバーについては言及していません。古いバージョンの SQL Server では、これを使用できます。
select top 1 * from mytable order by newid()
SQL Server 2005 以降では、TABLESAMPLE
繰り返し可能なランダム サンプルを取得するために使用できます。
SELECT FirstName, LastName
FROM Contact
TABLESAMPLE (1 ROWS) ;
SQL サーバーの場合
newid()/order by は機能しますが、すべての行の ID を生成して並べ替える必要があるため、大きな結果セットの場合は非常にコストがかかります。
TABLESAMPLE() はパフォーマンスの観点からは優れていますが、結果がまとまってしまいます (ページ上のすべての行が返されます)。
真のランダム サンプルのパフォーマンスを向上させる最善の方法は、行をランダムに除外することです。SQL Server Books Online の記事Limiting Results Sets by Using TABLESAMPLEで次のコード サンプルを見つけました。
個々の行のランダム サンプルが本当に必要な場合は、TABLESAMPLE を使用する代わりに、行をランダムに除外するようにクエリを変更します。たとえば、次のクエリは NEWID 関数を使用して、Sales.SalesOrderDetail テーブルの行の約 1% を返します。
SELECT * FROM Sales.SalesOrderDetail WHERE 0.01 >= CAST(CHECKSUM(NEWID(),SalesOrderID) & 0x7fffffff AS float) / CAST (0x7fffffff AS int)
SalesOrderID 列が CHECKSUM 式に含まれているため、NEWID() は行ごとに 1 回評価され、行ごとにサンプリングが行われます。式 CAST(CHECKSUM(NEWID(), SalesOrderID) & 0x7fffffff AS float / CAST (0x7ffffffff AS int) は、0 と 1 の間のランダムな float 値に評価されます。
1,000,000 行のテーブルに対して実行すると、次の結果が得られます。
SET STATISTICS TIME ON
SET STATISTICS IO ON
/* newid()
rows returned: 10000
logical reads: 3359
CPU time: 3312 ms
elapsed time = 3359 ms
*/
SELECT TOP 1 PERCENT Number
FROM Numbers
ORDER BY newid()
/* TABLESAMPLE
rows returned: 9269 (varies)
logical reads: 32
CPU time: 0 ms
elapsed time: 5 ms
*/
SELECT Number
FROM Numbers
TABLESAMPLE (1 PERCENT)
/* Filter
rows returned: 9994 (varies)
logical reads: 3359
CPU time: 641 ms
elapsed time: 627 ms
*/
SELECT Number
FROM Numbers
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), Number) & 0x7fffffff AS float)
/ CAST (0x7fffffff AS int)
SET STATISTICS IO OFF
SET STATISTICS TIME OFF
TABLESAMPLE の使用を回避できれば、最高のパフォーマンスが得られます。それ以外の場合は、newid()/filter メソッドを使用してください。結果セットが大きい場合は、newid()/order by を最後の手段にする必要があります。
可能であれば、ストアド ステートメントを使用して、RND() のインデックスとレコード番号フィールドの作成の両方の非効率性を回避してください。
PREPARE RandomRecord FROM "SELECT * FROM table LIMIT ?,1"; SET @n=FLOOR(RAND()*(SELECT COUNT(*) FROM テーブル)); EXECUTE RandomRecord USING @n;
遅くなりましたが、Google経由でここにたどり着いたので、後世のために別の解決策を追加します。
もう 1 つの方法は、順序を交互に変えて TOP を 2 回使用することです。TOPで変数を使用するため、「純粋なSQL」かどうかはわかりませんが、SQL Server 2008で機能します。ランダムな単語が必要な場合に、辞書の単語のテーブルに対して使用する例を次に示します。
SELECT TOP 1
word
FROM (
SELECT TOP(@idx)
word
FROM
dbo.DictionaryAbridged WITH(NOLOCK)
ORDER BY
word DESC
) AS D
ORDER BY
word ASC
もちろん、@idx は、ターゲット テーブルで 1 から COUNT(*) までのランダムに生成された整数です。列にインデックスが付けられている場合は、その恩恵も受けます。もう 1 つの利点は、NEWID() が許可されていないため、関数で使用できることです。
最後に、上記のクエリは、同じテーブルに対する NEWID() タイプのクエリの実行時間の約 1/10 で実行されます。YYMV。
SQL Server 2005 および 2008 で、個々の行のランダム サンプルが必要な場合 ( Books Onlineから):
SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), SalesOrderID) & 0x7fffffff AS float)
/ CAST (0x7fffffff AS int)
最善の方法は、その目的のためだけにランダムな値を新しい列に入れ、次のようなものを使用することです (疑似コード + SQL):
randomNo = random()
execSql("SELECT TOP 1 * FROM MyTable WHERE MyTable.Randomness > $randomNo")
これは、MediaWiki コードで採用されているソリューションです。もちろん、小さい値に対するバイアスはありますが、行がフェッチされない場合は、ランダムな値をゼロにラップするだけで十分であることがわかりました。
newid() ソリューションでは、各行に新しい GUID を割り当てることができるように、完全なテーブル スキャンが必要になる場合がありますが、これはパフォーマンスが大幅に低下します。
関数が一度だけ評価され、すべての行に同じ「乱数」が割り当てられるため、rand() ソリューションは (つまり、MSSQL では) まったく機能しない可能性があります。
MySQL でランダム レコードを取得するには
SELECT name
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
@cnuの回答に対する@BillKarwinのコメントで指摘されているように...
LIMIT と組み合わせると、実際の行を直接並べ替えるよりも、ランダムな順序で JOIN を実行する方が (少なくとも PostgreSQL 9.1 では) パフォーマンスがはるかに優れていることがわかりました。
SELECT * FROM tbl_post AS t
JOIN ...
JOIN ( SELECT id, CAST(-2147483648 * RANDOM() AS integer) AS rand
FROM tbl_post
WHERE create_time >= 1349928000
) r ON r.id = t.id
WHERE create_time >= 1349928000 AND ...
ORDER BY r.rand
LIMIT 100
「r」が結合される複雑なクエリのすべての可能なキー値に対して「rand」値を生成することを確認してください。ただし、可能な場合は「r」の行数を制限してください。
CAST as Integer は、整数および単精度浮動小数点型に対して特定のソート最適化を行う PostgreSQL 9.2 で特に役立ちます。
ここでのソリューションのほとんどは、並べ替えを回避することを目的としていますが、それでもテーブルを順次スキャンする必要があります。
インデックススキャンに切り替えることでシーケンシャルスキャンを回避する方法もあります。ランダム行のインデックス値がわかっている場合は、ほぼ瞬時に結果を取得できます。問題は、インデックス値を推測する方法です。
次のソリューションは、PostgreSQL 8.4 で機能します。
explain analyze select * from cms_refs where rec_id in
(select (random()*(select last_value from cms_refs_rec_id_seq))::bigint
from generate_series(1,10))
limit 1;
上記のソリューションでは、範囲 0 .. [id の最後の値] から 10 個のさまざまなランダム インデックス値を推測します。
10 という数字は任意です。(驚くべきことに) 応答時間に大きな影響を与えないため、100 または 1000 を使用できます。
また、問題が 1 つあります。ID がまばらな場合、見逃してしまう可能性があります。解決策は、バックアップ計画を立てることです:) この場合、random() クエリによる純粋な古い順序です。結合された id は次のようになります。
explain analyze select * from cms_refs where rec_id in
(select (random()*(select last_value from cms_refs_rec_id_seq))::bigint
from generate_series(1,10))
union all (select * from cms_refs order by random() limit 1)
limit 1;
union ALL句ではありません。この場合、最初の部分がデータを返す場合、2 番目の部分は決して実行されません!
関数を使ってみることもできnew id()
ます。
クエリを記述し、order bynew id()
関数を使用するだけです。それはかなりランダムです。
MSSQL(11.0.5569でテスト済み)で
SELECT TOP 100 * FROM employee ORDER BY CRYPT_GEN_RANDOM(10)
よりも大幅に高速です
SELECT TOP 100 * FROM employee ORDER BY NEWID()
SQL Server では、TABLESAMPLE と NEWID() を組み合わせて、非常に優れたランダム性を実現しながら速度を維持できます。これは、本当に 1 つまたは少数の行だけが必要な場合に特に便利です。
SELECT TOP 1 * FROM [table]
TABLESAMPLE (500 ROWS)
ORDER BY NEWID()
dbms_random.value を使用する代わりに Oracle 用のより良い解決策がありますが、dbms_random.value で行を並べ替えるにはフル スキャンが必要であり、大きなテーブルでは非常に遅くなります。
代わりにこれを使用してください:
SELECT *
FROM employee sample(1)
WHERE rownum=1
TableSample は実際には行のランダム サンプルを返さないので注意してください。行を構成する 8KB ページのランダムなサンプルを参照するようにクエリに指示します。次に、これらのページに含まれるデータに対してクエリが実行されます。これらのページでデータがグループ化される方法 (挿入順序など) が原因で、実際にはランダム サンプルではないデータになる可能性があります。
参照: http://www.mssqltips.com/tip.asp?tip=1308
この TableSample の MSDN ページには、データの実際のランダム サンプルを生成する方法の例が含まれています。
SELECT * FROM table ORDER BY RAND() LIMIT 1
CD-MaN に同意する必要があります。「ORDER BY RAND()」を使用すると、小さなテーブルや、SELECT を数回しか実行しない場合にうまく機能します。
また、「num_value >= RAND() * ...」手法も使用します。本当にランダムな結果が必要な場合は、テーブルに特別な「ランダム」列を作成し、1 日に 1 回程度更新します。その 1 回の UPDATE の実行には時間がかかりますが (特に、その列にインデックスを作成する必要があるため)、select が実行されるたびにすべての行に乱数を作成するよりもはるかに高速です。
リストされているアイデアの多くは、まだ順序付けを使用しているようです
ただし、一時テーブルを使用する場合は、(多くのソリューションで提案されているように) ランダムなインデックスを割り当ててから、0 から 1 の間の任意の数値より大きい最初のインデックスを取得できます。
例 (DB2 の場合):
WITH TEMP AS (
SELECT COMLUMN, RAND() AS IDX FROM TABLE)
SELECT COLUMN FROM TABLE WHERE IDX > .5
FETCH FIRST 1 ROW ONLY