SQLで効率的な単純なランダムサンプルを取得するにはどうすればよいですか?問題のデータベースはMySQLを実行しています。私のテーブルは少なくとも200,000行で、約10,000の単純なランダムサンプルが必要です。
「明白な」答えは次のとおりです。
SELECT * FROM table ORDER BY Rand() LIMIT 10000
大きなテーブルの場合、それは遅すぎます。すべての行(すでにO(n)に配置されている)に対してRand()を呼び出し、ソートして、せいぜいO(n lg n)にします。 O(n)よりも速くこれを行う方法はありますか?
注:Andrew Maoがコメントで指摘しているように、SQL Serverでこのアプローチを使用している場合は、T-SQL関数NEWID( )、Rand() すべての行に同じ値を返す可能性がある であるため。
編集:5年後
大きなテーブルを使用してこの問題に再び遭遇し、@ ignorantのソリューションのバージョンを使用して、2つの調整を行いました。
テーブルの1000項目のサンプルを取得するには、行をカウントし、frozen_Rand列で平均10,000行まで結果をサンプリングします。
SELECT COUNT(*) FROM table; -- Use this to determine Rand_low and Rand_high
SELECT *
FROM table
WHERE frozen_Rand BETWEEN %(Rand_low)s AND %(Rand_high)s
ORDER BY Rand() LIMIT 1000
(実際の実装では、アンダーサンプリングしないようにし、Rand_highを手動でラップするための作業が必要になりますが、基本的な考え方は「ランダムにNを数千に削減する」です)
これはいくらか犠牲になりますが、インデックススキャンを使用してデータベースをサンプリングし、ORDER BY Rand()が再び十分に小さくなるまでサンプリングすることができます。
このタイプの問題に関する非常に興味深い議論がここにあります: http://www.titov.net/2005/09/21/do-not-use-order-by-Rand -または-テーブルからランダム行を取得する方法/
O(n lg n)ソリューションが最適であるというテーブルについての仮定はまったくありません。実際には優れたオプティマイザーまたはわずかに異なる手法を使用すると、リストするクエリは少し優れている場合がありますが、O(m * n)ここでmは必要なランダムな行の数であり、必ずしも大きな配列全体をソートする必要はありません、最小m回だけ検索できます。しかし、あなたが投稿した数字の種類については、とにかくmはlg nよりも大きいです。
試してみる3つの仮定:
テーブルに一意のインデックス付き主キーがあります
選択するランダムな行の数(m)は、テーブル内の行の数(n)よりもはるかに少ない
一意の主キーは、1〜nの範囲の整数で、ギャップなし
仮定1と2のみで、これはO(n)で実行できると思いますが、仮定3に一致するようにテーブル全体にインデックスを書き込む必要があるため、必ずしも高速なO(n)ではありません。さらにテーブルについて何か他に良いことを想定できる場合は、O(m log m)でタスクを実行できます。仮定3は、簡単に使用できるニースの追加プロパティです。行にm個の数値を生成するときに重複が発生しないことを保証するNice乱数ジェネレーターを使用すると、O(m)ソリューションが可能になります。
3つの仮定が与えられた場合、基本的な考え方は、1からnまでのm個の一意の乱数を生成し、テーブルからそれらのキーを持つ行を選択することです。私は現在mysqlまたは私の前に何も持っていないので、少し擬似コードではこれは次のようになります:
create table RandomKeys (RandomKey int)
create table RandomKeysAttempt (RandomKey int)
-- generate m random keys between 1 and n
for i = 1 to m
insert RandomKeysAttempt select Rand()*n + 1
-- eliminate duplicates
insert RandomKeys select distinct RandomKey from RandomKeysAttempt
-- as long as we don't have enough, keep generating new keys,
-- with luck (and m much less than n), this won't be necessary
while count(RandomKeys) < m
NextAttempt = Rand()*n + 1
if not exists (select * from RandomKeys where RandomKey = NextAttempt)
insert RandomKeys select NextAttempt
-- get our random rows
select *
from RandomKeys r
join table t ON r.RandomKey = t.UniqueKey
本当に効率が気になる場合は、何らかの手続き型言語でランダムキー生成を実行し、データベースに結果を挿入することを検討してください。SQL以外のほとんどの場合は、必要なループと乱数生成の種類でおそらく優れているでしょう。
最速の解決策は
select * from table where Rand() <= .3
これが仕事をするはずだと思う理由です。
これは、Rand()が均一な分布で数値を生成していることを前提としています。これが最も簡単な方法です。
私は誰かがその解決策を推奨していたことを見て、彼らは証拠なしに撃shotされた..ここに私がそれを言うだろう-
mysqlは、行ごとに乱数を生成することができます。これを試して -
iNFORMATION_SCHEMA.TABLES limit 10からRand()を選択します。
問題のデータベースはmySQLであるため、これは適切なソリューションです。
このメソッドをORDER BY Rand()
よりもはるかに高速でテストしたため、O(n)時間で実行され、非常に高速です。
http://technet.Microsoft.com/en-us/library/ms189108%28v=sql.105%29.aspx から:
Non-MSSQLバージョン-私はこれをテストしませんでした
_SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= Rand()
_
MSSQLバージョン:
_SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), SalesOrderID) & 0x7fffffff AS float) / CAST (0x7fffffff AS int)
_
これにより、レコードの約1%が選択されます。したがって、正確なパーセント数またはレコードを選択する必要がある場合は、ある程度の安全マージンでパーセントを推定し、より高価なORDER BY Rand()
メソッドを使用して、結果セットから過剰なレコードをランダムに抜き取ります。
よく知られているインデックス列の値の範囲があるため、この方法をさらに改善することができました。
例えば、整数[0..max]が均一に分布したインデックス付きの列がある場合、それを使用してN個の小さな間隔をランダムに選択できます。プログラムでこれを動的に実行して、クエリの実行ごとに異なるセットを取得します。このサブセットの選択はO(N)になります。これは、完全なデータセットよりも桁違いに小さい場合があります。
私のテストでは、ORDER BY Rand()を使用して3 minsから20(20 milから)サンプルレコードを取得するために必要な時間を0.0秒!
どうやらSQLの一部のバージョンにはTABLESAMPLE
コマンドがありますが、すべてのSQL実装(特にRedshift)にはありません。
http://technet.Microsoft.com/en-us/library/ms189108(v = sql.105).aspx
ただ使う
WHERE Rand() < 0.1
レコードの10%を取得する、または
WHERE Rand() < 0.01
1%のレコードを取得するなど。
これらのソリューションはすべて、交換せずにサンプリングするように見えることを指摘したいと思います。ランダムソートから上位K行を選択するか、一意のキーをランダムな順序で含むテーブルに結合すると、置換なしでランダムサンプルが生成されます。
サンプルを独立させたい場合は、交換してサンプリングする必要があります。 user12861のソリューションと同様の方法でJOINを使用してこれを行う方法の一例については、 質問25451034 を参照してください。ソリューションはT-SQL用に作成されていますが、この概念はどのSQLデータベースでも機能します。
セットに基づいてテーブルのID(たとえば、カウント5)を取得できるという観察から始めます。
_select *
from table_name
where _id in (4, 1, 2, 5, 3)
_
文字列"(4, 1, 2, 5, 3)"
を生成できれば、Rand()
よりも効率的な方法になるという結果になります。
たとえば、Javaの場合:
_ArrayList<Integer> indices = new ArrayList<Integer>(rowsCount);
for (int i = 0; i < rowsCount; i++) {
indices.add(i);
}
Collections.shuffle(indices);
String inClause = indices.toString().replace('[', '(').replace(']', ')');
_
IDにギャップがある場合、初期の配列リストindices
はIDに対するSQLクエリの結果です。
正確にm
行が必要な場合、現実的にはSQLの外部でIDのサブセットを生成します。ほとんどのメソッドでは、ある時点で「n番目」のエントリを選択する必要があり、SQLテーブルは実際には配列ではありません。 1とカウントの間のランダムな整数を結合するためにキーが連続しているという仮定も満たすのが困難です。たとえば、MySQLはネイティブにサポートしておらず、ロック条件は... tricky =。
プレーンBTREEキーを想定したO(max(n, m lg n))
- time、O(n)
- spaceソリューションは次のとおりです。
O(n)
のお気に入りのスクリプト言語の配列に入れますm
スワップの後に停止し、ϴ(m)
のサブ配列[0:m-1]
を抽出しますSELECT ... WHERE id IN (<subarray>)
の元のデータセット(例:O(m lg n)
)とサブアレイを「結合」するSQLの外部でランダムサブセットを生成するメソッドには、少なくともこの複雑さが必要です。 BTREEでO(m lg n)
より速く結合することはできないため(O(m)
クレームはほとんどのエンジンにとって幻想的です)、シャッフルはn
およびm lg n
の下にバインドされ、漸近的な動作には影響しません。
Pythonic擬似コードの場合:
ids = sql.query('SELECT id FROM t')
for i in range(m):
r = int(random() * (len(ids) - i))
ids[i], ids[i + r] = ids[i + r], ids[i]
results = sql.query('SELECT * FROM t WHERE id IN (%s)' % ', '.join(ids[0:m-1])