sql server关系代数练习--进阶

前言:

一道关系代数题目的解法有很多种,最暴力的解法是一层嵌一层创建新关系,这样做的好处是简单易懂,坏处是sql语句过于冗长,而且嵌套层数多的时候往往是“凑”出来的答案,即仅限于当前条件,条件稍作更改就崩盘。所以最考验自己能力的是创建最少的新关系,解决更普遍的问题。当然不要走进极端,有的时候创建必要的新关系进行筛选信息还是rather important滴。

最近又练习了一套关系代数的试题,做完下来感觉提升了不少,
下面记录一下拿到题目,我的思路:

table描述:

  1. product(maker,model,type)

     maker:表示生产厂商
    
     model:生产的产品型号
    
     type:产品类型,有pc  laptop  printer三种
    
  2. pc(model,speed,ram,hd,price)

     分别表示:
     型号,速度,内存大小,硬盘大小,价格
    
  3. laptop(model,speed,ram,hd,screen,price)

     分别表示:
     型号,速度,内存大小,硬盘大小,屏幕大小和价格
    
  4. printer(model,color,type,price)

     分别表示的含义是:
    
     model:打印机型号;
    
     color:是否彩色, T 彩色,F 黑白
    
     type:类型,ink-jet 表示喷墨, laser 表示激光;
    
     price:单价
    

第一关

  • [题目描述]

     1. 查询生产pc也生产laptop的厂商。
     2. 查询生产型号为2001的厂商信息及该型号所属产品的种类。
     3. 查询厂商A 生产的PC中price大于900的产品型号。
     4. 查询生产ink-jet的打印机的厂商及价格。
     5. 查询厂商B生产的所有产品的型号和价格。
    

- [题解1]

查询:生产pc也生产laptop的厂商。

题目的意思是生产的产品中包含有pc和laptop的厂商,而不是“只有”。
先不说最简单的做法,看到题目大家想想有什么好的思路,一个好习惯是使用关系代数写出自己的思路,然后再使用sql语言表达出来。

我们可以先去除(select)只生产产品种类少于2种的厂商,这个好解决,然后在此基础上去除(select)生产种类为2种(count(type)=2)的厂商中,生产了printer(将种类数量映射回具体的种类)的厂商(那么一定不可能同时生产pc和lapop了),把自己都说晕了有木有~这种方法可想而知,语句很冗长很复杂,不具备通解的特点。

换种思路,生产pc and 生产了laptop,也就是生产pc intersect 生产laptop, 也就是本题可以使用交集来实现,上代码:

select maker from
(select * from product where type='laptop') as a
intersect 
select maker from
(select * from product where type='pc') as b;

这里作一个小插曲,在写sql语句的时候有的时候需要重命名,而有的时候不需要,如果你对这个地方有疑惑,看看下面的例子:

select distinct maker, type from product 
where maker not in
(select maker from product where type='printer');

简单的理解是:在"in"/"not in"后面生成的新关系其实是新“集合”(说成“集合”更为贴切),不用重命名;而在"from"后面生成的新关系就需要重命名;

- [题解2]

查询:生产型号为2001的厂商信息及该型号所属产品的种类。

select maker, type from product where model = '2001';

简单的查询语句就不做多说~

- [题解3]

查询:厂商A 生产的PC中price大于900的产品型号。
因为除了product 关系之外,其他的关系没有“厂商”的字段,但是product又没有price字段,所以这道题需要“join”操作。因为提及到pc,所以需要product和pc的join:

product join pc on product.model=pc.model

至于on之后的内容,我们更多的是选择能唯一标识一个元组、两表共有的属性。join完成之后就是常规的select 操作了:

select pc.model from product 
join pc on product.model = pc.model 
where maker='a' and price>900;

- [题解4]

查询:生产ink-jet的打印机的厂商及价格。

select maker, price from product 
join printer on product.model = printer.model 
where printer.type='ink-jet';

这道题和上到题的难点一样,不做多说~

- [题解5]

查询:厂商B生产的所有产品的型号和价格。
一般碰到一个问题的描述的时候不要盲目的将最后面几个词:“型号”、“价格”过滤掉,而是先看看整句表述所涉及到的属性是否在同一个关系内,如果是的,那就过滤;否则要考虑连接。本题就是“型号”和“价格”虽然可以在一个关系中,但是厂商和price不在一个关系内,所以要先连接,
如果仅仅看B 厂商生产的所有产品,我们可以在product表中解决,但是题目需要找出相应的产品价格和型号,所以本题在一个表中是解决不了的,需要join,可能你会说,我从pc或者laptop或者printer里边选出model和price,然后加上条件:maker=…还没说完就发现这三个表没有maker字段啊,那怎么办?没有maker就换种方式嘛。product 和其他三个商品表有一个共同的字段:model,它也可以唯一标识产品,那么一个厂商就对应了相应的产品的model,这样一来,上面所说的“加上条件”可以设置为:

model in (select model from product where maker = 'B')

不难理解对吧?那么B厂商所生产的pc的model和price用sql如何表示呢?:

select model, price from pc where model in (select model from product where maker = 'B')

然后选出所有的产品呢?如你所想,并:

select model, price from pc where model in (select model from product where maker = 'B')
union
select model, price from laptop where model in (select model from product where maker = 'B')
union 
select model, price from printer where model in (select model from product where maker = 'B');

第二关

  • [题目描述]

     1. 分类统计厂商(maker)A生产的各种产品的数量 
     2. 查询所有彩色激光打印机的生产商及型号。
     3. 找出生产产品型号最多的厂商。
     4. 查询出哪个生产厂商的笔记本电脑的硬盘容量至少100G。
    

- [题解1]

分类统计厂商(maker)A生产的各种产品的数量:

题目说明白点就是要求出A产商的所有产品,然后根据“产品类别”分类并求出每一个类别产品的数量。所以有一个先后的顺序:先求出A厂商生产的所有产品,然后分类统计。代码:

select type, count(model) from 
(select * from product where maker='A') as a 
group by type ;

- [题解2]

查询:所有彩色激光打印机的生产商及型号。

select maker, product.model from 
product join printer on product.model=printer.model 
where color='T' and printer.type='laser';

简单的语句就不多啰嗦啦~

- [题解3]

找出生产产品型号最多的厂商。

产品的型号model和厂商maker同时在product表中,所以不用join。
大体上的思路是,先根据厂商分组并计数每一个厂商所生产的产品(model)数量(作为新关系),然后从新关系中选出数量最大的那一个厂商即可。这样的思路很容易写出来,那么如何使用较为简洁的sql语言表达出来呢?我们将这道题分解,一步一步解决,先根据厂商分组并计数每一个厂商所生产的产品(model)数量:

select maker, count(model) as count_model from 
product group by maker;

因为后面还要选出最大数量,所以要先重命名为count_model便于后面调用。现在的任务是从下表中选出count_model数量最大的maker:
在这里插入图片描述

看到这里思路自然而然来了:用max(count_model)不就完了!但是max是一个聚集函数呀,它无法直接作为where后面的条件,也就是说这样写是错的:

select maker from 
(select maker, count(model) as count_model from 
product group by maker) as a
where count_model=max(count_model);

报错:

聚合不应出现在 WHERE 子句中,
除非该聚合位于 HAVING 子句或选择列表所包含的子查询中,
并且要对其进行聚合的列是外部引用。

也就是说聚集函数不能作为where 后面的条件来筛选。
或许你会想:用having可以嘛?试试看:

select maker from 
(select maker, count(model) as count_model from 
product group by maker) as a
having count_model=max(count_model);

报错:

HAVING 子句中的列 'a.count_model' 无效,
因为该列没有包含在聚合函数或 GROUP BY 子句中。

其实上面这句有语法上的错误,having语句得作为group by后面的条件语句,如果没有group by则只能使用where.

select maker from 
(select maker, count(model) as count_model from 
product group by maker) as a
having count_model=max(count_model);

而且,这样也会报错:

select maker from (select maker,count(model) as sum_model 
from product group by maker) as R group by maker 
having sum_model=max(sum_model);
HAVING 子句中的列 'R.sum_model' 无效,因为该列没有包含在聚合函数或 GROUP BY 子句中。

但是类似这样的写法是正确的:

select count(学号)as 学生人数 from 学生信息 
group by 专业 having 专业='计算机科学与技术';

从上面几个例子看出:

  1. group by 后面不能加聚集函数,即不能将聚集函数作为分组依据。
  2. select 域1 from table group by 域2 having 域3
    其中如果域1不含聚集函数,那么域1和域2要相等,域3要是域2的子集才有可能正确;如果域1包含聚集函数,那么除了聚集函数之外的字段需要和域2相等,且域3要是域2的子集才有可能正确;如果像上面这个例子一样,域1只包含聚集函数,那么域1和域2可以不相等。
    也就是说,聚集函数作为一个特殊的存在。

上面几个错误都是很容易犯的,得牢牢掌握~

那么如何解决这最后一公里问题呢?这里给出两种方法:
方法一:

select maker from 
(select maker, count(model) as count_model from 
product group by maker) as a where count_model in
(select max(count_model) as max_count_model from 
(select maker, count(model) as count_model from 
product group by maker) as a);

这种方法相信看了都会望而却之,我重新将每一个新关系重提取出来命名,这样会清晰一些(这样写在sql语句中不能运行的,仅为了方便理解):

(select maker, count(model) as count_model from 
product group by maker) as a -- 根据厂商分组并统计model数量

(select max(count_model) as max_count_model from 
(select maker, count(model) as count_model from 
product group by maker) as a) max_num --选出统计出的最大值

-- 选出计数为最大值的厂商:
select maker from a where count_model = max_num;

如果非要这样有条理点写出来,那得使用视图,利用每一步创建的新关系创建视图。
这种方法过于麻烦,下面看看这种方法:

select top 1 maker from 
(select maker, count(model) as count_model from 
product group by maker) as a order by count_model desc;

它的思想是计数了每一种maker生产的model之后进行从大到小排序,然后选择第一个厂商即可,这里的

select top n maker from table

意思是从table表中选出前n个maker。
再给一个更加精简的做法:

select top 1 maker from 
product group by maker order by count(model) desc;

- [题解4]

查询:哪个生产厂商的笔记本电脑的硬盘容量至少100G。

意思是求:生产的笔记本电脑硬盘容量全都大于等于100G的生产商。

这个好办,先根据厂商分组,然后求出每一个组别中的最小hd值(硬件容量值),选出最小值大于或等于100G的厂商:

select maker from
(select maker, min(hd) as min_hd from 
laptop join product on 
laptop.model = product.model group by maker) as a
where min_hd >= 100;

第三关

  • [题目描述]

     1. 查询具有同样处理速度和同样内存大小的PC对(显示满足条件的pc对的型号,
     同样的pc对只出现1次,如001 与 002 符合条件, 则仅出现001 002,不出现002 001)。
     2. 查询至少生产三种不同处理速度电脑(含pc和laptop)的厂商。
     3. 统计出pc,laptop,printer三种产品的不同型号数量,并按数量从大到小排序。
     4. 有客户有1500元钱,买laptop或pc,要求硬盘容量不小于80,请给出可能的产品型号,生产厂商。
     5. 找出至少生产5种不同型号产品的厂商。
    

- [题解1]

查询:具有同样处理速度和同样内存大小的PC对(显示满足条件的pc对的型号,同样的pc对只出现1次,如001 与 002 符合条件, 则仅出现001 002,不出现002 001)。

这道题需要将一个表中的两个元组的speed和hd属性进行分别比较,选出这两个属性相等的元组各自的model,仅用简单的sql增删查改语句无法同时操作两个元组对象,那么我们想办法将两个需要比较的元组对象合并到一个元组,然后对这一个元组的相关属性进行比较即可,也就是下方这样:

select pc1.model as model1, pc2.model as model2 from 
pc as pc1 cross join pc as pc2 
where pc1.speed=pc2.speed and pc1.ram=pc2.ram and pc1.model <> pc2.model;

来看看结果:
在这里插入图片描述

貌似不对,按照题目括号内的内容来讲,这是重复的,如何去除重复的呢?这个时候我们可以从数据的本身特点出发,来实现筛选,观察model属性,它是varchar类型,所有的model值都是前两位为10,后两位不定,那么我们可以根据这一特性,利用字符比较来筛选,将最后的条件改为:

where pc1.speed=pc2.speed and pc1.ram=pc2.ram and pc1.model < pc2.model;

也就是pc1.model<pc2.model,而不是仅仅“不等于”,运行一下:

select pc1.model as model1, pc2.model as model2 from 
pc as pc1 cross join pc as pc2 
where pc1.speed=pc2.speed and pc1.ram=pc2.ram and pc1.model < pc2.model;

结果:
在这里插入图片描述

看到这就实现了题目要求的功能。这道题虽然说利用数据的特性看起来不像是通法,但是在现有知识解决不了的时候不失为一种比较有效的方法,但是要注意,想利用数据本身特点来筛选数据的时候最好是选能作为候选键的属性列,因为这样的属性列一般情况下不会变化。

- [题解2]

查询至少生产三种不同处理速度电脑(含pc和laptop)的厂商。

速度和厂商不在同一个关系中,所以要连接操作,这里使用等值连接最合适,但是问题来了,它要求的是两种不同的电脑:pc和laptop,可以使用“并”来解决,所以第一步将新关系创建好的sql语句为:

select maker, product.model, speed from 
product join pc on product.model = pc.model 
union
select maker, product.model, speed from
product join laptop on product.model = laptop.model

假设上面创建好的新关系为R,现在需要从R中分组,计数不同速度的种类,sql语句是:

select maker, count(distinct speed) as speed_variety from R
group by maker;

还没完,还要从中选出speed_variety数目大于等于3的maker,假设上述关系为S,则sql语句为:

select maker from S
where speed_variety >= 3;

总的SQL语句为:

select maker from(
select maker, count(distinct speed) as speed_variety from(
select maker, product.model, speed from 
product join pc on product.model = pc.model 
union
select maker, product.model, speed from 
product join laptop on product.model = laptop.model) as a
group by maker) as b 
where speed_variety >= 3;

总的语句虽然不复杂,但是太长了,可以换一种写法:

select maker from(
select maker, product.model, speed from 
product join pc on product.model = pc.model 
union
select maker, product.model, speed from 
product join laptop on product.model = laptop.model) as a
group by maker having count(distinct speed) >= 3;

简化一下就是

select maker from a group by maker having count(distinct speed)>=3

这也教会自己,聚集函数可以作为分组操作的group by后面的having条件。

- [题解3]

统计出pc,laptop,printer三种产品的不同型号数量,并按数量从大到小排序。

涉及到型号model和type,由于pc和laptop无type属性,所以需要等值连接。有两个思路:

  1. 先将pc, laptop, printer分别和product等值连接,然后选取相同的属性列进行“并”操作,最后整体进行分组、聚集、排序。
  2. 将pc, laptop, printer和分别和product等值连接,然后各自先分组、聚集、排序,最后进行“并”操作。

下面对两种思路分别实现一遍:
第一种思路:

select type, count(model) as c from (
select type, product.model as model from 
product join pc on product.model = pc.model 
union
select type, product.model as model from 
product join laptop on product.model = laptop.model
union 
select product.type, product.model as model from 
product join printer on product.model = printer.model) as a 
group by type
order by c desc;

注意最后的group by语句只能在order by之前。
第二种思路:

select * from (
select type, count(product.model) as c from 
product join pc on product.model = pc.model group by type
union
select type, count(product.model) as c from 
product join laptop on product.model = laptop.model group by type
union 
select product.type, count(product.model) as c from 
product join printer on product.model = printer.model 
group by product.type) as a
order by c desc;

注意到一个地方:题目说“不同型号”,因为数据里展现的model本身就是不同的,所以我就没有加上distinct了,如果为了保险,也可以尝试着加过去。

- [题解4]

有客户有1500元钱,买laptop或pc,要求硬盘容量不小于80,请给出可能的产品型号,生产厂商。

当题目做多了,哪些是条件,哪里需要什么操作,题目读完稍作分析就很明了有木有~

条件:price<=1500, hd >= 80,
所选关系:laptop join product union pc join product
操作:select maker, model
这样一分解就简单了:

select maker, model from (
select product.maker as maker, product.model, hd, price from 
product join pc on product.model = pc.model
union 
select product.maker as maker, product.model, hd, price from 
product join laptop on product.model = laptop.model
) as a where price<=1500 and hd>=80 
order by model;

最后加一个order by model是因为我希望model按照一定的顺序展现出来。

- [题解5]

找出至少生产5种不同型号产品的厂商。

和前面的相比,这道题就是非常简单的啦,直接上码:

select maker from 
(select maker, count(model) as model_num from 
product group by maker) as a
where model_num>=5;

总体来说这套题目难度挺大的,如果想看看之前的一篇稍稍基础点的练习,点击我的这篇博客:
https://blog.csdn.net/weixin_43141320/article/details/105270490

发布了79 篇原创文章 · 获赞 8 · 访问量 3304

猜你喜欢

转载自blog.csdn.net/weixin_43141320/article/details/105485014