第五篇 - 大主子表关联时的 EXISTS
大主子表之间进行 EXISTS 计算往往会导致较差的性能。这样的计算本质上是在做主键关联,如果能预先将主子表都按照主键有序存储,也可以使用有序归并算法有效提速。
esProc SPL 可以把主子表的 EXISTS 转化为有序归并,从而提升计算性能。
下面通过订单表和订单明细表的例子,介绍这种情况的外置提速方法。
先完成数据准备,可以直接使用第四篇生成的 CTX 文件。
例 5.1 对包含 7 号产品的订单,按照客户号分组统计订单个数。
select o.customer_id, count(o.order_id)
from orders o
where o.order_date >= '2024-12-31'
and o.order_date < '2025-01-01'
and exists
(select *
from details d
where d.order_id = o.order_id
and d.product_id=7)
group by o.customer_id;
执行时间 41 秒
SPL 代码 23:
A |
|
1 |
=now() |
2 |
=file("ordersQ4.ctx").open().cursor@m(order_id,customer_id;order_date>=date(2024,12,31)&&order_date<date(2025,01,01)) |
3 |
=file("detailsQ4.ctx").open().cursor(order_id;product_id==7;A2) |
4 |
=A3.group@1(order_id) |
5 |
=joinx(A2:o,order_id;A4:d,order_id) |
6 |
=A5.groups(o.customer_id;count(o.order_id)) |
7 |
>output("query cost:"/interval@ms(A1,now())/"ms") |
A4 把 7 号产品的订单明细,按照 order_id 有序分组,每组只保留第一个 order_id,这样不用生成分组子集,性能更好。
分组结果在 A5 中和订单表有序归并,再在 A5 中分组统计订单数量。
A3 中游标的最后一个参数是 A2,表示多线程并行时,details 表会跟随 orders 表分段,保证后面两个表有序归并的正确性。
执行时间:0.2 秒
例 5.2 产品号为 6 的订单明细,有多少条在订单表中找不到记录。
select count(d.order_id)
from details d
where d.product_id = 6
and not exists
(select *
from orders o
where o.order_id = d.order_id);
执行时间:16 秒
SPL 代码 24:
A |
|
1 |
=now() |
2 |
=file(“ordersQ4.ctx”).open().cursor@m(order_id) |
3 |
=file("detailsQ4.ctx").open().cursor(order_id;product_id==6;A2) |
4 |
=joinx@d(A3,order_id;A2,order_id) |
5 |
=A4.skip() |
6 |
>output("query cost:"/interval@ms(A1,now())/"ms") |
7 |
=now() |
A4 中 joinx 的选项 @d,表示用订单表过滤明细表,只保留在订单表中找不到的 order_id。
A5 对 A4 游标计数,就是想要的结果了。
执行时间:0.2 秒
例 5.3 找出明细不止一条的订单,要求订单不包含 9 号产品,按照日期分组统计订单数量。
select o.order_date,count(distinct o.order_id)
from orders o
where
exists (
select 1
from details d
where d.order_id = o.order_id
group by d.order_id
having count(*) > 1
)
and not exists (
select 1
from details d
where d.order_id = o.order_id and d.product_id = 9
)
MYSQL 跑了 5 分钟没出结果。
SPL 代码 25:
A |
|
1 |
=now() |
2 |
=file("ordersQ4.ctx").open().cursor@m(order_id,order_date) |
3 |
=file("detailsQ4.ctx").open().cursor(order_id,product_id;;A2) |
4 |
=A3.group(order_id) |
5 |
=A4.select(~.count(order_id)>1 && !~.pselect(product_id==9)) |
6 |
=joinx(A2:o,order_id;A5:d,order_id) |
=A6.groups(o.order_date;count(o.order_id)) |
|
=now() |
A3 把明细表按照 order_id 有序分组。
A4 循环过滤每一组,~ 表示当前组。只保留 order_ id 个数大于 1 且不包含 product_id 为 9 的组。
执行时间:0.9 秒
小结一下性能(单位 - 秒):
MYSQL |
SPL |
|
例 5.1 |
41 |
0.2 |
例 5.2 |
16 |
0.2 |
例 5.3 |
超过 5 分钟 |
0.9 |
请动手练习一下:
1、对包含 8 号产品的订单,按照日期分组统计订单个数。
2、思考:在自己熟悉的数据库中有没有大主子表关联计算 EXISTS?是否可以用有序归并方法提速?