純粋なSQLでランダムな行(または可能な限り真にランダムな行に近い行)を要求するにはどうすればよいですか?
純粋なSQLでランダムな行(または可能な限り真にランダムな行に近い行)を要求するにはどうすればよいですか?
回答:
この投稿を参照してください:データベーステーブルからランダムな行を選択するSQL。これは、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
order by rand()
、すべてのdb に依存している、または同等のものである場合:|。ここにも触れます。
O(n)
とn
、テーブル内のレコードの数です。100万件のレコードがあるとします。100万件の乱数または一意のIDを本当に生成しますか?私はむしろそれを使用して、単一の乱数を持つCOUNT()
新しいLIMIT
式にそれを含めます。
Jeremiesのようなソリューション:
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
は、インデックスが付けられている場合、テーブルサイズに関係なく、対数時間で機能します。注意点の1つ:これはnum_value
、範囲内で均等に分散されていることを前提としています0..MAX(num_value)
。データセットがこの仮定から大きく外れている場合、結果は歪んでいます(一部の行は他の行よりも頻繁に表示されます)。
これがどれほど効率的かわかりませんが、以前に使用したことがあります。
SELECT TOP 1 * FROM MyTable ORDER BY newid()
GUIDはかなりランダムであるため、順序付けはランダムな行を取得することを意味します。
ORDER BY RAND() LIMIT 1
TOP 1
してnewid()
いるため、これもデータベース固有です。
ORDER BY NEWID()
取る 7.4 milliseconds
WHERE num_value >= RAND() * (SELECT MAX(num_value) FROM table)
取る0.0065 milliseconds
!
私は間違いなく後者の方法で行きます。
rand()
浮動小数点数を返しn
どこ0 < n < 1
。num_value
が整数であると仮定すると、の戻り値rand() * max(num_value)
も整数に強制変換されるため、小数点以下は切り捨てられます。したがって、rand() * max(num_value)
は常により小さいmax(num_value)
ため、最後の行は選択されません。
どのサーバーを使用しているかは言いませんでした。古いバージョンのSQL Serverでは、これを使用できます。
select top 1 * from mytable order by newid()
SQL Server 2005以降では、を使用TABLESAMPLE
して、反復可能なランダムサンプルを取得できます。
SELECT FirstName, LastName
FROM Contact
TABLESAMPLE (1 ROWS) ;
SQL Serverの場合
newid()/ order byは機能しますが、すべての行のIDを生成してソートする必要があるため、大きな結果セットの場合は非常に負荷がかかります。
TABLESAMPLE()はパフォーマンスの観点からは優れていますが、結果が集中します(ページのすべての行が返されます)。
パフォーマンスの高い真のランダムサンプルの場合、最善の方法は、行をランダムに除外することです。SQL Server Books Onlineの記事「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(0x7fffffff AS int)は、0と1の間のランダムな浮動小数点値に評価されます。
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()の両方のインデックスの非効率性を回避し、レコード番号フィールドを作成します。
RandomRecord FROM "SELECT * FROM table LIMIT?、1";を準備します。 SET @ n = FLOOR(RAND()*(SELECT COUNT(*)FROM table)); EXECUTE RandomRecord USING @n;
最善の方法は、その目的のためだけに新しい列にランダムな値を入れ、次のようなものを使用することです(疑似コード+ SQL):
randomNo = random()
execSql("SELECT TOP 1 * FROM MyTable WHERE MyTable.Randomness > $randomNo")
これはMediaWikiコードで採用されているソリューションです。もちろん、小さい値に対してはある程度のバイアスがありますが、行がフェッチされない場合は、ランダムな値をゼロにラップすることで十分であることがわかりました。
newid()ソリューションでは、全行をスキャンして、各行に新しいGUIDを割り当てることができるため、パフォーマンスが大幅に低下する可能性があります。
関数が一度だけ評価され、すべての行に同じ「ランダムな」番号が割り当てられるため、rand()ソリューションはまったく機能しない可能性があります(つまり、MSSQLでは)。
SQL Server 2005および2008の場合、個々の行のランダムなサンプルが必要な場合(Books Onlineから):
SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), SalesOrderID) & 0x7fffffff AS float)
/ CAST (0x7fffffff AS int)
推奨されていないため、RAND()の使用を意図しているため、単純に最大ID(= Max)を取得できます。
SELECT MAX(ID) FROM TABLE;
1..Max(= My_Generated_Random)の間のランダムを取得します
My_Generated_Random = rand_in_your_programming_lang_function(1..Max);
次に、このSQLを実行します。
SELECT ID FROM TABLE WHERE ID >= My_Generated_Random ORDER BY ID LIMIT 1
IDが選択した値と等しいかそれよりも高い行がないかチェックすることに注意してください。テーブルの行を探して、My_Generated_Random以下のIDを取得し、クエリを次のように変更することもできます。
SELECT ID FROM TABLE WHERE ID <= My_Generated_Random ORDER BY ID DESC 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は、整数および単精度浮動小数点型に対して特定のソート最適化を備えた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;
ユニオン ALL句ではありません。この場合、最初の部分がデータを返す場合、2番目の部分は決して実行されません。
最近、Google経由で入手したので、後世のために、代替ソリューションを追加します。
別のアプローチは、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(*)までの範囲です。列にインデックスが付けられている場合は、それからもメリットがあります。別の利点は、NEWID()が許可されていないため、関数で使用できることです。
最後に、上記のクエリは、同じテーブルに対するNEWID()タイプのクエリの実行時間の約1/10で実行されます。YYMV。
new id()
関数を使用することもできます。
クエリを記述し、new id()
関数の順序を使用するだけです。それはかなりランダムです。
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
回答のこの変化をまだ十分に理解していませんでした。最初のシードを指定して、毎回同じ行のセットを選択する必要がある追加の制約がありました。
MS SQLの場合:
最小の例:
select top 10 percent *
from table_name
order by rand(checksum(*))
正規化された実行時間:1.00
NewId()の例:
select top 10 percent *
from table_name
order by newid()
正規化された実行時間:1.02
NewId()
はに比べてわずかに遅いためrand(checksum(*))
、大きなレコードセットに対しては使用しないほうがよい場合があります。
初期シードを使用した選択:
declare @seed int
set @seed = Year(getdate()) * month(getdate()) /* any other initial seed here */
select top 10 percent *
from table_name
order by rand(checksum(*) % seed) /* any other math function here */
シードを指定して同じセットを選択する必要がある場合、これは機能するようです。
SQL Server 2012+では、OFFSET FETCHクエリを使用して単一のランダムな行に対してこれを行うことができます
select * from MyTable ORDER BY id OFFSET n ROW FETCH NEXT 1 ROWS ONLY
ここで、idはID列であり、nは目的の行です-テーブルの0とcount()-1の間の乱数として計算されます(オフセット0は結局最初の行です)
これは、ORDER BY句で使用するインデックスがある限り、テーブルデータのホールで機能します。それはまた、ランダム性に非常に適しています-あなたが自分自身で渡すように働きかけますが、他の方法でのニグルは存在しません。さらに、パフォーマンスはかなり良好です。小さいデータセットで十分に対応できますが、数百万行に対して本格的なパフォーマンステストを試したことはありません。
TableSampleは実際には行のランダムなサンプルを返さないので注意してください。これは、行を構成する8KBページのランダムなサンプルを調べるようにクエリに指示します。次に、これらのページに含まれているデータに対してクエリが実行されます。これらのページでデータがどのようにグループ化されるか(挿入順序など)により、これは実際にはランダムなサンプルではないデータにつながる可能性があります。
参照:http : //www.mssqltips.com/tip.asp?tip=1308
このTableSampleのMSDNページには、実際にランダムなデータのサンプルを生成する方法の例が含まれています。
リストされているアイデアの多くはまだ順序付けを使用しているようです
ただし、一時テーブルを使用する場合は、ランダムなインデックスを割り当てて(多くのソリューションが提案しているように)、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
http://akinas.com/pages/en/blog/mysql_random_row/からのシンプルで効率的な方法
SET @i = (SELECT FLOOR(RAND() * COUNT(*)) FROM table); PREPARE get_stmt FROM 'SELECT * FROM table LIMIT ?, 1'; EXECUTE get_stmt USING @i;
SQL Server 2005以降では、num_value
が連続値ではない場合の@GreyPantherの回答を拡張します。これnum_value
は、データセットが均等に分散されておらず、数値ではなく一意の識別子である場合にも機能します。
WITH CTE_Table (SelRow, num_value)
AS
(
SELECT ROW_NUMBER() OVER(ORDER BY ID) AS SelRow, num_value FROM table
)
SELECT * FROM table Where num_value = (
SELECT TOP 1 num_value FROM CTE_Table WHERE SelRow >= RAND() * (SELECT MAX(SelRow) FROM CTE_Table)
)
SQLのランダム関数が役立ちます。また、1行のみに制限する場合は、最後に追加します。
SELECT column FROM table
ORDER BY RAND()
LIMIT 1