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

时间:2009-05-11 16:19:12

标签: sql sql-server random

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

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

以前有人这样做过吗?有什么想法吗?

17 个答案:

答案 0 :(得分:355)

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 :(得分:76)

根据您的需求,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 page

答案 2 :(得分:37)

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

从性能的角度来看,

TABLESAMPLE()是好的,但你会得到结果的结果(页面上的所有行都会被返回)。

对于性能更好的真随机样本,最好的方法是随机过滤掉行。我在SQL Server联机丛书文章 Limiting Results Sets by Using TABLESAMPLE 中找到了以下代码示例:

  

如果你真的想要随机抽样   单个行,修改您的查询   随机过滤掉行,而不是   使用TABLESAMPLE。例如,   以下查询使用NEWID   函数返回大约一个   行的百分比   Sales.SalesOrderDetail表:

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

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

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

答案 3 :(得分:21)

MSDN上的

Selecting Rows Randomly from a Large Table有一个简单明了的解决方案,可以解决大规模的性能问题。

  SELECT * FROM Table1
  WHERE (ABS(CAST(
  (BINARY_CHECKSUM(*) *
  RAND()) as int)) % 100) < 10

答案 4 :(得分:9)

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

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

<强>更新

刚尝试过,newid()来电就足够了 - 不需要所有演员和所有数学。

答案 5 :(得分: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不接受变量)

答案 6 :(得分:8)

这个链接在Orderby(NEWID())和其他方法(包含1,7和13百万行的表)之间进行了有趣的比较。

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

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

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

  1. 分拣操作通常具有与之相关的高成本。 排序可以使用大量磁盘I / O并且可以运行很长时间。
  2. 在最糟糕的情况下,tempdb可能会耗尽空间。在里面 在最佳情况下,tempdb会占用大量磁盘空间 在没有手动收缩命令的情况下永远不会被回收。
  3. 您需要的是一种随机选择不使用tempdb的行的方法,并且随着表变大而不会慢得多。以下是关于如何做到这一点的新想法:

    SELECT * FROM Table1
      WHERE (ABS(CAST(
      (BINARY_CHECKSUM(*) *
      RAND()) as int)) % 100) < 10
    

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

    请阅读MSDN中的完整文章。

答案 7 :(得分:4)

在MySQL中你可以这样做:

SELECT `PRIMARY_KEY`, rand() FROM table ORDER BY rand() LIMIT 5000;

答案 8 :(得分:4)

这是初始种子构思和校验和的组合,它让我在没有NEWID()成本的情况下给出正确的随机结果:

SELECT TOP [number] 
FROM table_name
ORDER BY RAND(CHECKSUM(*) * RAND())

答案 9 :(得分: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 */

如果您需要选择给定种子的同一组,这似乎有效。

答案 10 :(得分:2)

试试这个:

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

答案 11 :(得分:0)

看来newid()不能在where子句中使用,所以这个解决方案需要一个内部查询:

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

答案 12 :(得分: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

请注意condtition的位置

答案 13 :(得分:0)

没有指定正在使用的服务器端处理语言(例如PHP,.net等),但如果是PHP,请抓取所需的数字(或所有记录),而不是随机化在查询中使用PHP的shuffle函数。我不知道.net是否具有相同的功能但如果确实如此,那么如果您使用.net

ORDER BY RAND()可能会有相当大的性能损失,具体取决于所涉及的记录数量。

答案 14 :(得分:0)

从表中选择* 在( 从表中选择ID 按random()排序 限制((从表格中选择count(*))* 55/100))

//随机选择55%的行

答案 15 :(得分:0)

这是一种更新和改进的抽样形式。它基于使用 CHECKSUM / BINARY_CHECKSUM 和模数的其他一些答案的相同概念。

使用与此类似的实现而不是其他答案的原因:

  • 在庞大的数据集上相对较快,并且可以有效地用于派生查询中/与派生查询一起使用。数以百万计的预过滤行可以在几秒钟内采样而无需使用 tempdb,并且如果与查询的其余部分保持一致,则开销通常很小。
  • 在运行数据时不会遇到 CHECKSUM(*) / BINARY_CHECKSUM(*) 问题。使用 CHECKSUM(*) 方法时,可以在“块”而不是“随机”!这是因为CHECKSUM 更喜欢速度而不是分发
  • 导致稳定/可重复行选择,并且可以简单地更改以在后续查询执行中生成不同的行。使用 NEWID() 的方法(例如 CHECKSUM(NEWID()) % 100)永远不会稳定/可重复。
  • 允许提高样本精度并减少引入的统计误差。采样精度也可以调整。 CHECKSUM 仅返回 int 值。
  • 不使用 ORDER BY NEWID(),因为排序可能成为大型输入集的重大瓶颈避免排序还可以减少内存和 tempdb 的使用.
  • 不使用 TABLESAMPLE,因此与 WHERE 预过滤器一起使用。

缺点/限制:

  • 执行时间稍慢,使用 CHECKSUM(*)。使用哈希字节,如下所示,每百万行增加大约 3/4 秒的开销。这是我的数据,在我的数据库实例上:YMMV。 如果使用来自 HASHBYTES 的结果“良好分布”bigint 值的持久计算列,则可以消除这种开销。
  • 与基本的 SELECT TOP n .. ORDER BY NEWID() 不同,不能保证返回“正好 N”行。相反,它返回一个 percentage 行,其中预先确定了这样的值。对于非常小的样本量,这可能会导致选择 0 行。此限制与 CHECKSUM(*) 方法相同。

这里是要点:

-- Allow a sampling precision [0, 100.0000].
declare @sample_percent decimal(7, 4) = 12.3456

select
    t.*
from t
where 1=1
    and t.Name = 'Mr. No Questionable Checksum Usages'
    and ( -- sample
        @sample_percent = 100
        or abs(
            -- Choose appropriate identity column(s) for hashbytes input.
            -- For demonstration it is assumed to be a UNIQUEIDENTIFIER rowguid column.
            convert(bigint, hashbytes('SHA1', convert(varbinary(32), t.rowguid)))
        ) % (1000 * 100) < (1000 * @sample_percent)
    )

注意事项:

  • 虽然 SHA1 在技术上自 SQL Server 2016 起已被弃用,但它足以完成任务,并且比 MD5 或 SHA2_256 略快。根据相关情况使用不同的散列函数。如果该表已包含散列列(分布良好),则也可能会使用该列。
  • bigint 的转换至关重要,因为它允许 2^63 位的“随机空间”应用模数运算符;这远远超过 CHECKSUM 结果的 2^31 范围。这降低了模数误差的极限,尤其是随着精度的提高。
  • 只要适当地乘以模数操作数和样本百分比,就可以更改采样精度。在本例中,1000 * 用于说明 @sample_percent 中允许的 4 位精度。
  • 可以将 bigint 值乘以 RAND() 以在每次运行时返回不同的行样本。这有效地改变了固定哈希值的排列。
  • 如果 @sample_percent 为 100,查询规划器可以完全消除较慢的计算代码。记住“参数嗅探”规则。无论是否启用采样,这都允许将代码保留在查询中。

计算具有下限/上限的 @sample_percent,并在查询中添加 TOP“提示”,因为当样本用于派生表时可能很有用上下文。

-- Approximate max-sample and min-sample ranges.
-- The minimum sample percent should be non-zero within the precision.
declare @max_sample_size int = 3333333
declare @min_sample_percent decimal(7,4) = 0.3333
declare @sample_percent decimal(7,4) -- [0, 100.0000]
declare @sample_size int

-- Get initial count for determining sample percentages.
-- Remember to match the filter conditions with the usage site!
declare @rows int
select @rows = count(1)
    from t
    where 1=1
        and t.Name = 'Mr. No Questionable Checksum Usages'

-- Calculate sample percent and back-calculate actual sample size.
if @rows <= @max_sample_size begin
    set @sample_percent = 100
end else begin
    set @sample_percent = convert(float, 100) * @max_sample_size / @rows
    if @sample_percent < @min_sample_percent
        set @sample_percent = @min_sample_percent
end
set @sample_size = ceiling(@rows * @sample_percent / 100)

select *
from ..
join (
    -- Not a precise value: if limiting exactly at, can introduce more bias.
    -- Using 'option optimize for' avoids this while requiring dynamic SQL.
    select top (@sample_size + convert(int, @sample_percent + 5))
    from t
    where 1=1
        and t.Name = 'Mr. No Questionable Checksum Usages'
        and ( -- sample
            @sample_percent = 100
            or abs(
                convert(bigint, hashbytes('SHA1', convert(varbinary(32), t.rowguid)))
            ) % (1000 * 100) < (1000 * @sample_percent)
        )
) sampled
on ..

答案 16 :(得分:-2)

这对我有用:

SELECT * FROM table_name
ORDER BY RANDOM()
LIMIT [number]