从 SQL Server 表中选择 n 个随机行
Posted
技术标签:
【中文标题】从 SQL Server 表中选择 n 个随机行【英文标题】:Select n random rows from SQL Server table 【发布时间】:2010-10-25 08:00:55 【问题描述】:我有一个包含大约 50,000 行的 SQL Server 表。我想随机选择大约 5,000 行。我想到了一个复杂的方法,创建一个带有“随机数”列的临时表,将我的表复制到其中,循环遍历临时表并使用RAND()
更新每一行,然后从该表中选择随机数数字列
This article 建议使用NEWID()
函数。这看起来很有希望,但我不知道如何可靠地选择一定百分比的行。
以前有人这样做过吗?有什么想法吗?
【问题讨论】:
MSDN 有一篇很好的文章,涵盖了很多这些问题:Selecting Rows Randomly from a Large Table How to request a random row in SQL?的可能重复 【参考方案1】: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())
这样做的成本将是值的键扫描加上连接成本,这在具有小百分比选择的大表上应该是合理的。
【讨论】:
牢记 newid() 并不是一个非常好的伪随机数生成器,至少不如 rand() 好。但是,如果您只需要一些模糊随机的样本并且不关心数学质量等,那就足够了。否则你需要:***.com/questions/249301/… 嗯,很抱歉,如果这很明显.. 但是[yourPk]
指的是什么?编辑:Nvm,想通了...主键。杜尔
newid - guid 被设计为唯一但不是随机的.. 方法不正确
具有大量行,例如超过 100 万行 newid()
排序估计 I/O 成本会非常高,并且会影响性能。
关于在大表上使用 NEWID() 的成本的评论不是“纯粹的垃圾”。甚至在官方 Microsoft Doc docs.microsoft.com/en-us/previous-versions/software-testing/… 中也提到了这一点。 “ORDER BY 子句导致表中的所有行都被复制到 tempdb 数据库中,并在其中进行排序”。 RJardines 发布的答案对此进行了扩展。【参考方案2】:
在 mysql 中你可以这样做:
SELECT `PRIMARY_KEY`, rand() FROM table ORDER BY rand() LIMIT 5000;
【讨论】:
这行不通。由于 select 语句是原子的,它只抓取一个随机数并为每一行复制它。您必须在每一行重新播种以强制其更改。 嗯...喜欢供应商的差异。 Select 在 MySQL 上是原子的,但我想以不同的方式。这将在 MySQL 中工作。 ORDER BY rand() 为我工作。【参考方案3】:只需按随机数对表进行排序,然后使用TOP
获取前 5,000 行。
SELECT TOP 5000 * FROM [Table] ORDER BY newid();
更新
刚刚试了一下,newid()
调用就足够了 - 不需要所有演员表和所有数学。
【讨论】:
使用“所有演员表和所有数学”的原因是为了获得更好的性能。【参考方案4】:根据您的需要,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。
【讨论】:
正如下面 Rob Boek 所指出的,表格采样会聚集结果,因此不是获得少量随机结果的好方法 你介意这个问题是如何工作的:选择前 1% * from [tablename] order by newid() 因为 newid() 不是 [tablename] 中的列。 sql server 是否在每行内部附加列 newid() 然后进行排序? tablesample 对我来说是最好的答案,因为我正在对一个非常大的表进行复杂查询。毫无疑问,它非常快。当我多次运行时,我确实得到了返回的记录数量的变化,但它们都在可接受的误差范围内。 @FrenkyB 是的,基本上。 SQL Server 将为整个表中的每一行生成一个 GUID,然后对结果集进行排序。它可能有一个优化的排序算法,可以在达到 1% 阈值时短路,但它仍然必须为表中的每一行生成一个 GUID,然后才能开始排序。其他任何东西都是有偏见的样本。对于一个非常大的表,这意味着 SQL Server 将求助于一个临时表来进行排序。【参考方案5】: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 浮动 / 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()
只被评估一次,而不是每行,我不喜欢...【参考方案6】:
这对我有用:
SELECT * FROM table_name
ORDER BY RANDOM()
LIMIT [number]
【讨论】:
@user537824,你在 SQL Server 上试过了吗? RANDOM 不是函数,LIMIT 不是关键字。您正在执行的操作的 SQL Server 语法是select top 10 percent from table_name order by rand()
,但这也不起作用,因为 rand() 在所有行上返回相同的值。【参考方案7】:
试试这个:
SELECT TOP 10 Field1, ..., FieldN
FROM Table1
ORDER BY NEWID()
【讨论】:
【参考方案8】:MSDN 上的Selecting Rows Randomly from a Large Table 有一个简单、清晰的解决方案,可以解决大规模的性能问题。
SELECT * FROM Table1
WHERE (ABS(CAST(
(BINARY_CHECKSUM(*) *
RAND()) as int)) % 100) < 10
【讨论】:
非常有趣。阅读文章后,我真的不明白为什么RAND()
不会为每一行返回相同的值(这会破坏BINARY_CHECKSUM()
逻辑)。是因为它是在另一个函数内部调用的,而不是 SELECT 子句的一部分吗?
这个查询在不到一秒的时间内在一个有 6MM 行的表上运行。
我在一个有 35 个条目的表上运行了这个查询,并且经常在结果集中有两个条目。这可能是rand()
或以上组合的问题 - 但出于这个原因我拒绝了这个解决方案。此外,结果的数量从 1 到 5 不等,因此在某些情况下这也可能是不可接受的。
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_CHECKSUM
乘以 9923。
我选择了 9923 有点武断。但是,我希望它是素数(尽管与 100 互素可能就足够了)。还因为只要 RAND() 不是非常小,9923 就足够大以分散团块。【参考方案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 不会接受变量)
【讨论】:
【参考方案10】:还没有完全看到答案中的这种变化。我有一个额外的约束,我需要一个初始种子,每次都选择相同的行集。
对于 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 有什么好处吗? 绝对,您使用了种子参数并通过日期参数填充它,RAND() 函数除了使用完整的时间值外,它执行相同的操作,我想知道使用像上面的种子这样方便的创建参数有什么好处RAND() 与否? 啊!好的,这是项目的要求。我需要以一种确定的方式生成一个 n 随机行列表。基本上,领导层想知道在选择和处理行之前几天我们会选择哪些“随机”行。通过基于年/月构建种子值,我可以保证对该年的任何查询调用都将返回相同的“随机”列表。我知道,这很奇怪,可能有更好的方法,但它奏效了...... 哈哈 :) 我明白了,但我认为随机选择记录的一般含义与不同运行查询的记录不同。【参考方案11】:此链接对具有 1、7 和 13 百万行的表的 Orderby(NEWID()) 和其他方法进行了有趣的比较。
通常,当在讨论组中询问有关如何选择随机行的问题时,会提出 NEWID 查询;它很简单,非常适合小桌子。
SELECT TOP 10 PERCENT *
FROM Table1
ORDER BY NEWID()
但是,当您将 NEWID 查询用于大型表时,它有一个很大的缺点。 ORDER BY 子句使表中的所有行都复制到 tempdb 数据库中,并在其中进行排序。这会导致两个问题:
-
排序操作通常具有与之相关的高成本。
排序会使用大量的磁盘 I/O,并且可以运行很长时间。
在最坏的情况下,tempdb 可能会耗尽空间。在里面
最佳情况下,tempdb 会占用大量磁盘空间
如果没有手动收缩命令,它们永远不会被回收。
您需要一种随机选择行的方法,该方法不会使用 tempdb,并且不会随着表变大而变慢。这是一个关于如何做到这一点的新想法:
SELECT * FROM Table1
WHERE (ABS(CAST(
(BINARY_CHECKSUM(*) *
RAND()) as int)) % 100) < 10
这个查询背后的基本思想是,我们要为表中的每一行生成一个 0 到 99 之间的随机数,然后选择所有那些随机数小于指定百分比值的行。在本例中,我们希望随机选择大约 10% 的行;因此,我们选择所有随机数小于 10 的行。
请在MSDN阅读全文。
【讨论】:
【参考方案12】:newid() 似乎不能用在 where 子句中,所以这个解决方案需要一个内部查询:
SELECT *
FROM (
SELECT *, ABS(CHECKSUM(NEWID())) AS Rnd
FROM MyTable
) vw
WHERE Rnd % 100 < 10 --10%
【讨论】:
【参考方案13】:这是最初的种子想法和校验和的组合,在我看来,它可以在没有 NEWID() 成本的情况下给出适当的随机结果:
SELECT TOP [number]
FROM table_name
ORDER BY RAND(CHECKSUM(*) * RAND())
【讨论】:
【参考方案14】:我在子查询中使用它,它在子查询中返回了相同的行
SELECT ID ,
( SELECT TOP 1
ImageURL
FROM SubTable
ORDER BY NEWID()
) AS ImageURL,
GETUTCDATE() ,
1
FROM Mytable
然后我解决了在 where 中包含父表变量
SELECT ID ,
( SELECT TOP 1
ImageURL
FROM SubTable
Where Mytable.ID>0
ORDER BY NEWID()
) AS ImageURL,
GETUTCDATE() ,
1
FROM Mytable
注意 where 条件
【讨论】:
【参考方案15】:未指定使用的服务器端处理语言(例如 php、.net 等),但如果是 PHP,则获取所需的数字(或所有记录),而不是在查询中随机化,而是使用 PHP 的 shuffle功能。我不知道 .net 是否具有等效功能,但如果有,那么如果您使用的是 .net,则使用该功能
ORDER BY RAND() 可能会产生相当大的性能损失,具体取决于所涉及的记录数。
【讨论】:
我不记得我当时使用它的确切用途,但我可能在 C# 中工作,可能在服务器上,或者可能在客户端应用程序中,不确定。 C# 没有任何东西可以直接与 PHP 的 shuffle afaik 相媲美,但可以通过在 Select 操作中应用 Random 对象中的函数,对结果进行排序,然后取前 10% 来完成。但是我们必须从数据库服务器上的磁盘读取整个表并通过网络传输它,结果只能丢弃 90% 的数据。直接在数据库中处理它几乎肯定会更有效。 还有一个“性能损失”,在洗牌之前拉回这么多过多的数据。想象一个 10M 行的表,其中想要选择 10k 行。甚至使用 ORDER BY RAND(我不推荐的一种方法)的“性能损失”也可以在很大程度上抵消 fetch + load + shuffle。【参考方案16】:select * from table
where id in (
select id from table
order by random()
limit ((select count(*) from table)*55/100))
// to select 55 percent of rows randomly
【讨论】:
【参考方案17】:这是一种更新和改进的抽样形式。它基于使用 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)
)
注意事项:
虽然自 SQL Server 2016 起 SHA1 在技术上已被弃用,但它既足以完成任务,又比 MD5 或 SHA2_256 略快。根据需要使用不同的散列函数。如果表已经包含一个散列列(具有良好的分布),那么也可能会被使用。bigint
的转换很关键,因为它允许 2^63 位的“随机空间”应用模运算符;这远远超过 CHECKSUM 结果的 2^31 范围。这减少了极限处的模数误差,尤其是随着精度的提高。
只要适当地乘以模操作数和采样百分比,就可以改变采样精度。在这种情况下,即 1000 *
来解释 @sample_percent
中允许的 4 位精度。
可以将bigint
值乘以RAND()
以在每次运行时返回不同的行样本。这有效地改变了固定哈希值的排列。
如果@sample_percent
为100,查询计划器可以完全消除较慢的计算代码。记住“参数嗅探”规则。这样一来,无论是否启用采样,代码都可以留在查询中。
计算@sample_percent
,具有下限/上限,并在查询中添加TOP
“提示”,因为可能在派生表上下文中使用示例时很有用。 p>
-- 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 ..
【讨论】:
【参考方案18】:如果您知道您有大约 N 行并且您想要大约 K 随机行,您只需要以 K/N 的机会拉出任何给定的行。使用 RAND() 函数可以让您在 0 和 1 之间进行公平分配,您可以在 PROB = K/N 的情况下执行以下操作。对我来说工作得很快。
SELECT * FROM some_table WHERE RAND() < PROB
【讨论】:
以上是关于从 SQL Server 表中选择 n 个随机行的主要内容,如果未能解决你的问题,请参考以下文章
选择 n 个随机行,其中 n 与每个值占总人口的百分比成比例