web-dev-qa-db-ja.com

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

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

この記事NEWID()関数の使用を提案します。それは有望に見えますが、私はどのようにして確実に一定の割合の行を選択することができるのかわかりません。

誰もが前にこれをしますか?何か案は?

283
John M Gant
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())

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

357

あなたのニーズに応じて、TABLESAMPLEはあなたをほぼ無作為でより良いパフォーマンスにするでしょう。これはMS SQL Server 2005以降で利用可能です。

TABLESAMPLEは、ランダムな行ではなくランダムなページからデータを返すので、返されないデータも取得しません。

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

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

20分以上かかりました。

select * from [tablename] tablesample(1 percent)

2分かかりました。

TABLESAMPLEの小さいサンプルでもパフォーマンスは向上しますが、newid()では向上しません。

これはnewid()メソッドのようにランダムではありませんが、まともなサンプリングを提供します。

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

76
Patrick Taylor

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の間のランダムな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が最後の手段になります。

37
Rob Boek

MSDNで大規模テーブルからランダムに行を選択する には、大規模なパフォーマンスの問題に対処するための単純で明確な解決策があります。

  SELECT * FROM Table1
  WHERE (ABS(CAST(
  (BINARY_CHECKSUM(*) *
  Rand()) as int)) % 100) < 10
21
Kyle McClellan

乱数でテーブルを並べ替え、TOPを使って最初の5000行を取得するだけです。

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

UPDATE

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

9

(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
Oskar Austegard

このリンクは、Orderby(NEWID())と、1,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 でお読みください。

8
RJardines

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

SELECT `PRIMARY_KEY`, Rand() FROM table ORDER BY Rand() LIMIT 5000;
4
Jeff Ferland

これは初期のシードのアイデアとチェックサムの組み合わせで、NEWID()のコストをかけずに適切にランダムな結果が得られるように思われます。

SELECT TOP [number] 
FROM table_name
ORDER BY Rand(CHECKSUM(*) * Rand())
4
Nanki

答えの中でこのバリエーションをまだよく見ていませんでした。毎回同じ行のセットを選択するために、最初のシードが与えられたときに、必要に応じて追加の制約がありました。

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

シードを与えて同じセットを選択する必要があるなら、これはうまくいくようです。

2
klyd

これを試して:

SELECT TOP 10 Field1, ..., FieldN
FROM Table1
ORDER BY NEWID()
2
Ravi Parashar

Newid()はwhere句では使用できないように見えるため、この解決策では内部クエリが必要です。

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

副問合せで使用していましたが、副問合せで同じ行が戻されました

 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
VISHMAY

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

Rand()による順序は、含まれるレコード数によっては、パフォーマンスがかなり低下する可能性があります。

0
SpacePhoenix