咱们每个人都想要优化SQL语句,以便可以提高性能,可是,若是不了解其机制,可能就会事倍功半。我以一个简单的例子 ,来说解SQL的部分机制。java
今天在公司工做时,面临这样一个需求:mysql
根据条件查询项目的预算金额。sql
查询要求:数据库
数据库有这样的两张表,一张是项目表project。项目表有些字段不便展现,于是,只作部分截图:编程
一张是项目人员表,这张表记录的是某个项目涉及哪些类型的人员,人员类型(枚举)以下表所示:编程语言
key值 | value值 |
---|---|
PERSON_TYPE_SALESMAN | 业务员 |
PERSON_TYPE_SALESMAN_MANAGER | 业务部经理 |
PERSON_TYPE_DESIGNER | 设计师 |
PERSON_TYPE_DESIGNER_MANAGER | 设计部经理 |
PERSON_TYPE_PROJECT_SUPERVISION | 工程监理 |
PERSON_TYPE_ENGINEERING_MANAGER | 工程部经理 |
于是,数据表项目人员(project_person)的的设计为:函数
SELECT SUM(budgetary_amount) FROM zq_project WHERE is_deleted = 0 AND id=167
输出结果为 10性能
SELECT SUM(zp.budgetary_amount) FROM zq_project zp LEFT JOIN zq_project_person zpp ON(zpp.is_deleted = 0 AND zpp.project_id = zp.id) WHERE zp.is_deleted = 0 AND zp.id=167
输出结果为 60测试
为何会出现上诉状况,当咱们在作一对多的sum求和时,就出现了笛卡尔积的现象。咱们查找出项目人员表中的项目编号为167的有多少条记录优化
SELECT * from zq_project_person zpp WHERE zpp.is_deleted = 0 and zpp.project_id = 167
输出结果如图所示:
由上图可知,一共有六条记录,也就是说,项目表中编号为167的这条记录对应着项目人员表中的6条记录,sum以后要计算6次,才变成60,好比下面的代码:
SELECT zp.id AS projectId, zp.budgetary_amount, zpp.id AS personId FROM zq_project zp LEFT JOIN zq_project_person zpp ON(zpp.is_deleted = 0 AND zpp.project_id = zp.id) WHERE zp.is_deleted = 0 AND zp.id=167;
输出结果如图所示:
这就涉及到mysql的执行前后的顺序形成笛卡尔积的紊乱
在讲解mysql执行的前后顺序以前,咱们了解一下left join的 on 和 where的区别。
on中的是副表的条件,where会将left join转化为inner join格式的数据,这是过滤数据用的。
假设有这两张表,一张是商品表(goods表),一张是商品分类表(goods_category),商品表的外键是商品分类表的主键。咱们来作left join的测试
查找语句为:
SELECT * FROM cce_goods cg LEFT JOIN cce_goods_category cgc ON(cgc.is_deleted = 0 AND cgc.id = cg.goods_category_id) WHERE cg.is_deleted = 0
查找结果如图所示:
你会发现,编号为1的商品分类的字段属性is_deleted的值明明是 1 ,而on以后的is_deleted 的值为 0 ,这应该是筛选不出来了,但仍是能筛选出来呢?这里就涉及到on的条件了。
针对第一种状况
你会发现,这是商品分类的字段属性是有值的,由于,副表的条件知足了,能拿到副表中的字段属性值。
若是咱们把left join 改为inner join ,而cgc.is_deleted = 0 不变,这又不同了,如代码所示:
SELECT * FROM cce_goods cg INNER JOIN cce_goods_category cgc ON(cgc.is_deleted = 0 AND cgc.id = cg.goods_category_id) WHERE cg.is_deleted = 0
这样,上面的两条数据也没了,由于,inner join 是主表和副表的交集,主表和副表的条件是平行条件,具备一样的权重,也就是说同时知足主副表的条件,才能出现数据。
再假如,咱们cgc.is_deleted = 0放到外面,如代码所示:
SELECT * FROM cce_goods cg INNER JOIN cce_goods_category cgc ON(cgc.id = cg.goods_category_id) WHERE cg.is_deleted = 0 AND cgc.is_deleted = 0
这样,也就把left join 隐性成了 inner join了,主表和副表的条件也是平行条件,具备一样的权重。
二、 咱们来执行如下的查询语句,如代码所示:
SELECT zp.id AS projectId, zp.budgetary_amount, zpp.id AS personId FROM zq_project zp LEFT JOIN zq_project_person zpp ON(zpp.is_deleted = 0 AND zpp.project_id = zp.id) WHERE zp.is_deleted = 0 AND zp.id=167;
目前只有三条记录,其余的五条记录没有展现,这是为何呢?这个只能意会,没法言传。就好比java中的对象,类Project对象是类ProjectPerson的成员属性,咱们能在ProjectPerson对象里填充Project对象,但没法在Project对象中填充ProjectPerson的对象是同样的道理。
上面也提到了mysql执行的前后顺序了,在下面,详细介绍mysql执行的前后顺序。
mysql在执行的过程会有必定的前后顺序的,它是按照什么顺序来的呢?
任何一种开发语言,不论是面向结构的c语言,仍是面向对象的JAVA语言,或者,结构化查询语言sql,其都有一个入口,C语言是main,java是public static void main(String[] args){...},SQL语言好比mysql,其入口是From,而后根据各个优先级。依次往下进行。
以项目表为主表,以项目人员表和项目进程表为副表,查找出项目名和项目的预算金额
SELECT DISTINCT zp.id AS projectId, SUM(zp.budgetary_amount) AS totalBugAmo, zp.`name` AS projectName FROM zq_project zp LEFT JOIN zq_project_person zper ON ( zper.is_deleted = 0 AND zper.project_id = zp.id ) LEFT JOIN zq_project_process zpro ON ( zpro.is_deleted = 0 AND zpro.project_id = zp.id ) WHERE zp.is_deleted = 0 GROUP BY zp.id HAVING totalBugAmo <= 12000 ORDER BY totalBugAmo DESC
执行结果如图所示:
执行顺序如图所示
在讲解这个问题前,咱们先看这张图:
咱们的查语句是:
SELECT zp.id AS projectId, zp.budgetary_amount AS bugAmo, zp.`name` AS projectName FROM zq_project zp LEFT JOIN zq_project_person zper ON ( zper.is_deleted = 0 AND zper.project_id = zp.id ) LEFT JOIN zq_project_process zpro ON ( zpro.is_deleted = 0 AND zpro.project_id = zp.id ) WHERE zp.is_deleted = 0 AND zp.id=167
查询结果的截图为:
你会发现,数据多了,为何会多?以项目编号为167的为研究点,此时,当left join项目人员表时,根据排列组合而来,$C(1,1)*C(2,1)$=2,多生成一张有两条记录的虚拟表。此时,再left join项目进程表时,根据排列组合而来,$2* C(3,1)$=6,就会出现,这时就会出现6条数据的虚拟表,这时,咱们再sum的话,就会计算6次,从而得出项目编号为167的预算金额是60,而不是10。
上面就出现了分组以后的项目编号为167的预算金额为90的了,一对多的关系若是sum,是会出现笛卡尔积的错误的。
由于,咱们须要使用disdict去重,因而,咱们重写代码后为:
SELECT vt1.projectId, SUM(vt1.bugAmo), vt1.projectName FROM ( SELECT DISTINCT zp.id AS projectId, zp.budgetary_amount AS bugAmo, zp.`name` AS projectName FROM zq_project zp LEFT JOIN zq_project_person zper ON ( zper.is_deleted = 0 AND zper.project_id = zp.id ) LEFT JOIN zq_project_process zpro ON ( zpro.is_deleted = 0 AND zpro.project_id = zp.id ) WHERE zp.is_deleted = 0 AND zp.id = 167 ) AS vt1
此时,将其去重后的数据做为虚拟表,放置在from里面,咱们拿到的数据就是正确的,如图所示:
若是,咱们想要查找所有项目的统计金额,也能够重写代码。
重写代码的思想:咱们先将查询结果去重,获得去重后的虚拟表;再过滤虚拟表的数据,从虚拟表中统计数据,因而乎获得:
SELECT SUM(vt1.bugAmo) AS toalBugAmo FROM ( SELECT DISTINCT zp.id AS projectId, zp.budgetary_amount AS bugAmo, zp.`name` AS projectName FROM zq_project zp LEFT JOIN zq_project_person zper ON ( zper.is_deleted = 0 AND zper.project_id = zp.id ) LEFT JOIN zq_project_process zpro ON ( zpro.is_deleted = 0 AND zpro.project_id = zp.id ) WHERE zp.is_deleted = 0 ) AS vt1 GROUP BY vt1.projectId HAVING toalBugAmo <= 12000 ORDER BY toalBugAmo DESC
这个执行结果为:
任何一门语言,只要掌握住了,它的机制是怎么运行的,你也就学会了如何优化,提高该语言的性能等。只要你真正掌握住了一门变成语言,你掌握其余的编程语言,学起来就很是地快。