【问题标题】:Select N random records in SQL Server without repetition在 SQL Server 中选择 N 条随机记录而不重复
【发布时间】:2011-08-11 22:35:59
【问题描述】:

如何一次从一个表中选择 N 条随机记录,而不重复以前由同一操作返回的记录?

一个明显的解决方案是:

SELECT TOP 5 * FROM
MyTable
WHERE Id NOT IN (SELECT Id FROM PreviouslyReturned)
ORDER BY newid()

但是随着 MyTable 开始增长,这不是效率很低吗?

我有一个很长的记录列表,我一次需要 5 条记录来进行回合制游戏,而不会重复给定游戏已经提取的任何记录。因为我知道大约会发生多少回合,所以我可以在游戏开始之前选择一个非常大的随机样本,但我宁愿它是“动态的”。我找到了this question,它使用 MySQL 的随机种子。

最终会有如此多的记录,重复不会成为问题(记录>> N),但在那之前,我需要记录是唯一的。在旁注中,我使用 Fluent NHibernate 作为我的持久层;也许 NHibernate 有一些允许这样做的功能。

【问题讨论】:

  • “最终会有如此多的记录,重复不会成为问题。”除非你能保证你的伪随机数生成器永远不会连续两次给你相同的值,否则我不知道你怎么能做出这样的陈述。
  • @Jim,我的意思是,千载难逢的重复对我的申请来说没什么大不了的。
  • 你的直觉可能在这里错了,Birthday problem

标签: sql sql-server fluent-nhibernate random unique


【解决方案1】:

不重复任何记录

这在程序员中不叫选择随机记录。随机选择的值比您想象的更频繁地重复,实际上计算重复的数量和长度是统计学家检测作弊的一种方法。

您要查找的内容称为随机播放。洗牌会随机化有限集合的顺序,比如卡片或钥匙。 (随机化行的顺序并不意味着选择随机行。)

在您的情况下,计划存储已为每个用户使用的密钥集。选择一组尚未包含在该组中的随机行。有几种方法可以存储每组随机的行(键);确保你能分辨出哪一个是 lastcurrent 集。

【讨论】:

  • 是的,你是对的。这是一个洗牌。如果我有几千行并且我已经提取了 100 行(我确实会跟踪),那么每个后续选择都需要一个 WHERE NOT IN (SELECT id FROM Used_Rows) 子句,这似乎真的很低效。还是这样?
  • 表达WHERE NOT IN (SELECT id FROM Used_Rows)的方式不止一种。例如,SQL Server 2005+ 支持EXCEPT。不同的表达式可能会给你不同的执行计划。如果只有几千行存在真正的性能问题,则可能首先是选择随机行,而不是根据 Used_Rows 中的值进行过滤。
【解决方案2】:

试试

SELECT TOP 5 *
FROM YOUR_TABLE
ORDER BY CHECKSUM(NEWID())

这里有一个相关问题详细介绍了随机随机选择的语义: Random select is not always returning a single row

SQL Server 每次查询都会计算一次 RAND,这意味着 mySQL 技巧无论如何都不起作用。

编辑:这也足够了

SELECT TOP 5 *
FROM YOUR_TABLE
ORDER BY NEWID()

我阅读了您更新的问题并提出了另一个建议:

上创建索引视图
SELECT mt.*
FROM MyTable mt
LEFT JOIN PreviouslyReturned pr ON mt.Id = pr.Id
WHERE pr.Id Is NULL

或类似的东西

然后

SELECT TOP 5 *
FROM YOUR_INDEXED_VIEW
ORDER BY NEWID()

【讨论】:

  • 为什么将NEWID() 包裹在CHECKSUM(...) 中会影响行的改组而不是更改顺序? CHECKSUM 的输出是否会因会话或查询的相同输入而有所不同?
  • @FreshCode:不会。在这种情况下,CHECKSUM 无关紧要。它实际上是偶然进入答案的。 ORDER BY NEWID() 将单独完成。 MS SQL 每次查询评估一次 RAND(),每行评估一次 NEWID(),这是重要的一点。关于 CHECKSUM 因会话而异,您可能想将其作为一个单独的问题提出。
  • @Chris,我知道每个采样都是独立的,并且可能会返回先前采样返回的行。我已经编辑了我的问题以澄清这一点。
  • @Chris,谢谢。 LEFT JOIN with a WHERE pr.Id is NULL 不等于 WHERE NOT IN( ... ) 吗?
  • @FreshCode:并非总是如此。当涉及 NULL 时,请注意 WHERE NOT IN 行为。即 WHERE Id NOT IN (1,2,NULL,etc) 我会比较执行计划...检查 LEFT JOIN 和 NOT IN 和 EXCEPT。
【解决方案3】:

您可以将表的所有主键存储在第二个表中,从该表中随机选择(从原始表中检索相关行)并在选择后删除它们(当然是从辅助表中)。

我希望这种方式比存储已使用的键和构造WHERE NOR IN(resp.EXCEPT)子句更有效(删除应该与插入一样高效,并且选择应该在没有附加条款)。但当然,这必须证明(通过分析......)

使用您引用的question 中的方法,选择五个随机 id 应该可以正常工作。

【讨论】:

  • 我想我可以有一个全局“shuffle”表,它在为空时会被补充,但是如果原始数据发生变化,插入新记录将是一件苦差事。
猜你喜欢
  • 2016-05-06
  • 2019-07-15
  • 2012-05-22
  • 1970-01-01
  • 1970-01-01
  • 2013-04-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多