SQL常见雷区,函数,优化,执行顺序,使用技巧汇总
SQL常见雷区,函数,优化,执行顺序,使用技巧汇总
一、sql使用技巧
1、去重统计
select count(distinct 去重字段) from 表
2、字段别名和 group by
踩这个坑还是很难受的,亏得公司的学长看出来了,希望下次不要再入坑了。
sql1:
select d.cellid,d.num from(
select z.cellid,avg(z.rsrp) as num
from data_zlwc z
where z.cellid!=''
group by z.cellid)
d where d.num=-60 ;
sql2:
select cellid,avg (rsrp) as rsrp
from yrh.data_zlwc
where cellid!=''
and rsrp =-60
group by cell
说一下具体的区别和注意事项:
1、sal1查的是avg(rsrp)=-60,sql2查的是rsrp=-60;
2、如果需要给字段取别名,不要将新取的别名和原有的字段名重复,
这样会导致所查询的字段是在结果上做的筛选,而不是在原数据上的筛选;
3、group by的语法,不能对结果进行筛选,
group by子句必须在where子句之后,order by子句之前。
Tips:
where 和 having:
1、聚合函数:例如SUM, COUNT, MAX, AVG等。这些函数和其它函数的根本区别就是它们一般作用在多条记录上;
2、having是分组(group by)后的筛选条件,分组后的数据组内再筛选;
3、where子句中不能使用聚集函数,而having子句中可以,所以在集合函数中加上了hiving来起到测试查询结果是否符合条件的作用。即having子句的适用场景是可以使用聚合函数;
4、having 子句限制的是组,而不是行。having 子句中的每一个元素也必须出现在select列表中。有些数据库例外,如oracle。
3、join
与join相关的查询后会出现2个相同名的字段,这时候需要在查询的时候指定具体表名。
--left join 中on/and 对左表中字段做条件限制无意义,左表中所有数据依旧会被查出来,左表中的字段筛选必须在where中增加条件;对右表中字段做条件限制会对右表数据先做筛选再连接,结果不显示;
--join 中on/and 对两边表中的字段做限制都会起作用。
select *
from sc s
left join student t on s.sid = t.sid
where s.cid = (select cid from teacher t
left join course c on t.tid = c.tid where t.tname = '张三');
4、行列转换
case xox when ooxx then xoxo end xxoo
以前在scala中用到过,但是单纯的SQL中还是第一次遇见,记录入册。
示例1:
-- 按平均成绩从高到低显示所有学生的所有课程的成绩以及平均成绩 (case when 不影响其他聚合函数的使用)
select *
from (select SId as "学号",
sum(case CId when '01' then score end) as "课程1",
sum(case CId when '02' then score end) as "课程2",
sum(case CId when '03' then score end) as "课程3",
avg(score) avgSorce
from sc
group by SId
order by avgSorce desc )a;
示例2:
-- 统计各科成绩各分数段人数:课程编号,课程名称,[100-85],[85-70],[70-60],[60-0]
select *
from(select CId,
sum(case when score >= 85 then 1 else 0 end) "100-85",
sum(case when score >= 70 and score < 85 then 1 else 0 end) "70-80",
sum(case when score >= 60 and score < 70 then 1 else 0 end) "60-70",
sum(case when score < 60 then 1 else 0 end) "60-0"
from sc
group by CId)a;
5、四大排序函数–RANK
涉及到Sql 四大排名函数(ROW_NUMBER、RANK、DENSE_RANK、NTILE)简介,NTILE比较复杂URL地址链接
https://www.cnblogs.com/shizhijie/p/9366247.html
四大排名函数相关拓展:
RANK() 跳跃排序,排序相同时会重复,重复时保留名次空缺,总数不会变
假设4位数,1,2两个数据相同,则 1134
DENSE_RANK() 连续排序,排序相同时会重复,总分重复时不保留名次空缺,总数会减少
假设4位数,1,2两个数据相同,则 1123
ROW_NUMBER() 会根据顺序计算,多用于web分页
NTILE() 可以对序号进行分组处理,将有序分区中的行分发到指定数目的组中,简称分桶
1、row_number 的用途的非常广泛,排序最好用他,一般可以用来实现web程序的分页,他会为查询出来的每一行记录生成一个序号,依次排序且不会重复,注意使用row_number函数时必须要用over子句选择对某一列进行排序才能生成序号。
select ROW_NUMBER() OVER(order by [SubTime] desc) as row_num,* from [Order]
2、rank 函数用于分组排序,是跳跃排序,即:如果有两个第1名,则接下来就是第3第4,忽略第2的情况。
1134
select RANK() OVER(order by [UserId]) as rank,* from [Order]
3、dense_rank 函数的功能与rank函数类似,dense_rank函数在生成序号时是连续的,而rank函数生成的序号有可能不连续。
112223
select DENSE_RANK() OVER(order by [UserId]) as den_rank,* from [Order]
Tips:
dense_rank函数出现相同排名时,将不跳过相同排名号,rank值紧接上一次的rank值。在各个分组内,rank()是跳跃排序,有两个第一名时接下来就是第四名,dense_rank()是连续排序,有两个第一名时仍然跟着第二名。
4、ntile 函数可以对序号进行分组处理,将有序分区中的行分发到指定数目的组中。 各个组有编号,编号从一开始。 对于每一个行,ntile 将返回此行所属的组的编号。这就相当于将查询出来的记录集放到指定长度的数组中,每一个数组元素存放一定数量的记录。ntile函数为每条记录生成的序号就是这条记录所有的数组元素的索引(从1开始)。也可以将每一个分配记录的数组元素称为“桶”。ntile函数有一个参数,用来指定桶数。下面的SQL语句使用ntile函数对Order表进行了装桶处理:
select NTILE(4) OVER(order by [SubTime] desc) as ntile,* from [Order]
6、partition by、group by、order by
partition by 用于给结果集分组,如果没有指定那么它把整个结果集作为一个分组。
partition by 是分析性函数的一部分,它能返回一个分组中的多条记录;
partition by 返回的是分组里的每一条数据,并且可以对分组数据进行排序操作;
group by 只能返回聚合之后的组的数据统计值的记录;
group by 聚合函数,一般只有一条反映统计值的记录。
group by 根据指定的规则对数据进行分组,所谓分组就是将一个'数据集'划分成若干个'小区域',然后针对若干个“小区域”进行数据处理
group by by后可以是一个或多个表字段来达到按多个条件分组的目的
group by 必须与聚合函数一起使用,例如count,sum,max,min,avg,(first,last)
order by 对数据库中的一组数据进行排序,可以升序降序排列数据
partition by xxx order by xxxx 一般是这样使用
HiveSQL:
Order By --- 对于查询结果做全排序,只允许有一个reduce处理
当数据量较大时,应慎用。严格模式下,必须结合limit来使用
Sort By --- 对于单个reduce的数据进行排序
Distribute By --- 分区排序,经常和Sort By结合使用
Cluster By --- 相当于 Sort By + Distribute By
Cluster By不能通过asc、desc的方式指定排序规则,可通过 distribute by column sort by column asc|desc 的方式)
案例:
1、查找同名同姓---group by
select Sname,Ssex,count(*) num from student group by Sname,Ssex;
7、having 和 where
本质的区别就是:
where 筛选的是数据库表里面本来就有的字段,
having 筛选的字段是从前面筛选的字段中筛选出来的。
8、条件子查询–on || where || hiving
on > where > hiving
(非oracle 中)on、where、having 这三个都可以加条件的子句中,on 是最先执行,where 次之,having 最后,因为on 是先把不符合条件的记录过滤后才进行统计,它就可以减少中间运算要处理的数据,按理说应该速度是最快的,
where 也应该比having 快点的,因为它过滤数据后才进行sum,在两个表联接时才用on 的,所以在一个表的时候,就剩下where 跟having比较了。
在这单表查询统计的情况下,如果要过滤的条件没有涉及到要计算字段,那它们的结果是一样的,只是where 可以使用rushmore 技术,而having 就不能,在速度上后者要慢。
如果要涉及到计算的字段,就表示在没计算之前,这个字段的值是不确定的,where 的作用时间是在计算之前就完成的,而having 就是在计算后才起作用的,所以在这种情况下,两者的结果会不同。
在多表联接查询时,on 比where 更早起作用。系统首先根据各个表之间的联接条件,把多个表合成一个临时表后,再由where 进行过滤,然后再计算,计算完后再由having 进行过滤。
由此可见,要想过滤条件起到正确的作用,首先要明白这个条件应该在什么时候起作用,然后再决定放在那里。
9、查询所在限制时间内数据
extract函数,to_date函数
-- 查询 1990 年出生的学生名单 (extract 用于提取时间的部分,如:年、月、日、时、分、秒)
# 方法1:
select * from student where extract(year from sage)=1990;
# 方法2:
select *
from student
where sage >= to_date('1990-01-01', 'yyyy-mm-dd')
and sage < to_date('1991-01-01', 'yyyy-mm-dd');
二、常见的sql优化
1、in|| not in|| exists|| not exists
子查询表小的 用 in
子查询表大的 用 exists
能用exists 就别用 in,慎用in 和 not in
a)使用技巧
in语句:只执行一次
1、确定给定的值是否与子查询或列表中的值相匹配。
2、in在查询的时候,首先查询子查询的表,然后将内表和外表做一个笛卡尔积,然后按照条件进行筛选。所以相对内表比较小的时候,in的速度较快。
eg:
select * from student s where s.stuid in(select stuid from score ss where ss.stuid = s.stuid)
exists语句:执行student.length次
1、如果成立则返回true不成立则返回false。
如果返回的是true的话,则该行结果保留;
如果返回的是false的话,则删除该行,最后将得到的结果返回。
2、exists在查询的时候,首先查主表(student),再查子查询表的内容(score),根据where条件的每一条记录依次判断条件是否成立。
eg:
select * from student s where EXISTS(select stuid from score ss where ss.stuid = s.stuid)
in 和 exists的区别:
1、如果子查询得出的结果集记录较少,主查询中的表较大且又有索引时应该用in;
如果外层的主查询记录较少,子查询中的表大,又有索引时使用exists。
2、其实我们区分in和exists主要是造成了驱动顺序的改变(这是性能变化的关键),
如果是exists,那么以外层表为驱动表,先被访问;
如果是IN,那么先执行子查询,所以我们会以驱动表的快速返回为目标,那么就会考虑到索引及结果集的关系了 ,另外IN时不对NULL进行处理。
3、in是把外表和内表作hash连接,
exists是对外表作loop循环,每次loop循环再对内表进行查询。
4、一直以来认为exists比in效率高的说法是不准确的。
not in 和 not exists的区别
如果查询语句使用了not in 那么内外表都进行全表扫描,没有用到索引;
而not extsts 的子查询依然能用到表上的索引。
所以无论那个表大,用not exists都比not in要快。
b)原理解析— in || extsts
in
▼▼
B表比A表小用in
select * from A where id in(select id from B)
以上查询使用了in语句,in()只执行一次,它查出B表中的所有id字段并缓存起来.之后,检查A表的id是否与B表中的id相等,如果相等则将A表的记录加入结果集中,直到遍历完A表的所有记录.
它的查询过程类似于以下过程:
List resultSet=[];
Array A=(select * from A);
Array B=(select id from B);
for(int i=0;i<A.length;i++) {
for(int j=0;j<B.length;j++) {
if(A[i].id==B[j].id) {
resultSet.add(A[i]);
break;
}
}
}
return resultSet;
可以看出,当B表数据较大时不适合使用in(),因为它会B表数据全部遍历一次.
如:A表有10000条记录,B表有1000000条记录,那么最多有可能遍历10000*1000000次,效率很差.
再如:A表有10000条记录,B表有100条记录,那么最多有可能遍历10000*100次,遍历次数大大减少,效率大大提升.
结论:in()适合B表比A表数据小的情况
exists
▼▼
exists()适合B表比A表数据大的情况
select a.* from A a where exists(select 1 from B b where a.id=b.id)
以上查询使用了exists语句,exists()会执行A.length次,它并不缓存exists()结果集,因为exists()结果集的内容并不重要,重要的是结果集中是否有记录,如果有则返回true,没有则返回false.
它的查询过程类似于以下过程:
List resultSet=[];
Array A=(select * from A)
for(int i=0;i<A.length;i++) {
if(exists(A[i].id) { //执行select 1 from B b where b.id=a.id是否有记录返回
resultSet.add(A[i]);
}
}
return resultSet;
当B表比A表数据大时适合使用exists(),因为它没有那么遍历操作,只需要再执行一次查询就行.
如:A表有10000条记录,B表有1000000条记录,那么exists()会执行10000次去判断A表中的id是否与B表中的id相等.
如:A表有10000条记录,B表有100000000条记录,那么exists()还是执行10000次,因为它只执行A.length次,可见B表数据越多,越适合exists()发挥效果.
再如:A表有10000条记录,B表有100条记录,那么exists()还是执行10000次,还不如使用in()遍历10000*100次,因为in()是在内存里遍历比较,而exists()需要查询数据库,我们都知道查询数据库所消耗的性能更高,而内存比较很快.
结论:exists()适合B表比A表数据大的情况
2、union|| union all
没有重复的前提下:union all > union
UNION 因为会将各查询子集的记录做比较,故比起UNION ALL 通常速度都会慢上许多。
确认没有重复数据或者不用剔除重复数据时,union all性能更好,解释器不用再扫描一遍表挑选重复
SELECT E_Name FROM Employees_China
UNION ALL
SELECT E_Name FROM Employees_USA
3、建表注意事项
a)数字类型 优先于 字符串
建表的时候能使用数字类型字段就不要使用字符串,数字类型的字段作为查询条件比字符串快。
b)varchar 优先于 char
定义一个char[10]和varchar[10],如果存进去的是‘abcd’,那么char所占的长度依然为10,除了字符‘abcd’外,后面跟六个空格,而varchar就立马把长度变为4了,取数据的时候,char类型的要用trim()去掉多余的空格,而varchar是不需要的。char的存取数度还是要比varchar要快得多,因为其长度固定,方便程序的存储与查找;
4、where
a)子句末尾最好是大数量数据
能过滤掉最大数量记录的条件必须写在where子句的最末尾。
b)避免在where子句中使用 != < >
尽量避免在where子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行全表扫描。
c)避免对where子句中的字段进项表达式操作
应尽量避免在 where 子句中对字段进行表达式操作,否则引擎将放弃使用索引而进行全表扫描。
select id from t where num/2= 100
应改为:
select id from t where num = 100*2
d)避免对where子句中的字段函数进行操作
应避免对where子句中的字段函数进行操作,否则引擎将放弃使用索引而进行全表扫描。
/*name以abc开头的id*/
select id from t where substring(name,1,3)='abc';
应改为:
select id from t where name like 'abc%';
e)不要在where子句中左边的 “=” 来进行运算
不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
f)where in 和 where =
/*查询姓名叫张三的学生的信息*/
select * from student where Sname='张三';
/*查询张三和李四的学生的信息*/
select * from student where Sname in('张三','李四');
/*查询学号是001的学生信息*/
select * from student where Sname in(select Sname from sn where Sno='001');
g)避免在where子句中使用or来连接条件
应尽量避免在 where 子句中使用 or 来连接条件,否则引擎将放弃使用索引而进行全表扫描。
select id from t where num=10 or num=20
可以这样查:
select id from t where num=10
union all
select id from t where num=20
h)where的 in 和 between
对连续数据的查询,between > in
select id from t where num between 1 and 3
5、查询字段多时,需慎用distinct
查询字段很多的情况下,慎用distinct关键字,会大大降低查询效率。
6、优化嵌套查询–join
某些子查询可以通过join来代替,因为join不需要在内存中创建一个临时表来存储数据。
7、慎用左右双重模糊查询
慎用左右模糊查询,使用 like 两边加 “%” 会造成索引失效,而进行全表扫描;
左边的 “%”少一个,这个索引不会失效。
select id from t where name like '%abc%';
8、避免临时表的频繁创建和删除
避免频繁创建和删除临时表,以减少系统表资源的消耗
9、临时表插入大量数据时,用select into
在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;
如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
10、存储最后上删除临时表显式
如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除;
先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
11、避免有空判断的语句
避免有空判断的语句。因为空判断将导致全表扫描,而不是索引扫描。对于空判断这种情况,可以考虑对这个列创建数据库默认值
/*空判断将导致全表扫描*/
select id from student where description is null;
可以改为:
/*可以考虑在需要经常null判断的列,增加默认值*/
select id from student where description='';
12、避免使用不等于条件
会导致全表扫描,对于不等于这种情况,考虑改为范围查询解决。
13、避免使用or条件
会导致全表扫描,对于or这种情况,可以改为 分别查询,然后 union all。
select id from student where id=3 or id=4;
可以改为:
select id from student where id=3
union all
select id from student where id=4;
三、sql语句执行顺序
1、书写顺序和执行顺序
书写顺序:
select–from–where–group by–having–order by
其中select和from是必须的,其他关键词是可选的
执行顺序:
from–where–group by–having–select–order by
from:需要从哪个数据表检索数据
where:过滤表中数据的条件
group by:如何将上面过滤出的数据分组
having:对上面已经分组的数据进行过滤的条件
select:查看结果集中的哪个列,或列的计算结果
order by :按照什么样的顺序来查看返回的数据
2、from和where解析顺序
from后面的表关联,是自右向左解析的,而where条件的解析顺序是自下而上的。
也就是说,写SQL时,尽量把数据量大的表放在最右边来进行关联,而把能筛选出大量数据的条件放在where语句的最下面。
3、连接查询的执行顺序 — on || where
SQL语句中join连表时on和where后都可以跟条件,那么对查询结果集,执行顺序,效率是如何呢?区别:
on是对中间结果进行筛选,where是对最终结果筛选。
执行顺序:
先进行on的过滤, 而后才进行join。
效率:
如果是inner join,放on和放where产生的结果一样,但没说哪个效率速度更高;
如果有outer join (left or right),就有区别了,因为on生效在先,已经提前过滤了一部分数据,而where生效在后。
4、select语句的执行顺序和执行步骤
SQL Select语句完整的执行顺序【从DBMS使用者角度】:
1、from子句组装来自不同数据源的数据;
2、where子句基于指定的条件对记录行进行筛选;
3、group by子句将数据划分为多个分组;
4、使用聚集函数进行计算;
5、使用having子句筛选分组;
6、计算所有的表达式;
7、使用order by对结果集进行排序。
SQL Select语句的执行步骤【从DBMS实现者角度,这个对我们用户意义不大】:
1)语法分析,分析语句的语法是否符合规范,衡量语句中各表达式的意义。
2)语义分析,检查语句中涉及的所有数据库对象是否存在,且用户有相应的权限。
3)视图转换,将涉及视图的查询语句转换为相应的对基表查询语句。
4)表达式转换, 将复杂的 SQL 表达式转换为较简单的等效连接表达式。
5)选择优化器,不同的优化器一般产生不同的“执行计划”
6)选择连接方式, ORACLE 有三种连接方式,对多表连接 ORACLE 可选择适当的连接方式。
7)选择连接顺序, 对多表连接 ORACLE 选择哪一对表先连接,选择这两表中哪个表做为源数据表。
8)选择数据的搜索路径,根据以上条件选择合适的数据搜索路径,如是选用全表搜索还是利用索引或是其他的方式。
9)运行“执行计划”。
from 子句--执行顺序为从后往前、从右到左
表名(最后面的那个表名为驱动表,执行顺序为从后往前, 所以数据量较少的表尽量放后)
oracle 的解析器按照从右到左的顺序处理,FROM 子句中的表名,FROM 子句中写在最后的表(基础表 driving table)将被最先处理,即最后的表为驱动表,在FROM 子句中包含多个表的情况下,你必须选择记录条数最少的表作为基础表。如果有3 个以上的表连接查询, 那就需要选择交叉表(intersection table)作为基础表, 交叉表是指被其他表所引用的表
多表连接时,使用表的别名并把别名前缀于每个Column上。可以减少解析的时间并减少那些由Column 歧义引起的语法错误.
▼
where子句--执行顺序为自下而上、从右到左
ORACLE 采用自下而上从右到左的顺序解析Where 子句,根据这个原理,表之间的连接必须写在其他Where 条件之前, 可以过滤掉最大数量记录的条件必须写在Where 子句的末尾。
▼
group by--执行顺序从左往右分组
提高GROUP BY 语句的效率, 可以通过将不需要的记录在GROUP BY 之前过滤掉。即在GROUP BY前使用WHERE来过虑,而尽量避免GROUP BY后再HAVING过滤。
▼
having 子句----很耗资源,尽量少用
避免使用HAVING 子句, HAVING 只会在检索出所有记录之后才对结果集进行过滤. 这个处理需要排序,总计等操作.
如果能通过Where 子句在GROUP BY前限制记录的数目,那就能减少这方面的开销.
▼
sql 语句用大写的;因为 oracle 总是先解析 sql 语句,把小写的字母转换成大写的再执行
四、函数
网址:
https://www.jianshu.com/p/fc627da82bb9
https://www.cnblogs.com/LUO77/p/5880053.html
https://blog.csdn.net/qq_33730348/article/details/79865553
https://www.cnblogs.com/winter-bamboo/p/10779466.html
extract
-- 查询 1990 年出生的学生名单 (extract 用于提取时间的部分,如:年、月、日、时、分、秒)
# 方法1:
select * from student where extract(year from sage)=1990;
# 方法2:
select *
from student
where sage >= to_date('1990-01-01', 'yyyy-mm-dd')
and sage < to_date('1991-01-01', 'yyyy-mm-dd');
还没有评论,来说两句吧...