分分钟搞掂SQL

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/d345389812/article/details/82832068

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三个表,在下面的引用当中三个表都得引用

猜你喜欢

转载自blog.csdn.net/d345389812/article/details/82832068
今日推荐