Select n random rows from SQL Server table
我有一个SQL Server表,其中包含大约50,000行。 我想随机选择大约5,000行。 我想到了一个复杂的方法,创建一个带有"随机数"列的临时表,将我的表复制到其中,循环遍历临时表并用
本文建议使用
有人曾经这样做过吗? 有任何想法吗?
1 | SELECT top 10 percent * FROM [yourtable] ORDER BY newid() |
回应关于大表的"纯垃圾"评论:你可以这样做以提高性能。
1 2 | SELECT * FROM [yourtable] WHERE [yourPk] IN (SELECT top 10 percent [yourPk] FROM [yourtable] ORDER BY newid()) |
这样做的成本将是对值的关键扫描加上加入成本,在具有较小百分比选择的大型表上应该是合理的。
根据您的需要,
这在MS SQL Server 2005及更高版本上可用。
在我测试的一张非常大的桌子上
1 | SELECT top 1 percent * FROM [tablename] ORDER BY newid() |
花了20多分钟。
1 | SELECT * FROM [tablename] tablesample(1 percent) |
花了2分钟。
对于
请记住,这不像
请参阅MSDN页面。
newid()/ order by会工作,但对于大型结果集来说会非常昂贵,因为它必须为每一行生成一个id,然后对它们进行排序。
从性能的角度来看,TABLESAMPLE()很好,但是你会得到结果的结果(页面上的所有行都会被返回)。
对于性能更好的真随机样本,最好的方法是随机过滤掉行。我在SQL Server联机丛书文章中使用TABLESAMPLE限制结果集中找到以下代码示例:
If you really want a random sample of
individual rows, modify your query to
filter out rows randomly, instead of
using TABLESAMPLE. For example, the
following query uses the NEWID
function to return approximately one
percent of the rows of the
Sales.SalesOrderDetail table:
1
2
3 SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(),SalesOrderID) & 0x7fffffff AS FLOAT)
/ CAST (0x7fffffff AS INT)The SalesOrderID column is included in
the CHECKSUM expression so that
NEWID() evaluates once per row to
achieve sampling on a per-row basis.
The expression CAST(CHECKSUM(NEWID(),
SalesOrderID) & 0x7fffffff AS float /
CAST (0x7fffffff AS int) evaluates to
a random float value between 0 and 1.
当针对包含1,000,000行的表运行时,以下是我的结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | 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上的大表中随机选择行有一个简单明了的解决方案,可以解决大规模的性能问题。
1 2 3 4 | SELECT * FROM Table1 WHERE (ABS(CAST( (BINARY_CHECKSUM(*) * RAND()) AS INT)) % 100) < 10 |
此链接在Orderby(NEWID())和具有1,7和13百万行的表的其他方法之间进行了有趣的比较。
通常,当在讨论组中询问有关如何选择随机行的问题时,建议使用NEWID查询;它很简单,适用于小桌子。
1 2 3 | SELECT TOP 10 PERCENT * FROM Table1 ORDER BY NEWID() |
但是,当您将NEWID查询用于大型表时,它有一个很大的缺点。 ORDER BY子句将表中的所有行复制到tempdb数据库中,并对它们进行排序。这会导致两个问题:
排序可以使用大量磁盘I / O并可以运行很长时间。
在最佳情况下,tempdb会占用大量磁盘空间
在没有手动收缩命令的情况下永远不会被回收。
您需要的是一种随机选择不使用tempdb的行的方法,并且随着表变大而不会慢得多。这是一个关于如何做到这一点的新想法:
1 2 3 4 | SELECT * FROM Table1 WHERE (ABS(CAST( (BINARY_CHECKSUM(*) * RAND()) AS INT)) % 100) < 10 |
这个查询背后的基本思想是我们想要为表中的每一行生成0到99之间的随机数,然后选择随机数小于指定百分比值的所有行。在这个例子中,我们希望随机选择大约10%的行;因此,我们选择随机数小于10的所有行。
请阅读MSDN中的完整文章。
如果你(不像OP)需要特定数量的记录(这使得CHECKSUM方法难以实现)并且希望比TABLESAMPLE本身提供的更随机的样本,并且还希望比CHECKSUM更快的速度,你可以合并TABLESAMPLE和NEWID()方法,如下所示:
1 2 3 4 5 6 7 8 | 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不接受变量)
只需按随机数对表格进行排序,然后使用
1 | SELECT TOP 5000 * FROM [TABLE] ORDER BY newid(); |
UPDATE
刚尝试过,
这是初始种子想法和校验和的组合,它让我在没有NEWID()成本的情况下给出适当的随机结果:
1 2 3 | SELECT TOP [NUMBER] FROM TABLE_NAME ORDER BY RAND(CHECKSUM(*) * RAND()) |
在MySQL中你可以这样做:
1 | SELECT `PRIMARY_KEY`, rand() FROM TABLE ORDER BY rand() LIMIT 5000; |
尚未在答案中看到这种变化。在给定初始种子的情况下,我需要一个额外的约束来每次选择相同的行集。
对于MS SQL:
最小例子:
1 2 3 | SELECT top 10 percent * FROM TABLE_NAME ORDER BY rand(checksum(*)) |
标准化执行时间:1.00
NewId()示例:
1 2 3 | SELECT top 10 percent * FROM TABLE_NAME ORDER BY newid() |
标准化执行时间:1.02
选择初始种子:
1 2 3 4 5 6 | 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 */ |
如果您需要选择给定种子的相同集合,这似乎有效。
试试这个:
1 2 3 | SELECT TOP 10 Field1, ..., FieldN FROM Table1 ORDER BY NEWID() |
使用的服务器端处理语言(例如PHP,.net等)未指定,但如果是PHP,则获取所需的数字(或所有记录),而不是在查询中随机使用PHP的shuffle函数。我不知道.net是否具有相同的功能,但如果确实如此,那么如果你使用的是.net
ORDER BY RAND()可能会有相当大的性能损失,具体取决于涉及的记录数量。
我在子查询中使用它,它在子查询中返回了相同的行
1 2 3 4 5 6 7 8 9 | SELECT ID , ( SELECT TOP 1 ImageURL FROM SubTable ORDER BY NEWID() ) AS ImageURL, GETUTCDATE() , 1 FROM Mytable |
然后我解决了包括父表变量在哪里
1 2 3 4 5 6 7 8 9 10 | SELECT ID , ( SELECT TOP 1 ImageURL FROM SubTable WHERE Mytable.ID>0 ORDER BY NEWID() ) AS ImageURL, GETUTCDATE() , 1 FROM Mytable |
请注意condtition的位置
看来newid()不能在where子句中使用,所以这个解决方案需要一个内部查询:
1 2 3 4 5 6 | SELECT * FROM ( SELECT *, ABS(CHECKSUM(NEWID())) AS Rnd FROM MyTable ) vw WHERE Rnd % 100 < 10 --10% |
这对我有用:
1 2 3 | SELECT * FROM TABLE_NAME ORDER BY RANDOM() LIMIT [NUMBER] |