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

Joh*_*ant 296 sql sql-server random

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

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

有人曾经这样做过吗?有任何想法吗?

Ral*_*ton 371

select top 10 percent * from [yourtable] order by newid()
Run Code Online (Sandbox Code Playgroud)

回应关于大表的"纯垃圾"评论:你可以这样做以提高性能.

select  * from [yourtable] where [yourPk] in 
(select top 10 percent [yourPk] from [yourtable] order by newid())
Run Code Online (Sandbox Code Playgroud)

这样做的成本将是对值的关键扫描加上加入成本,在具有较小百分比选择的大型表上应该是合理的.

  • 记住newid()不是一个非常好的伪随机数生成器总是好的,至少不如rand()好.但是如果你只是需要一些模糊的随机样本并且不关心数学质量等等,它就足够了.否则你需要:http://stackoverflow.com/questions/249301/simple-random-samples-from-a-mysql-database (13认同)
  • newid - guid被设计为独特但不随机..不正确的方法 (4认同)
  • 对于大量行,例如超过 100 万个 `newid()` Sort 估计 I/O 成本将非常高并且会影响性​​能。 (3认同)
  • 关于在大型表上使用 NEWID() 的成本的评论并不是“纯粹的垃圾”。甚至在官方 Microsoft 文档 https://docs.microsoft.com/en-us/previous-versions/software-testing/cc441928(v=msdn.10)?redirectedfrom=MSDN 中也提到了这一点。“ORDER BY 子句导致表中的所有行都被复制到 tempdb 数据库中,并在其中进行排序”。RJardines 发布的答案对此进行了扩展。 (2认同)
  • @BransDs GUID 作为一个整体被设计为唯一的,但 NEWID() 实际上是一个随机数。这是一个 v4 UUID,您可以看出,因为第三组的开头始终为 4。在 v4 UUID 中,除了第三组的开头(始终为 4)和第四组的开头(始终为 8、9) 、A 或 B,因为这是版本变体)所有其他位都是完全随机数。v4 UUID 只是奇特的 ~122 位随机数。就是这样。SQL Server 文档说 NEWID() 符合 RFC4122 标准,RFC4122 使随机性质明确清晰:https://tools.ietf.org/html/rfc4122#section-4.4 (2认同)

小智 78

根据您的需求,您TABLESAMPLE将获得几乎随机和更好的性能.这在MS SQL Server 2005及更高版本上可用.

TABLESAMPLE 将从随机页面而不是随机行返回数据,因此deos甚至不会检索它不会返回的数据.

在我测试的一张非常大的桌子上

select top 1 percent * from [tablename] order by newid()
Run Code Online (Sandbox Code Playgroud)

花了20多分钟.

select * from [tablename] tablesample(1 percent)
Run Code Online (Sandbox Code Playgroud)

花了2分钟.

对于较小的样品,性能也会提高,TABLESAMPLE而不会有newid().

请记住,这并不像newid()方法那样随意,但会给你一个不错的样本.

请参阅MSDN页面.

  • 正如Rob Boek在下面指出的那样,表格会产生结果,因此不是获得***随机结果的好方法 (7认同)

Rob*_*oek 37

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)
Run Code Online (Sandbox Code Playgroud)

SalesOrderID列包含在CHECKSUM表达式中,以便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
Run Code Online (Sandbox Code Playgroud)

如果您可以使用TABLESAMPLE,它将为您提供最佳性能.否则使用newid()/ filter方法.如果你有一个大的结果集,newid()/ order by应该是最后的手段.


Kyl*_*lan 21

MSDN上的大表中随机选择行有一个简单明了的解决方案,可以解决大规模的性能问题.

  SELECT * FROM Table1
  WHERE (ABS(CAST(
  (BINARY_CHECKSUM(*) *
  RAND()) as int)) % 100) < 10
Run Code Online (Sandbox Code Playgroud)

  • 我在具有35个条目的表上运行了此查询,并经常在结果集中保留其中两个。这可能是rand()或上面的组合的问题-但出于这个原因,我拒绝了这种解决方案。同样,结果的数量从1到5不等,因此在某些情况下这可能也不可接受。 (2认同)
  • “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整数)))%100`。如果您的数据遇到此问题,请将“BINARY_CHECKSUM”乘以 9923。 (2认同)

RJa*_*nes 11

此链接在Orderby(NEWID())和具有1,7和13百万行的表的其他方法之间进行了有趣的比较.

通常,当在讨论组中询问有关如何选择随机行的问题时,建议使用NEWID查询; 它很简单,适用于小桌子.

SELECT TOP 10 PERCENT *
  FROM Table1
  ORDER BY NEWID()
Run Code Online (Sandbox Code Playgroud)

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

  1. 分拣操作通常具有与之相关的高成本.排序可以使用大量磁盘I/O并可以运行很长时间.
  2. 在最坏的情况下,tempdb可能会耗尽空间.在最好的情况下,tempdb会占用大量的磁盘空间,如果没有手动收缩命令,它们将永远不会被回收.

您需要的是一种随机选择不使用tempdb的行的方法,并且随着表变大而不会慢得多.这是一个关于如何做到这一点的新想法:

SELECT * FROM Table1
  WHERE (ABS(CAST(
  (BINARY_CHECKSUM(*) *
  RAND()) as int)) % 100) < 10
Run Code Online (Sandbox Code Playgroud)

这个查询背后的基本思想是我们想要为表中的每一行生成0到99之间的随机数,然后选择随机数小于指定百分比值的所有行.在这个例子中,我们希望随机选择大约10%的行; 因此,我们选择随机数小于10的所有行.

请阅读MSDN中的完整文章.

  • 嗨Deumber,很好找到,你可能会充实它,因为链接只有答案很可能被删除. (2认同)

Osk*_*ard 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
Run Code Online (Sandbox Code Playgroud)

在我的情况下,这是随机性(它不是真的,我知道)和速度之间最直接的妥协.根据需要改变TABLESAMPLE百分比(或行) - 百分比越高,样本越随机,但预计速度会线性下降.(注意TABLESAMPLE不接受变量)


Dan*_*ner 8

只需按随机数对表格进行排序,然后使用获取前5,000行TOP.

SELECT TOP 5000 * FROM [Table] ORDER BY newid();
Run Code Online (Sandbox Code Playgroud)

UPDATE

只是试了一下,newid()电话就足够了 - 不需要所有演员和所有数学.

  • 使用"所有演员和所有数学"的原因是为了获得更好的表现. (10认同)

Nan*_*nki 7

这是初始种子想法和校验和的组合,在我看来,它可以提供适当的随机结果,而无需使用 NEWID():

SELECT TOP [number] 
FROM table_name
ORDER BY RAND(CHECKSUM(*) * RAND())
Run Code Online (Sandbox Code Playgroud)


Jef*_*and 6

在 MySQL 中你可以这样做:

SELECT `PRIMARY_KEY`, rand() FROM table ORDER BY rand() LIMIT 5000;
Run Code Online (Sandbox Code Playgroud)

  • 嗯...喜欢供应商差异。Select 在 MySQL 上是原子的,但我想是以不同的方式。这将在 MySQL 中工作。 (4认同)
  • 这是行不通的。由于 select 语句是原子的,因此它只获取一个随机数并为每一行复制它。您必须在每一行上重新播种才能强制其更改。 (3认同)