代码改变世界

MSSQL综合技术补习---常见笔试题1

2012-03-16 00:28  海不是蓝  阅读(1322)  评论(5编辑  收藏  举报

为什么写这篇文章


本人最近在找工作,各种面试笔试,很多知识点久了都快忘记了,特别SQL也很久没有写了。

为了快速系统的补习下SQL相关的知识,决定写“MSSQL综合技术补习”系列文章。开始几篇为常见sql面试题型,然后慢慢转向系统的sql知识。


考点1---分组聚合条件统计(透视转换)


这样的题先分组,然后根据条件用case语句来区分条件,最后再聚合条件列。

(虽然透视转换可以使用SQL内置的Pivoting来实现,但是我觉得死语法没有自己写那样思路清楚,所以我没有使用内置的透视转换)

透视转换的步骤:

 

3个逻辑处理阶段:1.分组  2.扩展  3.聚合

 

 

例题1

2005-05-09

2005-05-09

2005-05-09

2005-05-09

2005-05-10

2005-05-10

2005-05-10

如果要生成下列结果, 该如何写sql语句?

 


时间         

2005-05-09

2

2

2005-05-10

1

2

 


分析:先根据rq分组,然后根据shengfu扩展,最后再根据shengfu聚合

 

select 
rq as 日期,
sum(case shengfu when '' then 1 else 0 end) as 胜,
sum(case shengfu when '' then 1 else 0 end) as
from tmp
group by rq


分辨使用透视转换的场景!

1.数据行是否转换成列,例如胜负是shengfu这个列的2个状态,而且展示结果胜负变成了2个列。

2.转换结果列的数据行是否是聚合结果。

如果上面2个条件都符合,那么就可以使用透视转换来做!

 


下面也是网上经常出现的sql面试题,我们用他来分析

 

table1

月份mon

部门dep

业绩yj

一月份

01

10

一月份

02

10

一月份

03

5

二月份

02

8

二月份

04

9

三月份

03

8

 

table2

部门dep

部门名称dname

01

国内业务一部

02

国内业务二部

03

国内业务三部

04

国际业务部

          

请用sql得到下面的结果

table3 (result)

部门dep

一月份

二月份

三月份

01

10

null

null

02

10

8

null

03

null

5

8

04

null

null

9

 


分析:table1中的mon列的数据1月份,2月份,3月份在table3里面变成了列,而这些列的数据是table1的yj列的sum聚合,所以这里只用透视转换

 

select a.dep,
sum(case when b.mon=1 then b.yj else 0 end) as '一月份',
sum(case when b.mon=2 then b.yj else 0 end) as '二月份',
sum(case when b.mon=3 then b.yj else 0 end) as '三月份'
from table2 a left join table1 b on a.dep=b.dep


注意下这里是left join,因为你不可能知道部门的所有id,所以用部门的表去left join业绩表。

 

另外一个重点

就是在聚合数据的时候,需要考虑聚合数据是否存在NULL,如果存在,为了防止数字和NULL计算结果为NULL,应该使用ISNULL函数进行检查。

 

select 1+null
select sum(1+null)
--结果为NULL
select 1+isnull(null,0)
select sum(1+isnull(null,0))



考点2---求符合聚合结果的数据(HAVING)

 

这样的题出现在面试题中的机会是很大的,题意的答案几乎都是先分组聚合,再在分组集上使用Having寻找符合题意的数据。

下面给出个最经典的题目。

为管理岗位业务培训信息,建立3个表:


S-学员表

S#-学号

SN-学员姓名

SD-所属单位

SA-学员年龄

 

C-课程表

C#-课程编号

CN-课程名称

 

SC-成绩表

S#-学号

C#-课程编号

G-学习成绩

 

要求:

使用标准SQL嵌套语句查询选修全部课程的学员姓名和所属单位

做这样的题目需要对Group by 和having有比较好的理解。

 

创建表和插入数据的sql

 

创建表
create table S
(
S# int identity(1,1) primary key,
SN nvarchar(20) not null,
SD nvarchar(20) not null,
SA int not null
)
create table C
(
C# int identity(1,1) primary key,
CN nvarchar(30) not null
)
create table SC
(
S# int not null,
C# int not null,
G float not null
)
--插入学生表
insert into S (SN,SD,SA) values ('小明','广电河蟹部门',18)
insert into S (SN,SD,SA) values ('小花','有爱城管部门',18)
insert into S (SN,SD,SA) values ('小蛋','神秘有关部门',18)
--插入课程表
insert into C (CN) values ('数学')
insert into C (CN) values ('英语')
insert into C (CN) values ('语文')
insert into C (CN) values ('物理')
--插入成绩表
insert into SC (S#,C#,G) values (1,1,1)
insert into SC (S#,C#,G) values (1,2,8)
insert into SC (S#,C#,G) values (1,3,6)
insert into SC (S#,C#,G) values (1,4,4)
insert into SC (S#,C#,G) values (2,3,0)
insert into SC (S#,C#,G) values (3,4,12)


分析:

实际应用当中我们一般是不知道状态表会有多少数据的,也就是课程表,所以课程表的课程总数是应该在sql查询中去得到,然后再结合group by 和having来实现解这种类型题的经典解法。


 

select * from S 
where S# in
(
select SC.S# from SC right join C --注意这里能确定数据的表是课程表,所以用课程表来做连接主表
on SC.C#=C.C# --课程ID是连接的条件
group by SC.S# --根据学生ID分组
having count(distinct(SC.C#)) --注意一个同学可能存在一门课多个成绩
=(select count(C#) from C)
)


另外几个相关题目


列出有二门以上(含两门)不及格课程的学生姓名及其平均成绩   

 

select S.S#,S.SN,SC1.SG from S join 
(
select S#,avg(G) as SG from SC
where G<60 group by S#
having count(distinct C#)>=2
) as SC1
on S.S#=SC1.S#


列出既学过“数学”号课程,又学过“英语”号课程的所有学生姓名   

 

select S.S# from S where S# in 
(
select SC.S# from SC join C on SC.C#=C.C#
where C.CN in ('数学','英语')
group by SC.S#
having count(distinct C.CN)=2
)


列出“1”号课成绩比“2”号课成绩高的所有学生的学号及其“1”号课和“2”号课的成绩

 

--这里要考虑到实际应用中可能出现一门课程多条成绩,所以取最高的成绩比较
select t1.S#,t1.CN,t1.G,t2.CN,t2.G from
(select top 1 SC1.S#,C1.CN,SC1.G from SC as SC1 join C as C1
on SC1.C#=C1.C# where C1.CN='英语' order by SC1.G desc)
as t1
join
(select top 1 SC2.S#,C2.CN,SC2.G from SC as SC2 join C as C2
on SC2.C#=C2.C# where C2.CN='数学' order by SC2.G desc)
as t2
on t1.S#=t2.S# where t1.G>t2.G



考点3 取出表中的第31条到40条记录


取出表A中的第31条到40条记录,ID这里肯定是被破坏了的,不可能让你那么容易的得到。

这里主要是考你分页的知识点,不过比较简单,不多说了。

 

创建表
create table test1
(
id int identity(1,1) primary key,
number int
)
--插入数据
declare @i int
set @i=1
while @i<50
begin
insert into test1 (number) values(@i)
set @i=@i+1
end
--删除几行数据
delete from test1 where id=5
delete from test1 where id=11
delete from test1 where id=26
delete from test1 where id=37
delete from test1 where id=42

select* from test1


注意:虽然这里我们表中有ID和number这2个int字段,但是都是不完整的!而且题目不会让你怎么简单的去依靠连续体字段!

 

--错误的方案-注意order by
select top 10 * from
(
select top 40 * from test1
order by id desc
) as t order by t.id asc

--正确的方案1-结果降序
select top 10 * from
(
select top 40 * from test1
order by id asc
) as t order by t.id desc

--正确的方案2-结果升序
select top 10 * from test1
where id not in
(
select top 30 id from test1
order by id asc
) order by id asc


这里的第一个错误解决方案是因为最后一个order by把结果给升序了,导致出来的不是正确数据,关键在于order by比select 数据列先执行

 

 

考点4 数据库中重复的数据


这个题目就是超级经典的面试题了,而且解法比较多,这里我做个总结。

 

创建表
create table Student
(
id int identity(1,1) primary key,
name nvarchar(20) not null,
age int not null,
sex nchar not null
)
--插入数据
insert into Student (name,age,sex) values ('小明',10,'')
insert into Student (name,age,sex) values ('小花',9,'')
insert into Student (name,age,sex) values ('小蛋',3,'')
insert into Student (name,age,sex) values ('小瓜',4,'')
insert into Student (name,age,sex) values ('亚克西',15,'')
insert into Student (name,age,sex) values ('哦买噶',12,'')
insert into Student (name,age,sex) values ('雅蠛蝶',14,'')
insert into Student (name,age,sex) values ('小花',9,'')
insert into Student (name,age,sex) values ('二蛋',2,'')
insert into Student (name,age,sex) values ('狗蛋',3,'')
insert into Student (name,age,sex) values ('小明',11,'')
insert into Student (name,age,sex) values ('小明',10,'')
insert into Student (name,age,sex) values ('亚克西',15,'')


4-1 查询姓名相同所有重复数据(单字段重复)


常见的思路:

1:通过子查询去查询每个姓名的分组数据总和,然后筛选大于1的分组。

2:使用exists查询是否存在相同姓名不同ID的数据。

 

--判断重复数据条数
select * from Student as S1 where
(
select count(id) from Student as S2
where S2.[name]=S1.[name] group by S2.[name]
)>1

--判断ID
select S1.* from Student as S1 where exists
(
select S2.id from Student as S2
where S2.[name]=S1.[name] and S2.id<>S1.id
)


这2中方法属于比较常见的,如果园友有其它更好的方法请告知。

 

 

4-2 查询重复数据中最早创建的数据(单字段重复)


分组已经筛选大于1的分组,然后select分组中最小的id

 

select * from Student where id in
(
select min(id) from Student
group by [name] having count(id)>1
)


4-3 查询重复数据中非最早创建的数据(单字段重复)


去掉重复数据中最早创建的数据,只查询出后面创建的重复数据

1:使用row_number函数,根据重复字段name分区生成行号,去大于1的行号。

2:求出重复name最小的id,非最早创建的重复数据大于这个最小的id.

 

select * from Student where id in
(
select id from
(
select id,row_number() over(partition by [name] order by id)
as num from Student
) as t where num>1
)

select S1.* from Student as S1 where S1.id>
(
select min(S2.id) from Student as S2
where S2.[name]=S1.[name]
)


4-3 查询多字段重复的数据(name,age,sex)


这个和查询单字段重复数据思想都是一样,只是在判断重复字段的地方要写上需要判断的所有字段,如果有更好的方法请联系我谢谢。

 

--判断重复数据条数
select * from Student as S1 where
(
select count(id) from Student as S2
where S2.[name]=S1.[name]
and S2.age=S1.age
and S2.sex=S1.sex
group by S2.[name]
)>1

--判断ID
select S1.* from Student as S1 where exists
(
select S2.id from Student as S2
where S2.[name]=S1.[name]
and S2.age=S1.age
and S2.sex=S1.sex
and S2.id<>S1.id
)


4-5 删除重复数据


要求:删除全部重复数据

 

--判断重复数据条数
delete from Student where id in
(
select id from Student as S1 where
(
select count(id) from Student as S2
where S2.[name]=S1.[name] group by S2.[name]
)>1
)
--判断ID
delete from Student where id in
(
select S1.id from Student as S1 where exists
(
select S2.id from Student as S2
where S2.[name]=S1.[name] and S2.id<>S1.id
)
)


对于要求保留一条重复数据的题目,可以使用重复数据中最早创建的数据的SQL,获取到id再删除,多字段重复都是相同思路。


作者:海不是蓝

博客:http://www.cnblogs.com/hailan2012/

邮箱:hailan2012@sina.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。