题干
假设有一个张学生选课表(stud_subject),这个表有两个字段:姓名(name)和学科(subject),另假设班级学生姓名没有重名的,这个表的两个字段构成联合主键(name,subject)。建表和测试数据初始化语句如下:
create table stud_subject(
name varchar(50) not null,
subject varchar(50) not null
);
alter table stud_subject add primary key (name, subject);
insert into stud_subject values ('张三','语文');
insert into stud_subject values ('张三','数学');
insert into stud_subject values ('张三','化学');
insert into stud_subject values ('李四','语文');
insert into stud_subject values ('李四','英语');
insert into stud_subject values ('王五','数学');
insert into stud_subject values ('王五','化学');
commit;
问题:写一条sql语句选择所有的选了‘数学’这门课但是没有选‘语文’的同学姓名列表,对应上面的测试语句应该返回‘王五’这一名同学。要求能够给出多种写法,越多越好。
解答
所有的语句均在Oracle数据库中测试通过。
1. exists 或 in
这个是最容易想到的,exists:
select a.name
from stud_subject a
where a.subject='数学' and not exists (
select b.* from stud_subject b
where b.name=a.name and b.subject='语文')
in:
select a.name
from stud_subject a
where a.subject='数学' and a.name not in (
select b.name from stud_subject b where b.subject='语文')
2. left join
这个有一定的技巧,对于大数据量的表格来说这个应该是比较好优化的。
select a.name
from ( select name from stud_subject where subject='数学') a
left join
( select name from stud_subject where subject='语文') b
on (a.name =b.name)
where b.name is null
3. 复合语句
和 exists 一样也是最容易想到的。复合语句有点流氓,我感觉它就是两条语句,哈哈。
select name from stud_subject where subject='数学'
minus
select name from stud_subject where subject='语文'
4.交叉制表
这个有点像孔乙已茴香豆的“茴”的四种写法。
select a.name
from (select t.name, sum(t.shuxue) as shuxue, sum(t.yuwen) as yuwen
from (select name ,
case subject when '数学' then 1 else 0 end as shuxue,
case subject when '语文' then 1 else 0 end as yuwen
from stud_subject) t
group by t.name ) a
where a.shuxue = 1 and a.yuwen = 0
简化一下:
select a.name
from
(select name,
sum(case subject when '数学' then 1 else 0 end) as shuxue,
sum(case subject when '语文' then 1 else 0 end) as yuwen
from stud_subject
group by name ) a
where a.shuxue = 1 and a.yuwen = 0
5. having
把交叉制表的这个语句再简化一下,用having过滤:
select a.name
from ( select name , decode(subject,'数学',1,0) as shuxue,
decode(subject,'语文',1,0) as yuwen from stud_subject ) a
group by a.name
having sum(a.shuxue) = 1 and sum(a.yuwen) = 0
再变换一下,这样虽然更具技巧性,但是更加简洁。
select a.name
from ( select name , decode(subject,'数学',2,'语文',1,0) as tag
from stud_subject ) a
group by a.name
having sum(a.tag) = 2
再简化一下
select name
from stud_subject
group by name
having sum(decode(subject,'数学',2,'语文',1,0)) = 2
感想
这个题目重点考核sql语句与集合运算的关系。
本人一直从事面试工作,一般也不会文什么刁难的题目,只是碰到有ocp证书或者一直从事数据库开发的人员会问这个问题,但是比较遗憾的是超过3/4的人一个也想不出来,至今没有一个人说出两种以上的写法的人。作为一个软件开发人员基本功还是要练的。