SQLデータベースからの単純ランダムサンプル


93

SQLで効率的な単純ランダムサンプルを取得するにはどうすればよいですか?問題のデータベースはMySQLを実行しています。私のテーブルは少なくとも200,000行であり、約10,000の単純なランダムサンプルが必要です。

「明白な」答えは次のとおりです。

SELECT * FROM table ORDER BY RAND() LIMIT 10000

大きなテーブルの場合、これは遅すぎます。RAND()すべての行を呼び出し(すでにO(n)に配置されています)、それらを並べ替えて、せいぜいO(n lg n)にします。O(n)よりも速くこれを行う方法はありますか?

:Andrew Maoがコメントで指摘しているように、SQL Serverでこのアプローチを使用している場合はNEWID()、RAND()がすべての行に同じ値を返す可能性があるため、T-SQL関数を使用する必要があります

編集:5年後

私はより大きなテーブルでこの問題に再び遭遇し、2つの調整を加えた@ignorantのソリューションのバージョンを使用することになりました:

  • 行を希望のサンプルサイズの2〜5倍にサンプリングし、安価に ORDER BY RAND()
  • RAND()挿入/更新のたびに、の結果をインデックス付きの列に保存します。(データセットの更新がそれほど多くない場合は、この列を最新の状態に保つための別の方法を見つける必要がある場合があります。)

テーブルの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()再び十分に小さくなるまで、インデックススキャンを使用してデータベースをサンプリングすることができます。


3
RAND()後続の呼び出しごとに同じ値が返されるため、SQLサーバーでは機能しません。
Andrew Mao

1
良い点-SQLServerユーザーは代わりにORDERBY NEWID()を使用する必要があることに注意してください。
ojrac 2012

すべてのデータをソートする必要があるため、それでもひどく非効率的です。ある程度のランダムサンプリング手法の方が優れていますが、ここで多数の投稿を読んだ後でも、十分にランダムな許容可能なソリューションは見つかりませんでした。
Andrew Mao

質問を読んだら、ORDER BY RAND()がO(n lg n)であるため、具体的に質問しています。
ojrac 2012

以下のmuposatの回答は、RAND()の統計的ランダムネスにあまり夢中になっていない場合に最適です。
Josh Greifer 2014年

回答:


25

このタイプの問題については、非常に興味深い議論があります。 http://www.titov.net/2005/09/21/do-not-use-order-by-rand-or-how-to-get-random-rows-from-table/

私は、あなたのO(n lg n)ソリューションが最良であるというテーブルについての仮定はまったくないと思います。実際には、優れたオプティマイザまたはわずかに異なる手法を使用すると、リストするクエリの方が少し優れている場合がありますが、O(m * n)ここで、mは、大きな配列全体を必ずしも並べ替える必要がないため、必要なランダム行の数です。 、最小のm回を検索するだけで済みます。しかし、あなたが投稿した種類の数字については、とにかくmはlgnよりも大きいです。

私たちが試すかもしれない3つの仮定:

  1. テーブルには、一意のインデックス付きの主キーがあります

  2. 選択するランダムな行の数(m)は、テーブルの行の数(n)よりもはるかに少ないです。

  3. 一意の主キーは、ギャップのない1からnの範囲の整数です。

仮定1と2だけで、これはO(n)で実行できると思いますが、仮定3に一致するようにテーブルにインデックス全体を書き込む必要があるため、必ずしも高速なO(n)である必要はありません。さらに、テーブルについて何か他の良いことを想定できる場合は、O(m log m)でタスクを実行できます。仮定3は、操作しやすい追加のプロパティです。連続してm個の数を生成するときに重複がないことを保証する優れた乱数ジェネレーターを使用すると、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以外のほとんどのものは、必要な種類のループと乱数生成に適している可能性があります。 。


ランダムキーの選択に一意のインデックスを追加し、挿入の重複を無視することをお勧めします。そうすれば、個別のものを取り除くことができ、結合が高速になります。
サムサフラン

乱数アルゴリズムでは、いくつかの調整を使用できると思います。前述のようにUNIQUE制約を使用するか、2 * mの数値を生成し、SELECT DISTINCT、ORDER BY id(先着順なので、これはUNIQUE制約になります) )制限m。私はそれが好きです。
ojrac 2008年

ランダムキーの選択に一意のインデックスを追加し、挿入時に重複を無視することに関しては、これにより、ソートのO(m lg m)ではなくO(m ^ 2)の動作に戻る可能性があると思いました。ランダムな行を一度に1つずつ挿入するときに、サーバーがインデックスをどの程度効率的に維持しているかわからない。
user12861 2008年

2 * mの数などを生成するための提案については、何があっても動作することが保証されたアルゴリズムが必要でした。2 * mの乱数がmを超える重複を持つ可能性は常に(わずかに)あるため、クエリを実行するのに十分ではありません。
user12861 2008年

1
テーブルの行数をどのように取得しますか?
素晴らしい-o

54

最速の解決策は

select * from table where rand() <= .3

これが私がこれが仕事をするべきだと思う理由です。

  • 行ごとに乱数が作成されます。番号は0から1の間です
  • 生成された数が0から.3(30%)の場合、その行を表示するかどうかを評価します。

これは、rand()が一様分布で数値を生成していることを前提としています。これを行う最も簡単な方法です。

私は誰かがその解決策を勧めたのを見ました、そして彼らは証拠なしで撃墜されました..これが私がそれに言うことです-

  • これはO(n)ですが、ソートは必要ないため、O(n lg n)よりも高速です。
  • mysqlは、行ごとに乱数を生成することができます。これを試して -

    INFORMATION_SCHEMA.TABLES制限10からrand()を選択します。

問題のデータベースはmySQLであるため、これが適切なソリューションです。


1
まず、正確な希望の結果数ではなく、希望の数に近いが必ずしもその数とは限らない半乱数の結果が返されるため、これでは実際には質問に答えられないという問題があります。
user12861 2013

1
次に、効率に関しては、O(n)です。ここで、nはテーブルの行数です。これは、O(m log m)ほど良くはありません。ここで、mは必要な結果の数であり、m << nです。あなたが言うように、rand()を生成し、それらを定数と比較することは非常に速いので、実際にはもっと速いだろうということはまだ正しいかもしれません。あなたはそれを見つけるためにそれをテストしなければならないでしょう。小さいテーブルで勝つかもしれません。巨大なテーブルとはるかに少ない数の望ましい結果で、私はそれを疑っています。
user12861 2013

1
@ user12861は、これが正確な正しい数値を取得しないことについては正しいですが、データセットを適切な大まかなサイズに削減するための良い方法です。
ojrac 2013

1
データベースは次のクエリをどのように処理しますSELECT * FROM table ORDER BY RAND() LIMIT 10000 か?最初に各行に乱数を作成し(私が説明したソリューションと同じ)、次にそれを注文する必要があります。並べ替えは高価です!これが、ソートが必要ないため、このソリューションが説明したソリューションよりも遅くなる理由です。私が説明したソリューションに制限を追加することができ、それはあなたにその行数を超えることはありません。誰かが正しく指摘したように、正確なサンプルサイズは得られませんが、ランダムサンプルの場合、ほとんどの場合、正確な要件は厳密な要件ではありません。
無知な

最小行数を指定する方法はありますか?
CMCDragonkai 2014年

5

どうやらSQLのいくつかのバージョンにはTABLESAMPLEコマンドがありますが、それはすべてのSQL実装(特にRedshift)にあるわけではありません。

http://technet.microsoft.com/en-us/library/ms189108(v=sql.105).aspx


とてもかっこいい!PostgreSQLまたはMySQL / MariaDBによっても実装されていないように見えますが、それをサポートするSQL実装を使用している場合は素晴らしい答えです。
ojrac 2014年

私はそれTABLESAMPLEが統計的な意味でランダムではないことを理解しています。
ショーン

4

使用するだけ

WHERE RAND() < 0.1 

レコードの10%を取得する、または

WHERE RAND() < 0.01 

レコードの1%などを取得します。


1
これにより、すべての行に対してRANDが呼び出され、O(n)になります。ポスターはそれよりも良いものを探していました。
user12861 2012年

1
それだけでなく、RAND()(少なくともMSSQLでは)後続の呼び出しに対して同じ値を返します。つまり、その確率でテーブル全体を取得するか、テーブル全体を取得しないかのいずれかです。
Andrew Mao

4

ORDER BY RAND()よりも高速

私はこのメソッドをよりもはるかに高速であるとテストしたORDER BY RAND()ため、O(n)時間で実行され、非常に高速です。

http://technet.microsoft.com/en-us/library/ms189108%28v=sql.105%29.aspxから:

非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()を使用して20(20 mil)のサンプルレコードを取得するのに必要な時間を3分から0.0秒に短縮しました


1

これらのソリューションはすべて、置き換えなしでサンプリングされているように見えることを指摘したいと思います。ランダムな並べ替えから上位K行を選択するか、ランダムな順序で一意のキーを含むテーブルに結合すると、置換なしで生成されたランダムなサンプルが生成されます。

サンプルを独立させたい場合は、交換してサンプリングする必要があります。user12861のソリューションと同様の方法でJOINを使用してこれを行う方法の一例については、質問25451034を参照してください。ソリューションはT-SQL用に作成されていますが、この概念はどのSQLデータベースでも機能します。


0

セットに基づいてテーブルの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クエリの結果です。


0

正確にm行が必要な場合は、現実的にはSQLの外部でIDのサブセットを生成します。ほとんどのメソッドは、ある時点で「n番目」のエントリを選択する必要があり、SQLテーブルは実際には配列ではありません。1とカウントの間のランダムなintを結合するためにキーが連続しているという仮定も満たすのが困難です—たとえばMySQLはそれをネイティブにサポートしておらず、ロック条件は...トリッキーですです。

プレーンなBTREEキーだけを想定したO(max(n, m lg n))-time、O(n)-spaceソリューションは次のとおりです。

  1. データテーブルのキー列のすべての値を任意の順序で、でお気に入りのスクリプト言語の配列にフェッチします。 O(n)
  2. フィッシャー-イェーツシャッフルを実行し、mスワップ後に停止し、でサブ配列を抽出[0:m-1]しますϴ(m)
  3. サブアレイを元のデータセット(例SELECT ... WHERE id IN (<subarray>))と「結合」します。O(m lg n)

SQLの外部でランダムサブセットを生成するメソッドは、少なくともこの複雑さを持っている必要があります。参加する任意のよりも速くすることはできませんO(m lg n)(のでBTREEとのO(m)主張は、ほとんどのエンジンのファンタジーです)とシャッフルが下に有界であるnm lg nし、漸近的行動に影響を与えません。

Pythonの擬似コードの場合:

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])

0

Netezzaで3000のランダムレコードを選択します。

WITH IDS AS (
     SELECT ID
     FROM MYTABLE;
)

SELECT ID FROM IDS ORDER BY mt_random() LIMIT 3000

SQLダイアレクト固有のメモを追加する以外に、これは「ORDER BY rand()LIMIT $ 1」なしで行のランダムサンプルをクエリする方法の質問に答えるとは思いません。
ojrac

0

試してみてください

SELECT TOP 10000 * FROM table ORDER BY NEWID()

これにより、複雑になりすぎずに、望ましい結果が得られますか?


これNEWID()はT-SQLに固有であることに注意してください。
ピーターO.

謝罪いたします。です。おかげで、私がより良い方法で行ったように誰かがここに来て、T-SQLを使用しているかどうかを知ることは有用です
ノーザン

ORDER BY NEWID()機能的には同じですORDER BY RAND()-RAND()セット内のすべての行を呼び出します--O(n)-そして全体をソートします--O(n lg n)。言い換えれば、それはこの質問が改善しようとしている最悪の場合の解決策です。
ojrac

0

Microsoft SQL Server、PostgreSQL、Oracle(MySQLやSQLiteは除く)などの特定の方言では、次のようなことができます。

select distinct top 10000 customer_id from nielsen.dbo.customer TABLESAMPLE (20000 rows) REPEATABLE (123);

(10000 rows)なしで実行しない理由topは、TABLESAMPLEロジックが非常に不正確な行数(75%の場合、1.25%の場合など)を提供するため、オーバーサンプリングして必要な正確な数を選択する必要があるためです。REPEATABLE (123)ランダムシードを提供するためのものです。


-4

多分あなたはすることができます

SELECT * FROM table LIMIT 10000 OFFSET FLOOR(RAND() * 190000)

1
それは私のデータのランダムなスライスを選択するようです。もう少し複雑なもの、つまりランダムに分散された10,000行を探しています。
ojrac 2008年

次に、データベースでそれを実行したい場合の唯一のオプションは、ORDER BY rand()です。
staticsan 2008年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.