Simple Random Samples from a Sql database
如何在SQL中获取有效的简单随机样本?有关的数据库正在运行MySQL。我的表至少有200,000行,我想要一个大约10,000的简单随机样本。
"显而易见"的答案是:
对于大型表,这太慢了:它对每一行调用RAND()(已经将其放在O(n)),并对它们进行排序,使其充其量为O(n lg n)。有没有办法比O(n)更快地做到这一点?
注意:正如Andrew Mao在注释中指出的那样,如果在SQL Server上使用这种方法,则应使用T-SQL函数NEWID(),因为RAND()可能对所有行返回相同的值。
编辑:5年后
我再次遇到了一个更大的表的问题,最终使用了@ignorant解决方案的一个版本,进行了两次调整:
- 将行采样到我所需样本大小的2-5倍,以便宜的价格订购RAND()
- 在每次插入/更新时,将RAND()的结果保存到索引列中。 (如果您的数据集不是很重更新,则可能需要寻找另一种方法来使该列保持最新状态。)
要获取一个表的1000个项目的样本,我对数据行进行计数,并使用Frozen_rand列对结果进行平均采样,平均减少到10,000行:
1 2 3 4 5 6 |
(我的实际实现涉及更多工作,以确保我不会采样不足,并手动将rand_high包起来,但是基本思想是"将N随机减少到几千。")
尽管这样做有所牺牲,但它允许我使用索引扫描对数据库进行采样,直到足够小以再次进行ORDER BY RAND()。
我认为最快的解决方案是
这就是为什么我认为这应该做的原因。
- 它将为每一行创建一个随机数。数字介于0和1之间
- 如果生成的数字在0到.3(30%)之间,它将评估是否显示该行。
假设rand()正在生成均匀分布的数字。这是最快的方法。
我看到有人推荐了该解决方案,但他们却被拒绝,没有证据..这就是我要说的-
- 这是O(n),但不需要排序,因此它比O(nlg n)快
-
mysql非常有能力为每一行生成随机数。尝试这个 -
从INFORMATION_SCHEMA.TABLES限制10中选择rand();
由于所讨论的数据库是mySQL,因此这是正确的解决方案。
这里有关于这种类型问题的非常有趣的讨论:http://www.titov.net/2005/09/21/do-not-use-order-by-rand-or-how-to-get-random-行从 - 表/
我认为在没有任何假设的情况下,您的O(n lg n)解决方案是最好的。尽管实际上使用好的优化程序或稍有不同的技术,但您列出的查询可能会更好一些,O(m * n)其中m是所需的随机行数,因为它不必对整个大型数组进行排序,它可能只搜索最小的m次。但是对于您发布的那种数字,无论如何,m大于lg n。
我们可以尝试以下三种假设:
表中有一个唯一的,已索引的主键
您要选择的随机行数(m)比表中的行数(n)小得多
唯一主键是一个整数,范围是1到n,没有空格
仅假设1和2,我认为这可以在O(n)中完成,尽管您需要向表中写入一个完整的索引以匹配假设3,因此不一定需要快速的O(n)。如果我们可以另外假设该表有其他优点,则可以在O(m log m)中执行任务。假设3是一个易于使用的好属性。有了一个很好的随机数生成器,它可以保证在连续生成m个数时不会重复,因此O(m)解决方案是可能的。
给定这三个假设,基本思想是生成介于1和n之间的m个唯一的随机数,然后从表中选择具有这些键的行。我现在没有mysql或任何更新,所以在伪代码中看起来像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | create table RandomKeys (RandomKey int) create table RandomKeysAttempt (RandomKey int) -- generate m random keys between 1 and n for i = 1 to m insert RandomKeysAttempt select rand()*n + 1 -- eliminate duplicates insert RandomKeys select distinct RandomKey from RandomKeysAttempt -- as long as we don't have enough, keep generating new keys, -- with luck (and m much less than n), this won't be necessary while count(RandomKeys) < m NextAttempt = rand()*n + 1 if not exists (select * from RandomKeys where RandomKey = NextAttempt) insert RandomKeys select NextAttempt -- get our random rows select * from RandomKeys r join table t ON r.RandomKey = t.UniqueKey |
如果您真的担心效率,则可以考虑使用某种过程语言来生成随机密钥,并将结果插入数据库中,因为除SQL以外,几乎任何其他方法都可能在所需的循环和随机数生成方面更好。
比RAND()更快
我测试了该方法,它比
从http://technet.microsoft.com/zh-cn/library/ms189108%28v=sql.105%29.aspx:
非MSSQL版本-我没有测试
MSSQL版本:
1 2 |
这将选择?1%的记录。因此,如果需要选择确切的百分比或记录数,请以一定的安全余量估算百分比,然后使用更昂贵的
甚至更快
因为我有一个众所周知的索引列值范围,所以我能够进一步改进此方法。
例如,如果您的索引列具有均匀分布的整数[0..max],则可以使用该列随机选择N个小间隔。在程序中动态执行此操作,以为每次查询运行获取不同的集合。该子集选择将是O(N),它可以比整个数据集小几个数量级。
在我的测试中,我将使用ORDER BY RAND()从3分钟获得20条(超过2000万条)样本记录所需的时间减少到0.0秒!
显然,在某些版本的SQL中,有一个
http://technet.microsoft.com/en-us/library/ms189108(v=sql.105).aspx
只需使用
获得10%的记录或
获得1%的记录,等等。
我想指出的是,所有这些解决方案似乎都可以提供样品,无需更换。从随机排序中选择前K行,或以随机顺序连接到包含唯一键的表,将生成一个随机样本,无需替换。
如果要使样品独立,则需要更换样品。有关如何以类似于user12861解决方案的方式使用JOIN进行此操作的示例,请参见问题25451034。该解决方案是为T-SQL编写的,但该概念可在任何SQL数据库中使用。
如果确实需要
这是一个
任何在SQL外部生成随机子集的方法都必须至少具有这种复杂性。联接不能比使用BTREE的
在Pythonic伪代码中:
1 2 3 4 5 6 |
从观察到我们可以基于一个集合检索表的ID(例如计数5)开始:
我们可以得出的结果是,如果我们可以生成字符串
例如,在Java中:
1 2 3 4 5 6 |
如果id之间有间隔,则初始arraylist
也许你可以做