场景
有两张表,一张活动清单表actv_evt:记录了所有的活动,包括活动id,活动名称及活动相关配置信息;一张客户活动参与表cust_actv,记录了客户参与活动信息。
cust_actv中参与了活动的客户定义为活跃客户,现在公司想对活跃客户做推广,将没参与过的活动推送给他们
举例:共有actv1, actv2, actv3, actv4,客户cust1参与了actv2, actv4,客户cust2参与了actv1,那么需要找出这样的目标数据:
cust1:actv1,actv3
cust2:actv2,actv3,actv4
原脚本
-- 5.按uid,通过collect_set将每个客户未参加的活动行程组合,如cust1,[actv1, actv3]
select
a.cust_uid
,concat_ws(',',collect_set(concat_ws(':',actv_id,actv_nm))) as actv
from (
-- 4.拿步骤3做出来的全客户全活动对应表与b表关联,关联不上的数据就是cust_uid未真实参加的活动,如cust1对应没关联上的记录为cust1,actv1;cust1,actv3
select
a.actv_id,a.actv_nm,a.cust_uid
from (
-- 3.每个活跃客户与每个活动做笛卡尔积,数据量会很大,目的是做出每个客户与每个活动对应的数据,如对客户1:cust1,actv1;cust1,actv2;cust1,actv3;cust1,actv4
select
a.actv_id,a.actv_nm,b.cust_uid
from (
-- 2.筛出所有活动清单
select distinct a.actv_id,a.actv_nm from tb_actv_evt
) as a
left join (
-- 1.筛选出活跃客户
select distinct cust_uid from tb_cust_actv
) as b
on 1=1 --笛卡尔积
) as a
left join tb_cust_actv as b
on a.actv_id=b=actv_id
and a.cust_uid=b.cust_uid
where b.actv_id is null
) as a
group by cust_uid
优化一:
可以看出原脚本第1、2步骤两个distinct,3笛卡尔积是消耗资源多的点。本题的笛卡尔积不可避免,但可以减少笛卡尔积的数据量。
-- 3.拿步骤2的所有活动与步骤1参与的活动做差集运算
select
b.cust_uid
,a.actv_all-b.actv_usd -- 这里指的是差集运算
from (
-- 1.筛选出活跃客户参与过的活动清单.如客户1:cust1,[actv2, actv4]
select
cust_uid
,collect_set(concat_ws(':',b.actv_id,b.actv_nm) as actv_usd
from tb_cust_actv as b
group by cust_uid
) as b
left join (
-- 2.筛出所有活动清单,即[actv1, actv2, actv3, actv4],这时候再笛卡尔积其实相当于b表中每条客户数据只需要与[actv1,actv2,actv3,actv4]这一条数据笛卡尔积
select
collect_set(concat_ws(':',a.actv_id,a.actv_nm) as actv_all
from tb_actv_evt
-- group by collect_set(concat_ws(':',a.actv_id,a.actv_nm) collect_set是类似sum,avg的UDAF聚合函数,这里不用group by
) as a
on 1=1 --笛卡尔积
优化二:
优化一的本质是减少笛卡尔积处理的数据量,但是因为笛卡尔积是单条处理的,类似orderby的全局问题,在活跃客户量比较大时,大量客户逐条join一条活动处理仍然很慢,优化程度仍然不够。
附:笛卡尔积常用场景
1.常用于给表的每条记录加上一些通用性质(都是单一取值),如本题中整体活动清单(对所有cust活动清单都是一样的,只有单一取值)就是通用的,也可以用于加一些汇总值,通用时间属性等之类。
eg:查看每个项目的营业收入,并且每条数据要附上总营收
select a.proj_name ,a.income ,b.all_income ,b.avg_income from tb_proj_income inner join ( -- 在这里添加通用属性。一个优化思路:如果上面的主select语句中有很多通用重复计算,也可以采用将其固定下来在inner join的子查询中 select sum(income) as all_income avg(income) as avg_income --注意这里sum、avg的取值都是单一的 from tb_proj_income ) as b --因为除聚合函数,其余字段要加group by, select proj_name,sum(income)实现不了
2.计算留存率等