SQL Serverテーブルに約50,000行あります。私はそれらの行のうち約5000行をランダムに選択したいと思います。私は複雑な方法を考えました。「乱数」列を持つ一時テーブルを作成し、その中に自分のテーブルをコピーし、一時テーブルをループしてRand()
で各行を更新し、次に乱数列を選択<0.1。可能であれば、単一のステートメントでそれを行うためのより簡単な方法を探しています。
この記事 はNEWID()
関数の使用を提案します。それは有望に見えますが、私はどのようにして確実に一定の割合の行を選択することができるのかわかりません。
誰もが前にこれをしますか?何か案は?
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())
このコストは、値のキースキャンと結合コストです。選択率が小さい大きなテーブルでは、これが妥当なはずです。
あなたのニーズに応じて、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ページ を参照してください。
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が最後の手段になります。
MSDNで大規模テーブルからランダムに行を選択する には、大規模なパフォーマンスの問題に対処するための単純で明確な解決策があります。
SELECT * FROM Table1
WHERE (ABS(CAST(
(BINARY_CHECKSUM(*) *
Rand()) as int)) % 100) < 10
乱数でテーブルを並べ替え、TOP
を使って最初の5000行を取得するだけです。
SELECT TOP 5000 * FROM [Table] ORDER BY newid();
UPDATE
試してみただけでnewid()
呼び出しで十分です - すべてのキャストとすべての数学は必要ありません。
(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は変数を受け入れません)
このリンクは、Orderby(NEWID())と、1,700万および1300万行の表を持つ他のメソッドとの間で興味深い比較があります。
ランダムな行を選択する方法についてのディスカッショングループで質問が行われるときには、NEWIDクエリが推奨されます。それは簡単で、小さなテーブルにはとてもうまくいきます。
SELECT TOP 10 PERCENT *
FROM Table1
ORDER BY NEWID()
ただし、大きなテーブルに使用する場合、NEWIDクエリには大きな欠点があります。 ORDER BY句を使用すると、テーブル内のすべてのローがtempdbデータベースにコピーされ、そこでソートされます。これにより、2つの問題が発生します。
ランダムに行を選択する方法が必要です。これはtempdbを使用せず、テーブルが大きくなっても遅くなることはありません。これを行う方法についての新しいアイデアがあります。
SELECT * FROM Table1
WHERE (ABS(CAST(
(BINARY_CHECKSUM(*) *
Rand()) as int)) % 100) < 10
このクエリの背後にある基本的な考え方は、テーブル内の各行に0から99までの乱数を生成し、その乱数が指定されたパーセントの値より小さい行をすべて選択することです。この例では、約10パーセントの行をランダムに選択します。したがって、乱数が10未満のすべての行を選択します。
記事全体を MSDN でお読みください。
MySQLではこれを行うことができます。
SELECT `PRIMARY_KEY`, Rand() FROM table ORDER BY Rand() LIMIT 5000;
これは初期のシードのアイデアとチェックサムの組み合わせで、NEWID()のコストをかけずに適切にランダムな結果が得られるように思われます。
SELECT TOP [number]
FROM table_name
ORDER BY Rand(CHECKSUM(*) * Rand())
答えの中でこのバリエーションをまだよく見ていませんでした。毎回同じ行のセットを選択するために、最初のシードが与えられたときに、必要に応じて追加の制約がありました。
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 */
シードを与えて同じセットを選択する必要があるなら、これはうまくいくようです。
これを試して:
SELECT TOP 10 Field1, ..., FieldN
FROM Table1
ORDER BY NEWID()
Newid()はwhere句では使用できないように見えるため、この解決策では内部クエリが必要です。
SELECT *
FROM (
SELECT *, ABS(CHECKSUM(NEWID())) AS Rnd
FROM MyTable
) vw
WHERE Rnd % 100 < 10 --10%
副問合せで使用していましたが、副問合せで同じ行が戻されました
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条件に注意してください
使用しているサーバー側の処理言語(例:PHP、.netなど)は指定されていませんが、PHPの場合は必要な数(またはすべてのレコード)を取得し、クエリでランダム化する代わりにPHPのシャッフル関数を使用します。私は.netが同等の機能を持っているかどうか知りませんが、もしそれが持っていれば、.netを使っているならそれを使います。
Rand()による順序は、含まれるレコード数によっては、パフォーマンスがかなり低下する可能性があります。