本文已参与「新人创作礼」活动,一起开启掘金创作之路。
关于原生SQL语句的那些事儿
语句中各子语句的执行顺序
- from(也包括from中的子语句)
- join
- on
- where
- group by(这里开始使用select中的别名,后面的语句中都可以使用)
- avg,sum .... (聚合函数)
- having
- select
- distinct
- order by
- limit
执行顺序分析
所有的 查询语句都是从from开始执行的,在执行过程中,每个步骤都会为下一个步骤生成一个虚拟表,这个虚拟表将作为下一个执行步骤的输入。
from
form是一次查询语句的开端。
如果是一张表,会直接操作这张表; 如果这个from后面是一个子查询,会先执行子查询中的内容,子查询的结果也就是第一个虚拟表T1。(注意:子查询中的执行流程也是按照本篇文章讲的顺序哦)。 如果需要关联表,使用join
join
如果from后面是多张表,join关联,会首先对前两个表执行一个笛卡尔乘积,这时候就会生成第一个虚拟表T1(注意:这里会选择相对小的表作为基础表);
on
对虚表T1进行ON筛选,只有那些符合的行才会被记录在虚表T2中。(注意,这里的这里如果还有第三个表与之关联,会用T2与第三个表进行笛卡尔乘积生产T3表,继续重复3. on步骤生成T4表,不过下面的顺序讲解暂时不针对这里的T3和T4,只是从一个表关联查询T2继续说)
where
对虚拟表T2进行WHERE条件过滤。只有符合的记录才会被插入到虚拟表T3中。
group by
group by 子句将中的唯一的值组合成为一组,得到虚拟表T4。如果应用了group by,那么后面的所有步骤都只能操作T4的列或者是执行6.聚合函数(count、sum、avg等)。(注意:原因在于分组后最终的结果集中只包含每个组中的一行。谨记,不然这里会出现很多问题,下面的代码误区会特别说。)
avg,sum… 等聚合函数
聚合函数只是对分组的结果进行一些处理,拿到某些想要的聚合值,例如求和,统计数量等,并不生成虚拟表。
having
应用having筛选器,生成T5。HAVING子句主要和GROUP BY子句配合使用,having筛选器是第一个也是为唯一一个应用到已分组数据的筛选器。
select
执行select操作,选择指定的列,插入到虚拟表T6中。
distinct
对T6中的记录进行去重。移除相同的行,产生虚拟表T7.(注意:事实上如果应用了group by子句那么distinct是多余的,原因同样在于,分组的时候是将列中唯一的值分成一组,同时只为每一组返回一行记录,那么所以的记录都将是不相同的。 )
order by
应用order by子句。按照order_by_condition排序T7,此时返回的一个游标,而不是虚拟表。sql是基于集合的理论的,集合不会预先对他的行排序,它只是成员的逻辑集合,成员的顺序是无关紧要的。对表进行排序的查询可以返回一个对象,这个对象包含特定的物理顺序的逻辑组织。这个对象就叫游标。 oder by的几点说明
因为order by返回值是游标,那么使用order by 子句查询不能应用于表表达式。 order by排序是很需要成本的,除非你必须要排序,否则最好不要指定order by, order by的两个参数 asc(升序排列) desc(降序排列)
limit
取出指定行的记录,产生虚拟表T9, 并将结果返回。
limit后面的参数可以是 一个limit m ,也可以是limit m n,表示从第m条到第n条数据。
(注意:很多开发人员喜欢使用该语句来解决分页问题。对于小数据,使用LIMIT子句没有任何问题,当数据量非常大的时候,使用LIMIT n, m是非常低效的。因为LIMIT的机制是每次都是从头开始扫描,如果需要从第60万行开始,读取3条数据,就需要先扫描定位到60万行,然后再进行读取,而扫描的过程是一个非常低效的过程。所以,对于大数据处理时,是非常有必要在应用层建立一定的缓存机制)
书写SQL语句注意事项
书写规范上的注意
- 字符串类型的要加单引号
- select后面的每个字段要用逗号分隔,但是最后连着from的字段不要加逗号
- 使用子查询创建临时表的时候要使用别名,否则会报错。
增强性能
- 不要使select * from ……返回所有列,只检索需要的列,可避免后续因表结构变化导致的不必要的程序修改,还可降低额外消耗的资源
- 不要检索已知的列
select user_id,name from User where user_id = ‘10000050’ - 使用可参数化的搜索条件,如=, >, >=, <, <=,between, in, is null以及like ‘%’;尽量不要使用非参数化的负向查询,这将导致无法使用索引,如<>, !=, !>,!<, not in, not like,not exists, not between, is not null,like ‘%’
- 当需要验证是否有符合条件的记录时,使用exists,不要使用count(*),前者在第一个匹配记录处返回,后者需要遍历所有匹配记录
- Where子句中列的顺序与需使用的索引顺序保持一致,不是所有数据库的优化器都能对此顺序进行优化,保持良好编程习惯(索引相关)
- 不要在where子句中对字段进行运算或函数(索引相关)
- 如where amount / 2 > 100,即使amount字段有索引,也无法使用,改成where amount > 100 * 2就可使用amount列上的索引
- 如where substring( Lastname, 1, 1) = ‘F’就无法使用Lastname列上的索引,而where Lastname like ‘F%’或者where Lastname >= ‘F’ and Lastname < ‘G’就可以
- 在有min、max、distinct、order by、group by操作的列上建索引,避免额外的排序开销(索引相关)
- 小心使用or操作,and操作中任何一个子句可使用索引都会提高查询性能,但是or条件中任何一个不能使用索引,都将导致查询性能下降,如where member_no = 1 or provider_no = 1,在member_no或provider_no任何一个字段上没有索引,都将导致表扫描或聚簇索引扫描(索引相关)
- Between一般比in/or高效得多,如果能在between和in/or条件中选择,那么始终选择between条件,并用>=和<=条件组合替代between子句,因为不是所有数据库的优化器都能把between子句改写为>=和<=条件组合,如果不能改写将导致无法使用索引(索引相关)
- 调整join操作顺序以使性能最优,join操作是自顶向下的,尽量把结果集小的两个表关联放在前面,可提高性能。(join相关)