今天在做报表的时候遇到一个问题,想了很长时间没有解决,后来转换思路一下子就解决了。具体问题是这样的,我们公司有一张行业表,总共有四级行业需要维护,具体包括一级行业、二级行业、三级行业和四级行业,每个行业之间又存在包含关系,比如四级行业包含于三级行业,三级行业包含于二级行业,二级行业包含于一级行业,最诡异的地方就是我们把这么多信息放在一张表里维护,只不过额外加了两个字段以示区分,一个是行业等级,一个是父行业,具体的表结构如下:
行业ID | 行业等级 | 父行业ID |
二级行业 | 二级 | 一级行业 |
三级行业1 | 三级 | 二级行业 |
三级行业2 | 三级 | 二级行业 |
四级行业1 | 四级 | 三级行业1 |
四级行业2 | 四级 | 三级行业2 |
最后的需求是有另外一张表,是用四级行业划分的,其中有一项费用,最后需要按一级行业统计每个行业的费用。
一、模型
根据实际业务,为了说明这个问题,笔者在这里做了一个模型简化,假设我们只有两张表tb_cls和tb_cost,tb_cls包含行业id,行业等级cls,父行业p_id,所有行业(包括一级、二级、三级行业都保存在这张表里)都包含在内,具体创建出来的表如下(为了读者阅读方便,这里做了一个简化:id前面的第一位数代表一级行业编码,例如121表示属于一级大行业;整个id的位数代表几级行业,例如211总共三位表示三级行业):
另外一张表,我也做了简化,只提取其中用到的行业id和费用两个字段,具体的表内容如下:
二、问题
我们现在的任务有两个:
第一、建立三级行业跟一级行业一一对应关系;
第二、按一级行业统计费用。
三、思路
- 弯路:最开始的思路是嵌套,就是根据现实世界的逻辑关系一层一层建立联系,
SELECT * FROM tb WHERE id IN(SELECT * FROM tb WHERE)
,沿着这个思路尝试了很多,首先在SELECT外层声明的变量内层的嵌套识别不了,内外层建立的变量不能相互访问,另外一个这种建立起来的关系,没有一一对应关系,因为我们用的是IN,最终只要存在就可以,所以没有严格的一一对应关系。具体思路如下:
1.1 第1层:SELECT id FROM tb_cost
1.2 第2层:SELECT p_id FROM tb_cls WHERE id IN(SELECT id FROM tb_cost) AND cls=3
1.3 第3层:SELECT p_id FROM tb_cls WHERE id IN(SELECT p_id FROM tb_cls WHERE id IN(SELECT id FROM tb_cost) AND cls=3) AND cls=2
1.4 第4层(最终):SELECT t1.id,t2.id FROM tb_cls AS t1,tb_cost AS t2 WHERE t1.id IN(SELECT p_id FROM tb_cls WHERE id IN(SELECT p_id FROM tb_cls WHERE id IN(SELECT id FROM tb_cost) AND cls=3) AND cls=2)AND cls=1;
最终查询的结果如下: - 发现那里不对了没有,每个一级行业下面包含所有的三级行业,所以这种嵌套方式走不通,同时进一步深入下去研究发现嵌套内外层定义的变量是不能相互交互的,什么意思呢?
SELECT t1.id, var_1 FROM t1 WHERE p_id IN(SELECT id AS var_1 FROM t1)
var_1变量在内层那个SELECT是不可用的。 - 新思路:基于上面的弯路,笔者换了一个,假设我们有3张一模一样的表,通过这3张不同的表来区分各自的逻辑关系,把这3张表看成不同的表,一个个添加条件,具体思路如下:
2.1 第1层:tb_cls(AS t3)三级行业跟tb_cost(AS t4)建立关联:t3.id=t4.id AND t3.cls=3
2.2 第2层:tb_cls(AS t2)二级行业跟tb_cls(AS t3)建立关联:t3.p_id=t2.id AND t2.cls=2
2.3 第3层:tb_cls(AS t1)一级行业跟tb_cls(AS t2)建立关联:t2.p_id=t1.id AND t1.cls=1
最终,建立起来的三级行业对应一级行业的对应关系如下:SELECT t1.id,t4.id FROM tb_cls AS t1,tb_cls AS t2,tb_cls AS t3,tb_cost AS t4 WHERE t4.id=t3.id AND t3.p_id=t2.id AND t2.p_id=t1.id AND t3.cls=3 AND t2.cls=2 AND t1.cls=1;
查询结果如下,跟我们实际建立的情况一致,第一个任务(第一、建立三级行业跟一级行业一一对应关系)完成。 - 解决了第一个任务,第二个任务就简单多了,其实就是按照一级行业id加个GROUP BY,分一下组就可以,具体语句如下:
SELECT t1.id,SUM(t4.cost) FROM tb_cls AS t1,tb_cls AS t2,tb_cls AS t3,tb_cost AS t4 WHERE t4.id=t3.id AND t3.p_id=t2.id AND t2.p_id=t1.id AND t3.cls=3 AND t2.cls=2 AND t1.cls=1 GROUP BY t1.id;
查询结果如下,简单计算一下一级、二级、三级费用是不是查询出来的值,至此,任务二也圆满完成。 - 总结一下,当我们需要解决SQL语句的查询任务的时候,不要一味的选择深奥的技术、逻辑复杂的语言去解决(像笔者这里用多层嵌套,最后把自己绕进去了。)首先我们要做的是简化逻辑,能通过简单的思路解决复杂的问题本身也是一种能力,在这个基础上然后基于性能、需求、业务慢慢再继续优化SQL才是我们应该做的。