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

Posted

技术标签:

【中文标题】来自 Sql 数据库的简单随机样本【英文标题】:Simple Random Samples from a Sql database 【发布时间】:2010-09-19 22:37:23 【问题描述】:

如何在 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 在 cmets 中指出的,如果你在 SQL Server 上使用这种方法,你应该使用 T-SQL 函数 NEWID(),因为 RAND() @987654321 @。

编辑:5 年后

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

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

为了对包含 1000 个项目的表进行抽样,我计算行数并将结果抽样到平均 10,000 行,并使用 frozen_rand 列:

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

【问题讨论】:

这甚至在 SQL Server 中都行不通,因为RAND() 每次后续调用都返回相同的值。 好点——我要补充一点,SQL Server 用户应该使用 ORDER BY NEWID() 代替。 它仍然非常低效,因为它必须对所有数据进行排序。一定百分比的随机抽样技术更好,但我什至在这里阅读了一堆帖子后,我还没有找到一个足够随机的可接受的解决方案。 如果您阅读了这个问题,我特意问的是因为 ORDER BY RAND() 是 O(n lg n)。 如果您不太沉迷于 RAND() 的统计随机性,muposat 的回答非常棒。 【参考方案1】:

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

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

不只是在没有top 的情况下做(10000 rows) 的原因是TABLESAMPLE 逻辑给你的行数非常不准确(有时是75%,有时是1.25% 的倍数),所以你想要过采样并选择您想要的确切数字。 REPEATABLE (123) 用于提供随机种子。

【讨论】:

这看起来像是最佳答案的潜在有效版本(按RAND() 过滤)。有一些陷阱(基于存储布局的最有效的实现示例,对于某些应用程序可能不够随机),但这是一个很棒的工具。【参考方案2】:

试试

SELECT TOP 10000 * FROM table ORDER BY NEWID()

这是否会给出预期的结果,而不会过于复杂?

【讨论】:

请注意,NEWID() 特定于 T-SQL。 我很抱歉。它是。谢谢 但是知道是否有人像我一样以更好的方式来到这里很有用,并且正在使用 T-SQL ORDER BY NEWID() 在功能上与ORDER BY RAND() 相同——它为集合中的每一行调用RAND()——O(n)——然后对整个事物进行排序——O( n lg n)。换句话说,这是该问题希望改进的最坏情况解决方案。【参考方案3】:

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

WITH IDS AS (
     SELECT ID
     FROM MYTABLE;
)

SELECT ID FROM IDS ORDER BY mt_random() LIMIT 3000

【讨论】:

除了添加一些特定于 SQL 方言的注释之外,我认为这并不能回答如何在没有 'ORDER BY rand() LIMIT $1' 的情况下查询随机行样本的问题。【参考方案4】:

如果您确实需要m 行,实际上您将在SQL 之外生成ID 子集。大多数方法有时需要选择“第 n 个”条目,而 SQL 表实际上根本不是数组。假设键是连续的以便仅连接 1 和计数之间的随机整数也很难满足 - 例如 MySQL 本身不支持它,并且锁定条件是...tricky。

这是一个O(max(n, m lg n))-time, O(n)-space 解决方案,假设只是普通的 BTREE 键:

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

在 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])

【讨论】:

【参考方案5】:

我认为最快的解决方案是

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

这就是为什么我认为这应该可以完成这项工作。

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

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

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

这是 O(n),但不需要排序,因此它比 O(n lg n) 快

mysql 非常有能力为每一行生成随机数。试试这个 -

从 INFORMATION_SCHEMA.TABLES 限制 10 中选择 rand();

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

【讨论】:

首先,您遇到的问题是,这并不能真正回答问题,因为它返回的结果是半随机数,接近所需的数字但不一定完全是那个数字,而不是所需的精确结果数量。 接下来,关于效率,你的效率是O(n),其中n是表中的行数。这不如 O(m log m) 好,其中 m 是您想要的结果数,并且 m 虽然@user12861 没有得到准确的数字是正确的,但这是将数据集缩减到合适的粗略大小的好方法。 数据库如何为以下查询提供服务 - SELECT * FROM table ORDER BY RAND() LIMIT 10000 ?它必须首先为每一行创建一个随机数(与我描述的解决方案相同),然后对其进行排序.. 排序很昂贵!这就是为什么这个解决方案会比我描述的那个慢,因为不需要排序。您可以为我描述的解决方案添加一个限制,它不会给您超过该数量的行。正如有人正确指出的那样,它不会为您提供精确的样本量,但对于随机样本,精确通常不是严格要求。 有没有办法指定最小行数?【参考方案6】:

比 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() 将获取 20 条(超过 2000 万条)样本记录所需的时间从 3 分钟 减少到 0.0 秒

【讨论】:

【参考方案7】:

我想指出,所有这些解决方案似乎都可以在没有替换的情况下进行采样。从随机排序中选择前 K 行或加入包含以随机顺序的唯一键的表将产生一个随机样本,无需替换。

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

【讨论】:

【参考方案8】:

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

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

【讨论】:

非常酷!看起来它也不是由 PostgreSQL 或 MySQL/MariaDB 实现的,但如果您使用的是支持它的 SQL 实现,这是一个很好的答案。 我知道TABLESAMPLE在统计意义上不是随机的。【参考方案9】:

随便用

WHERE RAND() < 0.1 

获取 10% 的记录或

WHERE RAND() < 0.01 

获取1%的记录等

【讨论】:

这将为每一行调用 RAND,使其成为 O(n)。海报正在寻找比这更好的东西。 不仅如此,RAND() 还会为后续调用返回相同的值(至少在 MSSQL 上),这意味着您将获得整个表或一个都没有。【参考方案10】:

从观察开始,我们可以根据集合检索表的 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 查询的结果。

【讨论】:

【参考方案11】:

这里有一个关于这类问题的非常有趣的讨论: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。

我们可以尝试的三个假设:

    表中有唯一的索引主键

    要选择的随机行数(m)远小于表中的行数(n)

    唯一主键是一个整数,范围从 1 到 n,没有空格

只有假设 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) &lt 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 之外,几乎任何东西都可能在循环和随机方面做得更好需要生成数字。

【讨论】:

我建议在随机键选择上添加一个唯一索引,也许在插入时忽略重复项,然后你可以摆脱不同的东西,连接会更快。 我认为随机数算法可以使用一些调整——如提到的 UNIQUE 约束,或者只生成 2*m 数字,然后 SELECT DISTINCT, ORDER BY id(先到先得, 所以这减少到唯一约束) LIMIT m。我喜欢它。 至于向随机键选择添加唯一索引,然后在插入时忽略重复项,我认为这可能会让您回到 O(m^2) 行为而不是 O(m lg m)一种。不确定在一次插入随机行时服务器维护索引的效率如何。 关于生成 2*m 数字或其他东西的建议,我想要一个无论如何都能保证工作的算法。您的 2*m 随机数有超过 m 个重复项的可能性(很小),因此您的查询将不够。 如何获取表格的行数?【参考方案12】:

也许你可以这样做

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

【讨论】:

看起来这会随机选择我的数据片段;我正在寻找更复杂一点的东西——10,000 个随机分布的行。 那么你唯一的选择,如果你想在数据库中做,就是 ORDER BY rand()。

以上是关于来自 Sql 数据库的简单随机样本的主要内容,如果未能解决你的问题,请参考以下文章

带组的 SQL 随机样本

来自 ArrayType Pyspark 列的随机样本

数据帧的分层随机抽样

Python中的加权随机样本问题,怎么解决

生成对抗网络

来自给定双变量离散分布的随机样本