SQL Serverテーブルからn個のランダムな行を選択します


309

約50,000行のSQL Serverテーブルがあります。それらの行のうち約5,000をランダムに選択したいと思います。複雑な方法を考えました。「乱数」列を持つ一時テーブルを作成し、そこに自分のテーブルをコピーし、一時テーブルをループして各行をRAND()で更新し、乱数列< 0.1 可能な場合は1つのステートメントで、より簡単な方法を探しています。

この記事では、NEWID()関数の使用を提案しています。これは有望に見えますが、特定の割合の行を確実に選択する方法がわかりません。

誰かこれまでにこれをしたことがありますか?何か案は?


3
MSDNには、これらの問題の多くをカバーする優れた記事があります。大きなテーブルからランダムに行を選択する
KyleMit

回答:


387
select top 10 percent * from [yourtable] order by newid()

大きなテーブルに関する「純粋なゴミ」コメントへの応答:パフォーマンスを向上させるために、このようにすることができます。

select  * from [yourtable] where [yourPk] in 
(select top 10 percent [yourPk] from [yourtable] order by newid())

このコストは、値のキースキャンと結合コストであり、選択の割合が小さい大きなテーブルでは妥当です。


1
私はこのアプローチが、彼が参照した記事を使用するよりもずっと気に入っています。
JoshBerke、2009年

14
newid()は、少なくともrand()ほど良くない、本当に良い疑似乱数ジェネレータではないことを常に覚えておくことは常に良いことです。しかし、漠然とランダムなサンプルがいくつか必要なだけで、数学的な性質などを気にしない場合は、それで十分です。そうしないと次のものが必要です。stackoverflow.com/questions/249301/...
user12861

1
ええと、これが明らかな場合は申し訳ありませんが、何を[yourPk]指しますか?編集:Nvm、それを考え出した...主キー。ダラー
スナイラー、

4
newid-guidは一意であるがランダムではないように設計されています。不適切なアプローチ
Brans Ds

2
行数が多い場合、たとえば100万を超える場合は、newid()Sort Estimate I / Oコストが非常に高くなり、パフォーマンスに影響します。
aadi1295

81

ニーズに応じて、TABLESAMPLEほぼランダムで優れたパフォーマンスが得られます。これは、MS SQLサーバー2005以降で使用できます。

TABLESAMPLE ランダムな行の代わりにランダムなページからデータを返すため、deosは返さないデータを取得しません。

テストした非常に大きなテーブルで

select top 1 percent * from [tablename] order by newid()

20分以上かかりました。

select * from [tablename] tablesample(1 percent)

2分かかりました。

パフォーマンスは、小さいサンプルでも改善されますがTABLESAMPLE、では改善されませんnewid()

これはnewid()メソッドほどランダムではありませんが、適切なサンプリングを提供することに注意してください。

MSDNページを参照してください


7
塊の結果をtablesampling、以下のロブBoekによって指摘、したがって得るための良い道ではないので、小さなランダムな結果の数
オスカーAustegard

newid()は[tablename]の列ではないので、これがどのように機能するかという質問を気にします。SQLサーバーは各行に内部的に列newid()を追加してからソートしますか?
FrenkyB 2015年

非常に大きなテーブルで複雑なクエリを実行していたので、tablesampleが最良の答えでした。驚くほど高速だったことは間違いありません。これを複数回実行したときに返されたレコード数にはばらつきがありましたが、すべて許容可能な誤差範囲内でした。
jessier3 2016年

38

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を最後の手段としてください。


私もその記事を見てコードで試してみたところ、NewID()行ごとではなく1回しか評価されないようです。これは気に入らない...
Andrew Mao

23

MSDNの大きなテーブルからランダムに行を選択することは、大規模なパフォーマンスの問題に対処するシンプルで明確なソリューションを備えています。

  SELECT * FROM Table1
  WHERE (ABS(CAST(
  (BINARY_CHECKSUM(*) *
  RAND()) as int)) % 100) < 10

とても興味深い。記事を読んだ後、なぜRAND()各行に同じ値が返されないのか(BINARY_CHECKSUM()論理を無効にする)がよくわかりません。SELECT句の一部ではなく、別の関数内で呼び出されているためですか?
John M Gant 2012

このクエリは、1秒未満で6MM行のテーブルで実行されました。
マークメルビル

2
私はこのクエリを35のエントリを持つテーブルで実行しましたが、結果セットに2つのエントリが頻繁に含まれ続けました。これrand()は、上記の問題または上記の組み合わせの問題である可能性がありますが、そのため、この解決策を拒否しました。また、結果の数は1から5まで変化したため、これも一部のシナリオでは許容できない場合があります。
オリバー

RAND()はすべての行に同じ値を返しませんか?
Sarsaparilla、

RAND()すべての行に同じ値を返します(このため、このソリューションは高速です)。ただし、バイナリチェックサムが非常に接近している行は、同様のチェックサム結果を生成するリスクが高く、RAND()小さい場合はクランプが発生します。たとえば、(ABS(CAST((BINARY_CHECKSUM(111,null,null) * 0.1) as int))) % 100== SELECT (ABS(CAST((BINARY_CHECKSUM(113,null,null) * 0.1) as int))) % 100です。この問題からあなたのデータ遭遇すると、乗算BINARY_CHECKSUM9923.によって
ブライアン・

12

このリンクには、Orderby(NEWID())と、100万、700万、1300万行のテーブルの他のメソッドとの興味深い比較があります。

多くの場合、ディスカッショングループでランダムな行を選択する方法について質問されると、NEWIDクエリが提案されます。シンプルで、小さなテーブルに非常に適しています。

SELECT TOP 10 PERCENT *
  FROM Table1
  ORDER BY NEWID()

ただし、NEWIDクエリを大きなテーブルに使用すると、大きな欠点があります。ORDER BY句を使用すると、テーブル内のすべての行がtempdbデータベースにコピーされ、そこで並べ替えられます。これにより2つの問題が発生します。

  1. 通常、ソート操作には高いコストが伴います。並べ替えは、多くのディスクI / Oを使用する可能性があり、長時間実行できます。
  2. 最悪のシナリオでは、tempdbが領域不足になる可能性があります。最良のシナリオでは、tempdbは大量のディスク領域を占有する可能性があり、手動で縮小コマンドを実行しないと、ディスク領域は回収されません。

必要なのは、tempdbを使用せず、テーブルが大きくなっても遅くならないように、行をランダムに選択する方法です。これを行う方法に関する新しいアイデアを次に示します。

SELECT * FROM Table1
  WHERE (ABS(CAST(
  (BINARY_CHECKSUM(*) *
  RAND()) as int)) % 100) < 10

このクエリの基本的な考え方は、テーブルの行ごとに0から99までの乱数を生成し、その乱数が指定されたパーセントの値より小さいすべての行を選択することです。この例では、ランダムに選択された行の約10%が必要です。したがって、乱数が10未満のすべての行を選択します。

MSDNの記事全体をお読みください。


2
こんにちはDeumber、いい発見です。リンクのみの回答は削除される可能性が高いので、肉付けするかもしれません。
bummi 2014年

1
@bummiリンクのみの回答にならないように変更しました:)
QMaster

これが最良の答えです。'ORDER BY NEWID()'はほとんどの場合(小さいテーブル)で機能しますが、リフレクテッドリンクのベンチマークが示すように、テーブルが大きくなるにつれて遅れます
pedram bashiri

10

(OPとは異なり)特定の数のレコードが必要であり(これにより、CHECKSUMアプローチが困難になります)、TABLESAMPLE自体が提供するよりもランダムなサンプルが必要であり、CHECKSUMよりも優れた速度が必要な場合は、 TABLESAMPLEおよびNEWID()メソッドは、次のようになります。

DECLARE @sampleCount int = 50
SET STATISTICS TIME ON

SELECT TOP (@sampleCount) * 
FROM [yourtable] TABLESAMPLE(10 PERCENT)
ORDER BY NEWID()

SET STATISTICS TIME OFF

私の場合、これはランダム性(実際にはそうではない)と速度の間の最も単純な妥協点です。TABLESAMPLEのパーセンテージ(または行)を必要に応じて変更します。パーセンテージが高いほど、サンプルのランダム性は高くなりますが、速度の直線的な低下が予想されます。(TABLESAMPLEは変数を受け入れないことに注意してください)


9

乱数でテーブルを注文し、を使用して最初の5,000行を取得しTOPます。

SELECT TOP 5000 * FROM [Table] ORDER BY newid();

更新

試してみて、newid()呼び出しは十分です-すべてのキャストとすべての数学の必要はありません。


10
「すべてのキャストとすべての数学」が使用される理由は、パフォーマンスを向上させるためです。
hkf 2014年

6

これは、最初のシードアイデアとチェックサムの組み合わせであり、NEWID()のコストなしで適切にランダムな結果を与えるように私に見えます。

SELECT TOP [number] 
FROM table_name
ORDER BY RAND(CHECKSUM(*) * RAND())

3

MySQLではこれを行うことができます:

SELECT `PRIMARY_KEY`, rand() FROM table ORDER BY rand() LIMIT 5000;

3
これは機能しません。selectステートメントはアトミックなので、1つの乱数のみを取得し、行ごとに複製します。強制的に変更するには、各行に再シードする必要があります。
トムH

4
うーん...ベンダーの違いが大好きです。選択はMySQLではアトミックですが、私は別の方法で推測します。これはMySQLで動作します。
ジェフファーランド

2

答えのこの変化はまだまだわかりませんでした。最初のシードを指定して、毎回同じ行のセットを選択する必要がある追加の制約がありました。

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 */

シードを指定して同じセットを選択する必要がある場合、これは機能するようです。


RAND()に対して特別な@seedを使用する利点はありますか?
qmasterの

絶対に、シードパラメータを使用し、日付パラメータで入力しました。RAND()関数は、完全な時間値を使用する以外は同じことを行います。
QMaster

ああ!OK、これはプロジェクトの要件でした。決定論的な方法でn個のランダムな行のリストを生成する必要がありました。基本的にリーダーシップは、行が選択されて処理される数日前に、どの「ランダムな」行を選択するかを知りたいと考えていました。年/月に基づいてシード値を作成することで、クエリへの呼び出しがその年に同じ「ランダム」リストを返すことを保証できました。私は知っている、それは奇妙で、おそらくもっと良い方法があったがそれはうまくいった...
klyd

HAHA :)わかりましたが、ランダムに選択されたレコードの一般的な意味は、実行中の別のクエリの同じレコードではないと思います。
QMasterは2016


0

newid()はwhere句では使用できないように見えるため、このソリューションには内部クエリが必要です。

SELECT *
FROM (
    SELECT *, ABS(CHECKSUM(NEWID())) AS Rnd
    FROM MyTable
) vw
WHERE Rnd % 100 < 10        --10%

0

私はそれをサブクエリで使用していて、サブクエリで同じ行を返しました

 SELECT  ID ,
            ( SELECT TOP 1
                        ImageURL
              FROM      SubTable 
              ORDER BY  NEWID()
            ) AS ImageURL,
            GETUTCDATE() ,
            1
    FROM    Mytable

次に、親テーブル変数をどこに含めるかで解決しました

SELECT  ID ,
            ( SELECT TOP 1
                        ImageURL
              FROM      SubTable 
              Where Mytable.ID>0
              ORDER BY  NEWID()
            ) AS ImageURL,
            GETUTCDATE() ,
            1
    FROM    Mytable

where条件に注意してください


0

使用中のサーバー側処理言語(PHP、.netなど)は指定されていませんが、PHPの場合は、必要な数(またはすべてのレコード)を取得し、クエリでランダム化する代わりにPHPのシャッフル関数を使用します。.netに同等の機能があるかどうかはわかりませんが、.netを使用している場合はそれが使用されます

ORDER BY RAND()は、関係するレコードの数に応じて、パフォーマンスをかなり低下させる可能性があります。


当時これを何のために使用していたのか正確には思い出せませんが、おそらくC#、サーバー、またはクライアントアプリケーションで作業していたと思います。C#には、PHPのシャッフルアフェイクに直接匹敵するものはありませんが、選択操作内でRandomオブジェクトから関数を適用し、結果を順序付けして、上位10%を取得することで実現できます。しかし、テーブル全体をDBサーバーのディスクから読み取り、ネットワーク経由で送信するだけで、そのデータの90%を破棄するだけです。ほとんどの場合、DBで直接処理する方が効率的です。
ジョンMガント

-2

これは私にとってはうまくいきます:

SELECT * FROM table_name
ORDER BY RANDOM()
LIMIT [number]

9
@ user537824、SQL Serverで試しましたか?RANDOMは関数ではなく、LIMITはキーワードではありません。あなたがやっていることのSQL Server構文はになりますがselect top 10 percent from table_name order by rand()、rand()がすべての行で同じ値を返すため、これも機能しません。
John M Gant
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.