从巨大的table生成随机样本,有条件

Generate random sample from huge table, with conditions

我有一个 table (>500GB),我需要从中 select 5000 个随机行,其中 table.condition = True 和 5000 table.condition = False 的随机行。到目前为止,我的尝试使用 tablesample,但不幸的是,任何 WHERE 子句仅在样本生成后应用。所以我看到这个工作的唯一方法是执行以下操作:

  1. 生成2个空temporary_tables -- temporary_table_truetemporary_table_false -- 具有主table的结构,因此我可以迭代添加行。

    create temp temporary_table_true as select 
      table.condition, table.b, table.c, ... table.z
    from table LIMIT 0
    
    create temp temporary_table_false as select 
      table.condition, table.b, table.c, ... table.z
    from table LIMIT 0
    
  2. 创建一个循环,仅当我的 temporary_tables 的大小均为 5000 时才停止。

  3. 在该循环中,我在每次迭代中从 table 生成一批 100 个随机样本。从这些随机行中,我将带有 table.condition = True 的行插入到我的 temporary_table_true 中,将带有 table.condition = False 在我的 temporary_table_false.

你们能帮帮我吗?

教科书的解决方案是 运行 两个查询,一个用于 true 的行,一个用于 `false:

的行
SELECT * FROM mytable WHERE `condition`=true ORDER BY RAND() LIMIT 5000; 

SELECT * FROM mytable WHERE `condition`=false ORDER BY RAND() LIMIT 5000; 

WHERE 子句首先应用,以减少匹配的行,然后随机对行的子集进行排序,最多选择 5000 个。结果是一个随机子集。

此解决方案的优势在于它return是一组相当均匀分布的随机行,并且它会自动处理诸如真实比例未知的情况table 中为 false,如果其中一个条件值匹配少于 5000 行,甚至会处理。

缺点是对如此大的行集进行排序的成本非常高,而且索引无法帮助您按 RAND() 等不确定表达式进行排序。

如果您需要将其作为单个 SQL 查询,您可以使用 window 函数来执行此操作,但它仍然非常昂贵。

SELECT t.*
FROM (
  SELECT *, ROW_NUMBER() OVER (PARTITION BY `condition` ORDER BY RAND()) AS rownum
  FROM mytable
) AS t
WHERE t.rownum <= 5000;

另一种不使用随机排序操作的替代方法是执行 table-scan,并随机选择行的子集。但是您需要大致知道有多少行与每个条件值匹配,以便您可以估计其中约 5000 行的分数。例如,有 100 万行具有真值,50 万行具有假值:

SELECT * FROM mytable WHERE `condition`=true AND RAND()*1000000 < 5000;

SELECT * FROM mytable WHERE `condition`=false AND RAND()*500000 < 5000;

由于随机性,这不能保证 return 刚好 5000 行。但可能非常接近。而且table-scan还是挺贵的


O.Jones的回答给了我另一个思路。如果可以添加列,则可以在该列上添加索引。

ALTER TABLE `table` 
  ADD COLUMN rando FLOAT DEFAULT NULL,
  ADD INDEX (`condition`, rando);
UPDATE `table` SET rando = RAND() WHERE rando IS NULL;

然后您可以使用索引搜索。同样,您需要知道有多少行与每个值匹配才能执行此操作。

SELECT * FROM mytable 
WHERE `condition`=true AND rando < 5000/1000000
ORDER BY `condition`, rando
LIMIT 5000; 

SELECT * FROM mytable 
WHERE `condition`=true AND rando < 5000/500000
ORDER BY `condition`, rando
LIMIT 5000; 

如果使用我添加的索引,本例中的 ORDER BY 应该是 no-op。无论如何,这些行将按索引顺序读取,MySQL 的优化器不会对它们进行任何排序。

这个解决方案会快得多,因为它不需要对任何东西进行排序,也不需要执行 table-scan。 MySQL 有一个优化,可以在满足 LIMIT 后退出查询。

但缺点是,当您再次 运行 SELECT,或者如果不同的客户端 运行 查询时,它不会 return 不同的随机结果。您将不得不使用更新 re-randomize 整个 table 以获得不同的结果。根据您的需要,这可能不是 suitable。

向您的 table 添加一列并用随机数填充它。

ALTER TABLE `table` ADD COLUMN rando FLOAT DEFAULT NULL;
UPDATE `table` SET rando = RAND() WHERE rando IS NULL;

然后做

SELECT * 
  FROM `table` 
 WHERE rando > RAND() * 0.9
   AND condition = 0
 ORDER BY rando
 LIMIT 5000

condition = 1 再做一次,Bob 是你的叔叔。它将从随机行开始以随机顺序拉取行。

一些注意事项:

  • 0.9 可以提高您实际获得 5000 行的机会,而不是更少的数字。
  • 您可能必须将 LIMIT 1000 添加到 UPDATE 语句,然后 运行 多次添加以填充完整的 rando 列:尝试更新一个表中的所有行大 table 可以产生巨大的交易并长时间淹没您的服务器。
  • 如果您需要生成另一个随机样本,运行 再次更新或更新。