加快 SQL 连接速度

问题描述 投票:0回答:1

我有一个表 A,其中包含订单数据以及用于计时的序列号。我有另一个表 B,其中包含单独的订单数据,也带有序列号。两个表之间的序列号是同步的,这意味着表 A 中较高的序列号意味着它位于表 B 中较低的序列号之后。

我的目标是,对于表 A 中的每个订单,我想从表 B 中获取紧邻其之前的订单。为了实现这一点,我还在表 B 中创建了一个引导序列号,如下所示:

SELECT 
    *
    , LEAD(sequence, 1, 999999999999) OVER (order by sequence) as lead_sequence
FROM 
    table_b

这是我到目前为止的加入情况:

SELECT 
    *
FROM  
    table_a a  
INNER JOIN 
    table_b b
ON 
    a.sequence > b.sequence
AND 
    a.sequence < b.lead_sequence;

从逻辑上讲,这是可行的,但似乎性能不佳。有什么聪明的方法可以加快速度吗?除了序列号和单独的时间戳之外,我没有任何类型的上下文 ID 将这两个表链接在一起。

sql performance optimization snowflake-cloud-data-platform query-optimization
1个回答
0
投票

好问题:

所以让我们构建一些生成一些数据的代码,以允许重复测试:

with orders as (
    select 
        random() as r
        ,x
    from (
        values ('a'),('b')
    ) as t(x)
    ,table(generator(rowcount=>10))
)
select o.*,
    row_number() over (order by r) as rn
from orders as o
order by rn;

提供听起来像您所说的内容:

所以我们可以把它变成一个表,有100,000行而不是20行,然后从那里取出A表和B表,然后我们就有这样的表,其中有一些A行在B行之前.

create or replace table source_rows as
with orders as (
    select 
        random() as r
        ,x
    from (
        values ('a'),('b')
    ) as t(x)
    ,table(generator(rowcount=>100000))
)
select o.*,
    row_number() over (order by r) as rn
from orders as o
order by rn;

create or replace table a_rows as  
select x, rn
from source_rows 
where x = 'a';

create or replace table b_rows as  
select x, rn
from source_rows 
where x = 'b';
select count(*) from source_rows;
-- 200000
select count(*) from a_rows;
-- 100000
select count(*) from b_rows;
-- 100000

因此,如果我们按原样运行您的 SQL:

SELECT 
    *
FROM a_rows a  
JOIN (
    SELECT 
        *
        ,LEAD(rn, 1, 99999999999) OVER (order by rn) as lead_rn
    FROM b_rows
) b
    ON a.rn > b.rn
        AND a.rn < b.lead_rn;

在我的 100K 行(两个表中)上花费了 107 秒

因此 ON 中的范围连接可以替换为 BETWEEN,我经常会使用 BETWEEN 和 LESS THAN,例如:

    ON a.rn between> b.rn AND b.lead_rn
       AND a.rn > b.rn
       AND a.rn < b.lead_rn

为了避免您已排除的边,但因为您的状态它们来自相同的序列,所以不会有双打,我们可以只使用 BETWEEN,因为边将不存在。

    ON a.rn between> b.rn AND b.lead_rn
       AND a.rn > b.rn
       AND a.rn < b.lead_rn

现在对于相同的测试数据,上面花了109秒,所以这是我意想不到的。正如我所看到的,它在过去表现得更好。

检查结果是否相同:

select * from table(result_scan('01b0d613-0004-dbe1-0001-d15f00051096'))
minus 
select * from table(result_scan('01b0d60d-0004-db2d-0001-d15f0005504a'));

select * from table(result_scan('01b0d60d-0004-db2d-0001-d15f0005504a'))
minus
select * from table(result_scan('01b0d613-0004-dbe1-0001-d15f00051096'));

没有给出缺失的结果,所以很高兴看到。

所以让我们从混合物中删除主音:

create or reaplce table lead_b_rows as 
    SELECT 
        *
        ,LEAD(rn, 1, 99999999999) OVER (order by rn) as lead_rn
    FROM b_rows;
SELECT 
    *
FROM a_rows a  
JOIN lead_b_rows b
    ON a.rn > b.rn
        AND a.rn < b.lead_rn;

好吧,这也花了 109 秒,所以这意味着 LEAD 是免费的,并且 107,109 都是按“同一时间”的顺序排列的,这是有道理的,我们正在寻求 10% 以上的改进。

现在让我大脑兴奋的是,正好有 100K 行。我预计会出现一些

ABBBAAB
条纹,因此第四个 B 将绑定到之前的两个 A,并且我编写的第一个 SQL 来检查此计数 A,但现在我将所有这些都输入出来,我发现我想对双绑定 B 进行计数

select rn_1, count(*) as c_b_rn
from table(result_scan('01b0d613-0004-dbe1-0001-d15f00051096'))
group by 1
having c_b_rn > 1
order by c_b_rn desc;

啊,这样更好:

因此,在这个虚构的数据中,有 B 记录绑定到许多不同的 A 记录。我怀疑这不是您预期的用例。此时可能需要删除那些额外的匹配,这会增加更多的时间成本,但也不是 100% 清楚您想要这种行为,所以我可能会忽略这条路径。

所以如果我们走分桶路线:

对于此数据,我们知道最大间隙是 15,因此我们可以使用 3 个 10 个桶 (0,10,20),并且知道我们将覆盖数据的范围,因此给定 A 将绑定到最近的 B之前,我们将 a rn 向下取整至 10,并对 b 的行号执行相同操作,但还添加 0、10、20(1、2、3 在除以 10 后按原样使用,因此也需要在正确的值空间,但如果在除法之前完成加法,则可以使用 0,10,20),然后加入这些等连接样式,然后过滤

a.rn > b.rn
以避免“未来”b,然后过滤通过限定,每个 a.rn 的多桶匹配:

with a_bucket as (
    SELECT 
        *
        ,floor(rn/10) as bucket
    FROM a_rows
where rn < 100
), b_bucket as (
    SELECT 
        *
        ,floor(rn/10)+ t.d as bucket
    FROM b_rows
    CROSS JOIN (values (0),(1),(2)) as t(d)
where rn < 100
)--, bucketed as (
SELECT 
    a.x as a_x
    ,a.rn as a_rn
    ,b.x as b_x
    ,b.rn as b_rn
FROM a_bucket a  
JOIN b_bucket b
    on a.bucket = b.bucket
        and a.rn > b.rn
qualify row_number() over (partition by a_rn order by b_rn desc) = 1
order by a_rn

所以我们可以看到,a(2) 绑定到 b(1) a(,4,5,6) 绑定到 b(3) 并且 a(9) 绑定到 b(8) 意味着还有一个 b(7 )被跳过了。所以看起来效果很好。

我们可以删除那些测试过滤器

where rn < 100
,看看它如何在更大的 100K 数据集上工作:

我没想到 0.7 秒,这是一个进步..

我们最好检查一下结果是否都相同,它们是......

因此,如果您能够制定出始终覆盖您的桶窗口的“安全”保证金,您就可以节省大量资金。为此,对于我的示例代码,我更改为

values (0),(1),(2),(3),(4),(5),(6)
,但它仍然是 750 毫秒。将源大小从 100K 更改为 1M,上面的代码在您原来加入的 3.1s 版本中运行,我在 10 分钟后停止了。

© www.soinside.com 2019 - 2024. All rights reserved.