提问人:Nick Crews 提问时间:3/2/2023 最后编辑:Nick Crews 更新时间:3/3/2023 访问量:98
[SQL]:从笛卡尔连接中高效采样
[SQL]: Efficient sampling from cartesian join
问:
我有两张桌子。我想要的是来自所有可能配对的随机样本。假设 t1 的大小是 100,t2 的大小是 200,我想要一个 300 配对的样本。这样做的幼稚方法(在在线 duckdb shell 上运行)是:
CREATE TABLE t1 as FROM 'https://shell.duckdb.org/data/tpch/0_01/parquet/lineitem.parquet' LIMIT 100;
CREATE TABLE t2 as FROM 'https://shell.duckdb.org/data/tpch/0_01/parquet/lineitem.parquet' LIMIT 200;
SELECT * FROM t1, t2 USING SAMPLE 300;
但是,这会导致一个完整的叉积,然后是样本。对于真正的大桌子来说,这是棘手的。EXPLAIN
我尝试颠倒操作顺序,例如洗牌后跟限制,用
SELECT * FROM (FROM t1 ORDER BY RANDOM()) AS t1, (FROM t2 ORDER BY RANDOM()) AS t2 LIMIT 300;
这更智能,并且能够根据 .但是,这种对的采样是有偏见的,因为在 duckdb 的笛卡尔积实现中(我猜大多数引擎都是相同的,事实上我认为如果你想流式传输,它是必需的)它就像一个嵌套的 for 循环,其中 t1 中的第一条记录与 t2 中的所有记录配对,然后转到 t1 中的第二条记录, 等。因此,t1 中的某些记录是“过采样”的。EXPLAIN
因此,我的想法是:让我们以随机顺序从每个表中抽取尽可能多的记录,流式处理,并根据需要一遍又一遍地对同一个表进行重新采样(因为我们需要 300 对,而每个源表的长度只有 100 或 200)。然后将这些流配对在一起并将它们拉上拉链。在伪代码中:
WITH
t1_generator AS (
FROM t1 ORDER BY RANDOM()
UNION ALL
FROM t1 ORDER BY RANDOM()
UNION ALL
FROM t1 ORDER BY RANDOM()
-- etc.
),
t2_generator AS (
FROM t2 ORDER BY RANDOM()
UNION ALL
FROM t2 ORDER BY RANDOM()
UNION ALL
FROM t2 ORDER BY RANDOM()
-- etc.
),
s1 AS (
SELECT *, ROW_NUMBER() OVER () AS __rn FROM t1_generator
),
s2 AS (
SELECT *, ROW_NUMBER() OVER () AS __rn FROM t2_generator
),
zipped AS (
SELECT * EXCLUDE __rn FROM s1 JOIN s2 ON s1.__rn = s2.__rn
)
SELECT * FROM zipped
LIMIT 300
除了 和 是硬编码的,你需要计算你需要多少 s,这很丑陋。它还在不替换的情况下进行采样(即循环风格,在重新访问之前会遍历所有 t1),当我真的想要替换时。我尝试了递归 CTE 的东西,但这些方法并不能在每一轮中重新洗牌数据。t1_generator
t2_generator
UNION ALL
关于如何做到这一点的任何想法?如果这很重要,我正在使用 duckdb。谢谢!
答:
我假设你在两个表上都有一个索引——在表 A 上,它从 1 到 N,在表 B 上,它从 1 到 M
然后你只需要生成 300 对随机元组(R(1..N)、R(1..M)) 将其存储在表中,然后将表 A 连接到第一列,将表 B 连接到第二列
对于大型表,这比从交叉联接采样要快得多
评论