多表联查行数异常排查及获取min(TIME_DAY_KEY)的SQL咨询
关联多表后行数暴增的原因及解决方案
嘿,先别急着怪那个月份转换语句——它其实没惹祸!咱们来拆解下问题根源,以及怎么在保持4000行的前提下拿到最早消费日期:
为什么行数会暴增?
核心问题出在postpaid_summary表的关联方式和GROUP BY的字段,和月份处理语句完全无关:
- 你用了
JOIN postpaid_summary p(内连接),而postpaid_summary里同一个MSISDN(对应ACTIVATIONS的act_phone_no)大概率有多条消费记录(每个TIME_DAY_KEY对应一条)。这就导致前面三张表的每一行,都会和这个号码对应的所有消费记录做笛卡尔积,直接把行数撑起来了。 - 更关键的是你的GROUP BY里还包含了
p.TIME_DAY_KEY——这意味着每个不同的TIME_DAY_KEY都会单独成组,完全达不到你想要的“按主表行取最早消费日期”的目的,反而会把所有匹配的消费记录都保留下来,自然行数就爆炸到10万+了。
怎么解决?保持4000行同时获取min(TIME_DAY_KEY)
要先对postpaid_summary做预聚合,提前按MSISDN算出最早的TIME_DAY_KEY,再把这个聚合后的结果和前面的表关联,这样就不会产生行膨胀了。
修改后的SQL如下:
SELECT a.act_actdevice, a.act_phone_no, a.bi_account_id, a.packag_start_date, TO_CHAR(TRUNC(a.packag_start_date, 'MONTH'), 'MON-YYYY') AS PACKAG_START_DATE_MONTHYEAR, a.retailer_name, a.retailer_type, a.dms_id as "DSR/BPR_ID", a.dsr_name as "DSR/BPR_NAME", a.agent_type, a.distributor_id, a.distributor_name, a.SALES_DISTRICT, a.profileid, s.district, s.province, c.identification_number, c.account_type, c.account_status, c.activation_date, c.permanent_disconnection_date, c.temporary_disconnection_date, c.status_change_date, c.credit_limit, c.average_monthly_bill_amount, c.primary_packag_start__date, c.package_code, c.sales_channel, c.site_id, c.district_name, c.usage_arpu, c.bill_to_contact_name, p.first_consumption_date FROM ACTIVATIONS a LEFT JOIN customer c ON TO_CHAR(a.act_phone_no) = c.msisdn_voice LEFT JOIN agent_dtl s ON a.dms_id = s.agent_id -- 关键改动:预聚合postpaid_summary,每个号码只取最早消费日期 LEFT JOIN ( SELECT MSISDN, MIN(TIME_DAY_KEY) AS first_consumption_date FROM postpaid_summary GROUP BY MSISDN ) p ON a.act_phone_no = p.MSISDN WHERE a.packag_start_date BETWEEN TO_DATE('2020-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS') AND TO_DATE('2020-05-31 23:59:59', 'YYYY-MM-DD HH24:MI:SS') -- 注意:GROUP BY不再包含p的字段,因为已经提前聚合完成 GROUP BY a.act_actdevice, a.act_phone_no, a.bi_account_id, a.packag_start_date, TO_CHAR(TRUNC(a.packag_start_date, 'MONTH'), 'MON-YYYY'), a.retailer_name, a.retailer_type, a.dms_id, a.dsr_name, a.agent_type, a.distributor_id, a.distributor_name, a.SALES_DISTRICT, a.profileid, s.district, s.province, c.identification_number, c.account_type, c.account_status, c.activation_date, c.permanent_disconnection_date, c.temporary_disconnection_date, c.status_change_date, c.credit_limit, c.average_monthly_bill_amount, c.primary_packag_start__date, c.package_code, c.sales_channel, c.site_id, c.district_name, c.usage_arpu, c.bill_to_contact_name
几个关键说明:
- 这里用了
LEFT JOIN替代原来的JOIN,避免因为某些号码在postpaid_summary里没有消费记录而被过滤掉(如果你确实只想保留有消费记录的号码,改回JOIN即可)。 - 预聚合子查询确保每个
MSISDN只返回一行最早的消费日期,关联后不会增加主表的行数,就能保持原来的4000行左右。 - 原来的月份转换语句
TO_CHAR(TRUNC(a.packag_start_date, 'MONTH'), 'MON-YYYY')完全没问题,它只是把日期转成了月份格式,不会导致行数变化。
内容的提问来源于stack exchange,提问作者Shulaz Shan




