SQL是数据分析师最最基础的一项技能,而身为数据分析师的小文,每天必做的事情就是写SQL取数,那么今天我们就来说说关于SQL的一些使用心得。
开始之前,先来说说关于SQL的读音,有人说SQL在国外的读音是'S-Q-L'三个字母的读音,而在国内大部分都是读作'sequel',音译的话是'社口',那到底哪一个才是正确的读音呢?经考究,正确的读音是'S-Q-L',当然你要读'sequel'也可以,就像APP,国内大部分人都读'A-P-P',而国外的,甚至香港的同胞们都读'æp',知道指的是哪一个就可以了,如果你没有强迫症的话就没必要去纠正自己的发音,更没必要去纠正别人(不然别人会觉得你莫名其妙 -_-!)而小文呢,习惯性地把SQL念为'S-Q-L',因为小文觉得它是由三个单词的首字母组成的,当然啦APP我也是念'A-P-P'。
示例:
例子涉及三个基础表,分别是
- jcb1(字段:姓名,性别,年龄,月份)
- jcb2(字段:姓名,伙食费,月份)
- jcb3(字段:姓名,小区地址)
现在我们要做的是将人数top10的小区的每个住户按照性别年龄伙食费分组统计人数(4—6月)。
with t1 as
(select a.name,a.gender,a.age
from
(select name,gender,age,month,row_number() over (partition by name order by month desc) px
from jcb1
where month between 04 and 06) a
where a.px=1),
t2 as
(select name,sum(fy) fee
from jcb2
where month between 04 and 06
group by name),
t3 as
(select b.place,b.name
from jcb3 b
join
(select place,count(distinct name) amount
from jcb3
group by place
order by amount desc
limit 10 ) c
on b.place = c.place)
insert into young_fee
select
count(distinct t3.name) amount,
t3.place,
case when t1.age < 30 then '0-30'
when t1.age >= 30 and t1.age < 60 then '30-60'
when t1.age >= 60 then '60+'
else '其他'
end age,
case when t2.fee < 1000 then '0-1000'
when t2.fee >= 1000 and t2.fee < 3000 then '1000-3000'
when t2.fee >= 3000 then '3000+'
else '其他'
end fee,
case when t1.gender = 0 then '女'
when t1.gender = 1 then '男'
else '其他'
end gender
from t3
left join t1 on t3.name = t1.name
left join t2 on t3.name = t2.name
group by
case when t1.age < 30 then '0-30'
when t1.age >= 30 and t1.age < 60 then '30-60'
when t1.age >= 60 then '60+'
else '其他'
end,
case when t2.fee < 1000 then '0-1000'
when t2.fee >= 1000 and t2.fee < 3000 then '1000-3000'
when t2.fee >= 3000 then '3000+'
else '其他'
end,
case when t1.gender = 0 then '女'
when t1.gender = 1 then '男'
else '其他'
end
t3.place
上面举例的这段SQL语句可以说涵盖了我们常用的语句了,下面我们将拆出来一个一个来介绍。
1、执行顺序
一般我们写SQL的时候,语句顺序为:
- select
- from
- where
- group by
- having
- order by
那么运行的顺序也是如此吗?答案是否定的,SQL语句在运行的过程中它的顺序是这样的:
- from
- where
- group by
- having
- select
- distinct
- union
- order by
也就是说取表--过滤--分组聚合--过滤--取想要的字段--去重--合并--排序。
这样的运行顺序常常导致我们在写SQL的时候,稍微不留意就执行失败了,所以还是有必要了解一下SQL语句运行的顺序。
2、过滤:where or having
在上面我们提到过滤,有两个语句都能实现条件过滤,一个是where,一个是having。
它们主要区别在于运行的顺序:where在group by前,having在group by后。
也就是说where是分组聚合之前就进行过滤,所以过滤的条件只能是表里面原有的字段,这也是为什么where后面不能是聚合函数的原因。而having是分组聚合之后再进行过滤,所以过滤的条件可以是表里面原有的字段,也可以是聚合函数,比如:
#错误语句:
select count(distinct name) amount,place
from t3
where count(distinct name) > 10
group by place
#正确语句:
select count(distinct name) amount,place
from t3
group by place
having count(distinct name) > 10
这里提到group by就顺便说说group by吧。当我们跑取的字段里面需要做聚合时,我们会用到group by,这时候除了使用了聚合函数的字段外,其余的字段都得放到group by之后,比如:
select count(distinct t3.name) amount,t3.place,
case when t1.gender = 0 then '女'
when t1.gender = 1 then '男'
else '其他'
end gender
from t3
left join t1 on t3.name = t1.name
group by
case when t1.gender = 0 then '女'
when t1.gender = 1 then '男'
else '其他'
end,
t3.place
3、去重:distinct or row_number()over()
两者都可以实现去重的效果,但是实现的原理有些许不同。
(1)distinct:单字段去重时,distinct放在该字段前面即可。比如:
select count(distinct name) amount,place
from t3
group by place
此时返回唯一不同的name然后计数,而需要多字段去重时,distinct必须放在所有字段前面。比如:
select distinct name,place from t3
此时返回唯一不同的name和place值,去重的是name,place都相同的记录。
(2)row_number()over():排序然后按条件去重,比如
select a.name,a.gender,a.age
from
(select name,gender,age,month,row_number() over (partition by name order by month desc) px
from jcb1
where month between 04 and 06) a
where a.px=1)
上面的例子我们可以看到为了避免在4到6月中存在多条同一个name,但性别或者年龄不一样的记录(也就是说某个人的记录发生了更新,但是没有覆盖掉之前的记录),因此我们以name分组按照month降序,取第一的那条记录,也就是4到6月最新的那条记录,达到去重效果。当然用row_number()over()进行排序,取前5前10也是可以的,只是这样就达不到去重的效果,此时的功能等同于order by。
这里提到order by就顺便说说order by吧。order by有升序(asc)和降序(desc)两种,运行顺序排在最后,也就是说执行完所有语句之后再进行排序,在order by 后面加个limit,可以筛选出前多少条记录或者后多少条记录,比如:
select place,count(distinct name) amount
from jcb3
group by place
order by amount desc
limit 10
返回以place分组,amount降序,amount前十的记录。
4、合并:union or union all
两者都能实现多个表或者多个查询结果集合并,不同的地方是union会自动去重,union all则会保留所有记录。
另外需要注意的一点是union或者union all 运行顺序是在order by 前面的,也就是说合并完之后才能实现排序。而合并之前就算排好了序,合并后总表的顺序还是乱的,比如
select a.place,count(distinct a.name) amount
from a
group by a.place
order by a.amount desc
union
select b.place,count(distinct b.name) amount
from b
group by b.place
order by b.amount desc
上面的select结果集都排好了序然后union在一起,但是合并后的顺序还是乱的,要排序的话还是要再建一个子查询,比如
select c.place,c.amount
from
(select a.place,count(distinct a.name) amount
from a
group by a.place
order by a.amount desc
union
select b.place,count(distinct b.name) amount
from b
group by b.place
order by b.amount desc) c
order by c.amount desc
5、分组:case when then else end
这个语句很好理解用起来也比较简单却很实用,比如:
select count(distinct t3.name) amount,t3.place,
case when t1.gender = 0 then '女'
when t1.gender = 1 then '男'
else '其他'
end gender
from t3
left join t1 on t3.name = t1.name
group by
case when t1.gender = 0 then '女'
when t1.gender = 1 then '男'
else '其他'
end,
t3.place
当 t1.gender为0,则为女;当t1.gender为1,则为男;否则为其他
6、公用表表达式:with as
with as的优势:
- 增强SQL语句的可读性:查询时嵌套的表比较多或者语句比较复杂时
- 提高效率:with子句只做一次查询,作为临时表,可反复使用,提高效率
比如:
with t1 as
(select a.name,a.gender,a.age
from
(select name,gender,age,month,row_number() over (partition by name order by month desc) px
from jcb1
where month between 04 and 06) a
where a.px=1),
t2 as
(select name,sum(fy) fee
from jcb2
where month between 04 and 06
group by name),
t3 as
(select b.place,b.name
from jcb3 b
join
(select place,count(distinct name) amount
from jcb3
group by place
order by amount desc
limit 10 ) c
on b.place = c.place)
insert into young_fee
select ......
上面这个with as子句查询了三个临时表,在接下来的查询过程中,都可重复使用,起到优化的作用,需要注意的是:
- with查询的t1,t2,t3表,只需要一个with,t1与t2之间用逗号隔开,t2与t3用逗号隔开,t3与接下来的SQL语句用括号隔开即可
- with查询的结果列有别名时,引用时必须使用别名,比如t3里面的amount,在下面的引用当中也得用amount
- with查询的结果表,在下面的引用当中必须得引用,否则会报错。比如with查询的结果表有t1,t2,t3三个表,在下面的引用当中三个表都得引用