这个项目做的很累,主要参与做IS报表中正向批注指标。
工作量包括:
1. UI部分
IS报表页面中添加正向批注一列,正向批注明细的下钻页面
2. Java逻辑
Dao及 Service的实现
3. SQL
主要包括正向指标历史数据(存储过程生成的数据表 tst_in_postil)及实时数据(cust_in_postil)的查询SQL
由于需要统计正向批注的数量、正向批注明细的数量及展示正向批注的明细,故总共有三条SQL,最开始写出来的SQL效率很低,PM取消了实时查询的需求,故工作难点在于历史数据查询的SQL。
正向批注指标描述:
在查询时间范围内,商机有提升的客户跟进批注。如一条客户资料,在查询时间范围内,商机由0-20-0,则不算一条正向商机;若0-20-40-80-40,则算一条正向批注,且明细显示0-20、20-40、40-80的批注明细;
最开始的SQL思路:
1. 查询明细
I 遍历整表tst_in_postil,表按 add_time 做order by,然后按 cust_id, in_ucid做 group by, 这样找出查询时间范围内,对于一个cust,其In跟进的最早时间添加的商机 biz_before;
II 类似这样找出对于一个cust,其In跟进的最近时间添加的商机 biz_after;
III 找出所有满足正向批注的 cust_id, in_ucid;
IV 遍历整表,查询时间范围内,与上面的 cust_id, in_ucid相同的且商机有提升的明细;
对应SQL
SELECT DISTINCT temp1.cust_id, temp1.cust_name, temp1.in_realname, temp1.biz_before, temp1.biz_after, temp1.add_time
FROM tst_in_postil_daily temp1 INNER JOIN (
SELECT temp2.cust_id, temp2.in_ucid, temp2.biz_before, temp3.biz_after
FROM (SELECT cust_id, in_ucid, biz_before FROM (SELECT * FROM tst_in_postil_daily WHERE add_time > '2010-11-01' AND add_time < '2010-12-01'
ORDER BY add_time ASC) temp GROUP BY cust_id, in_ucid) temp2
INNER JOIN
(SELECT cust_id, in_ucid, biz_after FROM (SELECT * FROM tst_in_postil_daily WHERE add_time > '2010-11-01' AND add_time < '2010-12-01'
ORDER BY add_time DESC) temp GROUP BY cust_id, in_ucid) temp3
WHERE temp2.cust_id = temp3.cust_id AND temp2.in_ucid = temp3.in_ucid AND IFNULL(temp3.biz_after, 0) > IFNULL(temp2.biz_before, 0)) temp4
ON temp1.cust_id = temp4.cust_id AND temp1.in_ucid = temp4.in_ucid
WHERE temp1.add_time > '2010-11-01' AND temp1.add_time < '2010-12-01' AND IFNULL(temp1.biz_after, 0) > IFNULL(temp1.biz_before, 0)
2. 查询正向批注数
同上面思路,找到满足条件的所有正向批注的cust_id, in_ucid, count(*)
3. 查询正向批注明细数(用于分页)
同查询明细,只不过 select 的不是明细,而是 count(*)数数;
缺陷:
多次查询整表,整表几十W的数据量量,而且步骤3, 4需要做大表的Join查询,很费时间,查询的半年的数据(正向批注约6W行记录),需要跑二十多分钟,效率太低
优化1:
取消所有的 inner join 改用自然连接来做,因为还是要做大表的连接查询,还是很慢,没有多大的改善。
优化2:
I 合并步骤I,II, 因为做过一次 group by之后就可以统计出对应一个cust,其In添加批注的最早时间及最近时间;
II 据此时间查询出对应的商机,保存所有满足正向批注的cust_id, in_ucid;
III遍历整表,查询商机有提升的正向批注明细
缺陷:
其实效率低下的 join主要在于查询得到的满足 正向批注 的 cust_id,in_ucid集合和整表的Join,而这部分优化并没有改变这以现状,故实际效率仍然低下,虽有所提升,但没有太大改善。
对应的SQL
SELECT t1.cust_id, t1.cust_name, t1.in_employee_no, t1.in_realname, t1.biz_before, t1.biz_after, t1.postil, t1.add_time
FROM
(SELECT * FROM (
SELECT t3.*,
(SELECT t.biz_after FROM tst_in_postil_daily t WHERE t.cust_id = t3.cust_id AND t.in_ucid = t3.in_ucid AND t.add_time = t3.last1) AS last_biz,
(SELECT t.biz_before FROM tst_in_postil_daily t WHERE t.cust_id = t3.cust_id AND t.in_ucid = t3.in_ucid AND t.add_time = t3.first1) AS first_biz
FROM (
SELECT *, MAX(add_time) AS last1, MIN(add_time) AS first1 FROM tst_in_postil_daily
WHERE add_time >= '2010-11-01' AND add_time < '2010-12-01'
AND in_ucid IN (1968605, 1968615)
GROUP BY cust_id, in_ucid ) t3
) t2
WHERE IFNULL(t2.biz_after, '0%') > IFNULL(t2.biz_before, '0%') ) t5,
tst_in_postil_daily t1
WHERE t1.cust_id = t5.cust_id AND t1.in_ucid = t5.in_ucid
AND t1.add_time >= '2010-11-01' AND t1.add_time < '2010-12-01' AND IFNULL(t1.biz_after, '0%') > IFNULL(t1.biz_before, '0%');
优化3:
其实对整表扫描一遍后,所有的信息都已经具备,很多 Join 操作都不是必须的。该优化主要在于在一次 group by 后就拿到满足正向批注的 cust_id, in_ucid及其商机有替身的批注明细数;对于分页查询放到Java逻辑中去处理,不作大表的Join,这样统计正向批注数及明细数、明细展示都不需要做大表join了,大大提升了大数据量的查询速度;
统计正向批注数
SELECT count(*) as totalCount
FROM (
SELECT cust_id, in_ucid,
SUBSTRING_INDEX(GROUP_CONCAT(IFNULL(biz_after, '0%') ORDER BY in_postil_id), ',', -1) AS last_biz,
SUBSTRING_INDEX(GROUP_CONCAT(IFNULL(biz_before, '0%') ORDER BY in_postil_id), ',', 1) AS first_biz,
FROM tst_in_postil_daily
WHERE add_time>=? AND add_time<? AND in_ucid = ?
GROUP BY cust_id, in_ucid HAVING IFNULL(last_biz, '0%') > IFNULL(first_biz, '0%')
) t1
备注:
SUBSTRING_INDEX(GROUP_CONCAT(IFNULL(biz_after, '0%') ORDER BY in_postil_id), ',', -1)
其中GROUP_CONCAT函数用来把按 cust_id, in_ucid分组后的 biz_after 的数据以字符串的形式连接在一起,然后通过SUBSTRING_INDEX来截取分别表示最早时间及最近时间所对应的biz_before, biz_after,这样在Having条件下比较下,便可以一次性的分辨出正向批注的cust_id, in_ucid集合,直接统计对应的正向批注数。
统计正向批注明细数
SELECT SUM(t1.num) as totalCount
FROM (
SELECT cust_id, in_ucid,
SUBSTRING_INDEX(GROUP_CONCAT(IFNULL(biz_after, '0%') ORDER BY in_postil_id), ',', -1) AS last_biz,
SUBSTRING_INDEX(GROUP_CONCAT(IFNULL(biz_before, '0%') ORDER BY in_postil_id), ',', 1) AS first_biz,
SUM(IF(IFNULL(biz_after, '0%') > IFNULL(biz_before, '0%'), 1, 0)) num
FROM tst_in_postil_daily
WHERE add_time>=? AND add_time<? AND in_ucid = ?
GROUP BY cust_id, in_ucid HAVING IFNULL(last_biz, '0%') > IFNULL(first_biz, '0%')
) t1
备注:
SUM(IF(IFNULL(biz_after, '0%') > IFNULL(biz_before, '0%'), 1, 0)) num
主要用来在分辨出正向批注对应的cust_id, in_ucid后,然后将其跟进明细中,对应商机有提升的记录数统计出来,这样最后统计所有正向批注明细数时,只要对num 求和便可以
展示正向批注明细
SELECT cust_id, in_ucid,
SUBSTRING_INDEX(GROUP_CONCAT(IFNULL(biz_after, '0%') ORDER BY in_postil_id), ',', -1) AS last_biz,
SUBSTRING_INDEX(GROUP_CONCAT(IFNULL(biz_before, '0%') ORDER BY in_postil_id), ',', 1) AS first_biz,
SUM(IF(IFNULL(biz_after, '0%') > IFNULL(biz_before, '0%'), 1, 0)) num
FROM tst_in_postil_daily
WHERE add_time>=? AND add_time<? AND in_ucid = ?
GROUP BY cust_id, in_ucid HAVING IFNULL(last_biz, '0%') > IFNULL(first_biz, '0%')
生成的满足正向批注要求的一个临时表,里面有详细的各个批注明细中商机有提升的明细个数,这样在Java逻辑中,根据查询指定的页面及统计出来的正向批注明细总数,确定该Page的起始行与终止行,然后通过临时表中的num可以确定该Page记录,应该对应哪些cust_id, in_ucid的集合,再去tst_in_postil_daily 表中将该Page的明细取出。