从巨大的table生成随机样本,有条件
Generate random sample from huge table, with conditions
我有一个 table (>500GB),我需要从中 select 5000 个随机行,其中 table.condition = True 和 5000 table.condition = False 的随机行。到目前为止,我的尝试使用 tablesample,但不幸的是,任何 WHERE 子句仅在样本生成后应用。所以我看到这个工作的唯一方法是执行以下操作:
生成2个空temporary_tables -- temporary_table_true和temporary_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
创建一个循环,仅当我的 temporary_tables 的大小均为 5000 时才停止。
在该循环中,我在每次迭代中从 table 生成一批 100 个随机样本。从这些随机行中,我将带有 table.condition = True 的行插入到我的 temporary_table_true 中,将带有 table.condition = False 在我的 temporary_table_false.
你们能帮帮我吗?
- 有没有更好的方法?
- 如果没有,请问我如何编写第 2 部分和第 3 部分的代码?
教科书的解决方案是 运行 两个查询,一个用于 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 可以产生巨大的交易并长时间淹没您的服务器。
- 如果您需要生成另一个随机样本,运行 再次更新或更新。
我有一个 table (>500GB),我需要从中 select 5000 个随机行,其中 table.condition = True 和 5000 table.condition = False 的随机行。到目前为止,我的尝试使用 tablesample,但不幸的是,任何 WHERE 子句仅在样本生成后应用。所以我看到这个工作的唯一方法是执行以下操作:
生成2个空temporary_tables -- temporary_table_true和temporary_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
创建一个循环,仅当我的 temporary_tables 的大小均为 5000 时才停止。
在该循环中,我在每次迭代中从 table 生成一批 100 个随机样本。从这些随机行中,我将带有 table.condition = True 的行插入到我的 temporary_table_true 中,将带有 table.condition = False 在我的 temporary_table_false.
你们能帮帮我吗?
- 有没有更好的方法?
- 如果没有,请问我如何编写第 2 部分和第 3 部分的代码?
教科书的解决方案是 运行 两个查询,一个用于 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 可以产生巨大的交易并长时间淹没您的服务器。 - 如果您需要生成另一个随机样本,运行 再次更新或更新。