来自Sql数据库的简单随机样本

时间:2008-10-30 04:48:03

标签: mysql sql postgresql random random-sample

如何在SQL中使用高效的简单随机样本?有问题的数据库正在运行MySQL;我的表至少有200,000行,我想要一个约10,000的简单随机样本。

“显而易见”的答案是:

SELECT * FROM table ORDER BY RAND() LIMIT 10000

对于大型表来说,这太慢了:它为每一行调用RAND()(已将它放在O(n)处)并对它们进行排序,最多使它成为O(n lg n)。有没有办法比O(n)更快地完成这个?

注意:正如Andrew Mao在评论中指出的那样,如果你在SQL Server上使用这种方法,你应该使用T-SQL函数NEWID(),因为RAND(){{ 3}}

编辑:5年后

我用更大的表再次遇到了这个问题,并最终使用了@ ignorant的解决方案,并进行了两次调整:

  • 将行采样到我想要的样本大小2-5倍,以便宜的ORDER BY RAND()
  • 在每次插入/更新时将RAND()的结果保存到索引列。 (如果您的数据集不是非常大,那么您可能需要找到另一种方法来保持此列的新鲜度。)

要获取表的1000项样本,我计算行并使用frozen_rand列将结果平均下降到10,000行:

SELECT COUNT(*) FROM table; -- Use this to determine rand_low and rand_high

  SELECT *
    FROM table
   WHERE frozen_rand BETWEEN %(rand_low)s AND %(rand_high)s
ORDER BY RAND() LIMIT 1000

(我的实际实现涉及更多的工作,以确保我没有欠采样,并手动包裹rand_high,但基本的想法是“随机削减你的N到几千。”)

虽然这会做出一些牺牲,但它允许我使用索引扫描对数据库进行采样,直到它再次小到ORDER BY RAND()。

12 个答案:

答案 0 :(得分:39)

我认为最快的解决方案是

select * from table where rand() <= .3

这就是为什么我认为这应该做的工作。

  • 它将为每一行创建一个随机数。该数字介于0和1之间
  • 如果生成的数字介于0和.3(30%)之间,它会评估是否显示该行。

这假设rand()以均匀分布生成数字。这是最快捷的方式。

我看到有人推荐了这个解决方案而且他们在没有证据的情况下被击落了......这就是我要说的 -

  • 这是O(n),但不需要排序,因此它比O(n lg n)快
  • mysql非常能够为每一行生成随机数。试试这个 -

    从INFORMATION_SCHEMA.TABLES限制10;

  • 中选择rand()

由于有问题的数据库是mySQL,这是正确的解决方案。

答案 1 :(得分:21)

此处对此类问题进行了非常有趣的讨论: http://www.titov.net/2005/09/21/do-not-use-order-by-rand-or-how-to-get-random-rows-from-table/

我认为绝对没有关于表格的假设你的O(n lg n)解决方案是最好的。虽然实际上有一个好的优化器或稍微不同的技术,但是你列出的查询可能会更好一些,O(m * n)其中m是所需的随机行数,因为它不必对整个大型数组进行排序,它可以只搜索最小的m次。但是对于你发布的那种数字,无论如何m都大于lg n。

我们可能尝试三种假设:

  1. 中有一个唯一的,已建立索引的主键
  2. 您要选择的随机行数(m)远小于表中的行数(n)

  3. 唯一主键是一个整数,范围从1到n,没有间隙

  4. 只有假设1和2我认为这可以在O(n)中完成,尽管你需要在表中写一个完整的索引来匹配假设3,所以它不一定是快速的O(n)。如果我们可以另外假设关于表的其他好处,我们可以在O(m log m)中执行任务。假设3将是一个简单的好的额外属性来使用。使用一个很好的随机数生成器,在连续生成m个数字时保证没有重复,可以使用O(m)解决方案。

    鉴于这三个假设,基本思想是在1和n之间生成m个唯一的随机数,然后从表中选择带有这些键的行。我现在面前没有mysql或任何东西,所以在稍微伪代码中,这看起来像是:

    
    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以外的任何东西都可能更好地处理循环和随机需要数字生成。

答案 2 :(得分:4)

比ORDER BY RAND()

更快

我测试此方法比ORDER BY RAND()快得多,因此它在 O(n)时间内运行,并且速度非常快。

来自http://technet.microsoft.com/en-us/library/ms189108%28v=sql.105%29.aspx

非MSSQL版 - 我没有测试过这个

SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= RAND()

MSSQL版本:

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

这将选择约1%的记录。因此,如果您需要选择精确的百分比或记录数,请使用更昂贵的ORDER BY RAND()方法,以一定的安全范围估算您的百分比,然后从结果集中随机抽取多余的记录。

更快

我能够进一步改进这种方法,因为我有一个众所周知的索引列值范围。

例如,如果您的索引列具有均匀分布的整数[0..max],则可以使用该列随机选择N个小间隔。在程序中动态执行此操作,以便为每个查询运行获取不同的集合。此子集选择将 O(N),这可能比您的完整数据集小许多个数量级。

在我的测试中,我使用ORDER BY RAND()减少了从 3分钟获得20(20 mil)样本记录所需的时间,直到 0.0秒!< / p>

答案 3 :(得分:3)

只需使用

WHERE RAND() < 0.1 

获得10%的记录或

WHERE RAND() < 0.01 

获得1%的记录等

答案 4 :(得分:3)

显然在某些版本的SQL中有一个TABLESAMPLE命令,但它不在所有SQL实现中(特别是Redshift)。

http://technet.microsoft.com/en-us/library/ms189108(v=sql.105).aspx

答案 5 :(得分:1)

我想指出所有这些解决方案似乎都没有替换。从随机排序中选择前K行或连接到包含随机顺序的唯一键的表将产生随机样本而无需替换。

如果您希望样品独立,则需要更换样品。有关如何使用JOIN以类似于user12861解决方案的方式执行此操作的示例,请参阅Question 25451034。该解决方案是为T-SQL编写的,但该概念适用于任何SQL数据库。

答案 6 :(得分:1)

在某些方言中,例如Microsoft SQL Server,PostgreSQL和Oracle(但不是MySQL或SQLite),您可以执行类似的操作

select distinct top 10000 customer_id from nielsen.dbo.customer TABLESAMPLE (20000 rows) REPEATABLE (123);

不只使用(10000 rows)而不执行top的原因是TABLESAMPLE逻辑给您的行数极其不精确(例如有时是75%,有时是1.25%的行) ),因此您要进行过采样并选择所需的确切数字。 REPEATABLE (123)用于提供随机种子。

答案 7 :(得分:0)

从观察开始我们可以根据集合检索表的id(例如,计数5):

select *
from table_name
where _id in (4, 1, 2, 5, 3)

我们可以得出结果,如果我们可以生成字符串"(4, 1, 2, 5, 3)",那么我们将比RAND()更有效。

例如,在Java中:

ArrayList<Integer> indices = new ArrayList<Integer>(rowsCount);
for (int i = 0; i < rowsCount; i++) {
    indices.add(i);
}
Collections.shuffle(indices);
String inClause = indices.toString().replace('[', '(').replace(']', ')');

如果id有间隙,那么初始arraylist indices是对id进行sql查询的结果。

答案 8 :(得分:0)

如果您需要完全m行,实际上您将在SQL之外生成您的ID子集。大多数方法都需要在某些时候选择&#34; nth&#34;条目,SQL表根本就不是数组。假设密钥是连续的,以便只加入1和计数之间的随机整数也很难满足 - 例如,MySQL本身并不支持它,锁定条件是...... tricky

这是一个O(max(n, m lg n)) - 时间,O(n) - 空间解决方案,假设只有普通的BTREE密钥:

  1. O(n)
  2. 中您喜欢的脚本语言,以任意顺序将数据表的键列的所有值提取到数组中
  3. 执行Fisher-Yates shuffle,在m互换后停止,并在[0:m-1]
  4. 中提取子数组ϴ(m)
  5. &#34;加入&#34;包含SELECT ... WHERE id IN (<subarray>)
  6. 中原始数据集(例如O(m lg n))的子数组

    在SQL之外生成随机子集的任何方法都必须至少具有这种复杂性。与BTREE O(m lg n)相比,加入速度可能更快(因此O(m)声明对于大多数引擎来说都是幻想的)并且随机播放位于nm lg n以下且不会影响渐近行为。

    在Pythonic伪代码中:

    ids = sql.query('SELECT id FROM t')
    for i in range(m):
      r = int(random() * (len(ids) - i))
      ids[i], ids[i + r] = ids[i + r], ids[i]
    
    results = sql.query('SELECT * FROM t WHERE id IN (%s)' % ', '.join(ids[0:m-1])
    

答案 9 :(得分:0)

在Netezza中选择3000条随机记录:

function sayHi(){
    console.log('hi');
}

var collectionOfObjects = [];

for(var i=0; i<1000; i++){
    collectionOfObjects.push({ sayHi: sayHi });
}

答案 10 :(得分:0)

尝试

SELECT TOP 10000 * FROM table ORDER BY NEWID()

这会带来想要的结果,而不会过于复杂吗?

答案 11 :(得分:-2)

也许你可以做到

SELECT * FROM table LIMIT 10000 OFFSET FLOOR(RAND() * 190000)