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

Select n random rows from SQL Server table

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

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

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


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())

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


根据您的需要,TABLESAMPLE将为您提供几乎随机和更好的性能。
这在MS SQL Server 2005及更高版本上可用。

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

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

1
SELECT top 1 percent * FROM [tablename] ORDER BY newid()

花了20多分钟。

1
SELECT * FROM [tablename] tablesample(1 percent)

花了2分钟。

对于TABLESAMPLE中的较小样本,性能也会提高,而newid()则不会。

请记住,这不像newid()方法那样随机,但会给你一个不错的采样。

请参阅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) &amp; 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会占用大量磁盘空间
    在没有手动收缩命令的情况下永远不会被回收。
  • 您需要的是一种随机选择不使用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不接受变量)


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

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

    UPDATE

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


    这是初始种子想法和校验和的组合,它让我在没有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

    newid()的速度明显慢于rand(checksum(*)),因此您可能不希望将其用于大型记录集。

    选择初始种子:

    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]