[SQL]:从笛卡尔连接中高效采样

[SQL]: Efficient sampling from cartesian join

提问人:Nick Crews 提问时间:3/2/2023 最后编辑:Nick Crews 更新时间:3/3/2023 访问量:98

问:

我有两张桌子。我想要的是来自所有可能配对的随机样本。假设 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_generatort2_generatorUNION ALL

关于如何做到这一点的任何想法?如果这很重要,我正在使用 duckdb。谢谢!

sql 记录联动 duckdb

评论

0赞 nbk 3/2/2023
这就是交叉连接始终实现的方式,QAS 您需要一种确定性的方式,因此循环是最好的方法。
0赞 Nick Crews 3/2/2023
我用一个更有针对性的问题编辑了我原来的问题,也许你可以再看一遍@nbk?我同意交叉加入是行不通的,所以我正在尝试其他方法。
0赞 nbk 3/2/2023
为什么不从每张桌子上抽取 5 个或您需要的东西,添加一个row_number并加入它们。
0赞 Nick Crews 3/3/2023
啊,是的,当样本大小小于两个表中的行数时,这很好用,但是假设我的表大小为 100 和 200,并且我想要一个 300 对的样本,这比两个源表都大。所以我需要一遍又一遍地重新采样表格......我编辑了原始问题,以便更清楚。
0赞 Hogan 3/3/2023
两个表都有索引吗?是 (aprox) 1..n 和 1..m 吗?

答:

2赞 Hogan 3/3/2023 #1

我假设你在两个表上都有一个索引——在表 A 上,它从 1 到 N,在表 B 上,它从 1 到 M

然后你只需要生成 300 对随机元组(R(1..N)、R(1..M)) 将其存储在表中,然后将表 A 连接到第一列,将表 B 连接到第二列

对于大型表,这比从交叉联接采样要快得多

评论

0赞 Nick Crews 3/21/2023
这将起作用,但仍然不是流式处理,因为我们必须实现对表。不过,可能更有效,因为我们只需要存储两个 int 列。我认为这是我迄今为止看到的最好的主意,但如果它是流媒体会更好。
0赞 Hogan 3/22/2023
我不确定你说的流媒体是什么意思
0赞 Nick Crews 3/22/2023
也许我也不明白。但据我了解,某些 SQL 查询(例如 LIMIT)不会具体化整个结果集,而只会具体化所需的行。通过这种实现,我们始终实现 300 个索引对。但也许我正在超越自己,这对我的用例来说实际上不是问题。
0赞 Hogan 3/22/2023
好的,这正是我要解决的问题。您事先选择索引号,并在联接这些数字时将它们放入表中,因为索引只会读取表中的元素。