从SQL Server表中选择n个随机行


309

我有一个包含约50,000行的SQL Server表。我想随机选择大约5,000行。我想到了一种复杂的方法,用“随机数”列创建一个临时表,将我的表复制到该表中,遍历该临时表并用来更新每一行RAND(),然后从该表中选择随机数列< 0.1。我正在寻找一种更简单的方法,如果可能的话,可以在一个语句中。

本文建议使用该NEWID()功能。这看起来很有希望,但是我看不到如何可靠地选择一定百分比的行。

有人做过吗?有任何想法吗?


3
MSDN上有一篇很好的文章涵盖了许多这些问题:从大表中随机选择行
KyleMit 2015年

Answers:


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
与使用他引用的文章相比,我更喜欢这种方法。
2009年

14
始终牢记newid()并不是一个非常好的伪随机数生成器,至少不如rand()好。但是,如果您只需要一些模糊随机的样本,并且不关心数学性质等,那就足够了。否则,你需要:stackoverflow.com/questions/249301/...
user12861

1
嗯,很抱歉,如果这很明显..但是,这是[yourPk]指什么呢?编辑:Nvm,弄清楚了...主键。Durrr
Snailer

4
newid-guid被分配为唯一的但不是随机的。.错误的方法
Brans Ds

2
具有大量行(例如,超过一百万个newid()排序估计I / O成本)将非常高并且会影响性​​能。
aadi1295

81

根据您的需求,TABLESAMPLE将为您带来几乎一样的随机性和更好的性能。在MS SQL Server 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
正如下面的Rob Boek所指出的那样,表格抽样会导致结块,因此并不是获得少量随机结果的好方法
Oskar Austegard 2012年

您介意这是如何工作的:通过newid()从[tablename]顺序中选择前1%*,因为newid()不是[tablename]中的一列。sql server是在每行的内部在内部附加newid()列然后进行排序吗?
FrenkyB 2015年

tablesample对我来说是最好的答案,因为我正在对一个非常大的表进行复杂的查询。毫无疑问,它的速度非常快。多次运行时,返回的记录数确实有所变化,但所有记录均在可接受的误差范围内。
jessier3 '16

38

newid()/ order by可以工作,但是对于大型结果集来说非常昂贵,因为它必须为每一行生成一个id,然后对其进行排序。

从性能的角度来看,TABLESAMPLE()很好,但是您会得到成堆的结果(将返回页面上的所有行)。

为了获得性能更好的真实随机样本,最好的方法是随机过滤出行。我在SQL Server联机丛书文章“ 使用TABLESAMPLE限制结果集”中找到以下代码示例:

如果您确实希望随机获取单个行的样本,请修改查询以随机过滤掉行,而不是使用TABLESAMPLE。例如,以下查询使用NEWID函数返回Sales.SalesOrderDetail表的大约百分之一的行:

SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(),SalesOrderID) & 0x7fffffff AS float)
              / CAST (0x7fffffff AS int)

CHECKSUM表达式中包含SalesOrderID列,因此NEWID()每行评估一次,以实现每行采样。表达式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()它只被评估一次,而不是每行评估一次,我不喜欢...
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年

此查询在不到6秒的时间内在具有6MM行的表上运行。
Mark Melville 2014年

2
我在具有35个条目的表上运行了此查询,并经常在结果集中保留其中两个。这可能是上述问题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。–
Brian Brian

12

对于具有1、7和13百万行的表,此链接对Orderby(NEWID())和其他方法进行了有趣的比较。

通常,当在讨论组中询问有关如何选择随机行的问题时,就会提出NEWID查询。它很简单,适用于小桌子。

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

但是,当将NEWID查询用于大型表时,它有一个很大的缺点。ORDER BY子句使表中的所有行都复制到tempdb数据库中,并在其中进行排序。这导致两个问题:

  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

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

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语句是原子的,因此它仅获取一个随机数并将其复制到每一行。您将必须在每行上重新设置其种子以强制更改它。
汤姆H

4
嗯...喜欢供应商的差异。Select在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()函数除了使用完整的时间值外,功能相同,我想知道使用诸如RAND()之上的种子这样的便捷创建的参数有什么好处吗?
QMaster

啊!。好的,这是项目的要求。我需要以确定性的方式生成n个随机行的列表。基本上,领导层想知道在选择和处理行之前几天我们将选择哪些“随机”行。通过基于年份/月份构建种子值,我可以保证对该年份的任何查询调用都将返回相同的“随机”列表。我知道,这很奇怪,也许还有更好的方法,但是它起作用了……
klyd

哈哈:)我明白了,但是我认为随机选择的记录的一般含义是不同运行查询中的相同记录。
QMaster


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

注意哪里有条件


0

没有指定使用中的服务器端处理语言(例如PHP,.net等),但是如果是PHP,则获取所需的数字(或所有记录),而不是使用PHP的shuffle函数在查询中进行随机化。我不知道.net是否具有等效功能,但是如果您使用.net则使用该功能

ORDER BY RAND()可能会影响性能,具体取决于所涉及的记录数量。


我不记得当时我到底在使用什么,但是我可能不确定使用C#,也许是在服务器上,或者是在客户端应用程序中。C#没有与PHP的洗牌afaik直接可比的任何东西,但是可以通过在Select操作中应用来自Random对象的函数,对结果进行排序,然后取前10%来完成。但是,我们必须从数据库服务器上的磁盘读取整个表,然后通过网络进行传输,而只丢弃90%的数据。几乎可以肯定,直接在数据库中处理它会更有效率。
John M Gant

-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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.