MySQL面试试题(四)

MySQL知识点整理已经进行到第四期了,间隔一段时间再看以前的题目,蓦然发现有些操作莫名其妙,可能是对表的操作还是没有透彻的理解。暑假可能要去实习了,做的就是SQL,希望归来是王者~

一、分组数据

1.创建分组

之前我们整理过聚集函数,总共有5个。我们使用聚集函数可以对行进行计数,计算平均值,求和,最大值和最小值。目前为止,所有的计算都是在表的所有数据或匹配特定的WHERE子句的数据上进行的。
比如,下面的例子返回的是供应商1003提供的产品数目:

SELECT  COUNT(*) AS  num_prods
FROM    products
WHERE   vend_id = '1003';

结果:
在这里插入图片描述
若要返回每个供应商提供的产品数目呢?提供10个以上的产品的供应商呢?
这里,我们将使用分组,将数据分为多个逻辑组,以便对每个分组进行聚集计算。
比如,我们返回每个供应商提供的产品数目,

SELECT  vend_id,COUNT(*) AS  num_prods
FROM    products
GROUP BY vend_id;

结果:
在这里插入图片描述
GROUP BY 按vend_id排序对每个供应商进行分组,然后对每个分组,分别计算num_prods,即提供的产品的个数。
使用GROUP BY 子句的一些规定:

  1. GROUP BY 子句可以包含任意数目的列,这使得能对分组进行嵌套,为数据分组进行更加精细的控制。
  2. 如果在GROUP BY 子句中嵌套了分组,那么所有的列将被一起计算,而不能从个别的列中提取数据。
  3. GROUP BY 子句列出的每个列都必须是检索列或有效的表达式(但不能是聚集函数)。如果在SELECT中使用表达式,则必须在GROUP BY 子句中指定相同的表达式,不能使用别名。
  4. 除聚集语句外,SELECT子句的每个列都必须出现在GROUP BY 子句中出现。
  5. 如果分组列中含有NULL值,则NULL将会被作为一个分组返回。如果列中有多行NULL值,它们将会被分为一组。
2.过滤分组

在进行分组以后,我们需要排除一些分组,比如,列出至少有两个以上订单的顾客。为了得到这种数据,必须基于完整的分组而不是个别的行进行过滤,即where子句不能使用。而HAVING子句用来过滤分组。
比如,返回订单数不小于2的顾客,

SELECT  cust_id,COUNT(*) AS  orders 
FROM    orders
GROUP BY cust_id
HAVING   COUNT(*) >=2;

结果:
在这里插入图片描述
我们可以在一条语句中同时使用WHERE和HAVING子句,比如,要返回具有2个或2个以上、价格不小于10的产品的供应商。

SELECT  vend_id,COUNT(*) AS  num_prods
FROM    products
WHERE   prod_price >=10
GROUP BY  vend_id
HAVING  COUNT(*) >=2;

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

3.分组和排序

在这里插入图片描述
为了说明GROUP BY 和ORDER BY 语句的用法,我们举个栗子:检索总计订单价格不小于50的订单的订单号和总计订单价格:
我们先看只有GROUP BY 的情况:

SELECT  order_num,SUM(quantity*item_price) AS ordertotal 
FROM    orderitems
GROUP BY  order_num
HAVING   SUM(quantity*item_price) >=50;

结果:
在这里插入图片描述
我们再看GROUP BY和HAVING同时存在的情况:

SELECT  order_num,SUM(quantity*item_price) AS ordertotal 
FROM    orderitems
GROUP BY  order_num
HAVING   SUM(quantity*item_price) >=50
ORDER BY  ordertotal;

结果:
在这里插入图片描述
我们可以看到order_num没有变化,但是ordertotal按照从小到大的顺序进行了排序。一般而言,ORDER BY 子句默认是升序排列,如果想逆序排列,则在后面加 DESC即可。

4.SELECT子句顺序

书写顺序:
select from where group by having order by limitselect \ \to from\ \to where\ \to group\ by\ \to having\ \to order \ by\ \to limit

执行顺序:
from where group by having select order by limitfrom \ \to where\ \to group\ by\ \to having\ \to select\ \to order \ by\ \to limit

二、使用子查询

SELECT语句是SQL的查询。我们之前用的都是从单个数据库中检索单条语句。但如果涉及多个表格,查询多条语句,怎么办呢?SQL中使用子查询来解决这个问题。子查询是嵌套在其他查询中的查询。

1.利用子查询进行过滤

如图,有三个表格,第一个表格是客户信息表 Customer,主键是 cust_id:顾客编号。第二个表格是订单表 Order,主要包括 order_num—订单号;order_date—订单日期;cust_id—顾客编号;第二个表格通过 cust_id—顾客ID与第一个表格关联。
第三个表格是订单物品存储信息 OrderItems,通过订单号 order—num与订单表 Order关联。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
问题:我们想查询订购物品TNT2的所有顾客信息,怎么使用子查询?

  1. 在订单物品存储信息表 OrderItems 中查询 物品TNT2 对应的 order_num—订单号;
  2. 在订单信息表 Orders 中查询1中的 order_num 对应的 cust_id—顾客ID;
  3. 在顾客表 Customers 中查询2中的 cust_id—顾客ID 对应的 cust_name 和cust_contact。

&&如果用以前的单条查询,过程是这样的&&

第一步:

SELECT   order_num
FROM     orderitems
WHERE    prod_id = 'TNT2';

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

SELECT   cust_id
FROM     orders
WHERE    order_num IN (20005,20007);

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

SELECT   cust_name,cust_contact
FROM     customers
WHERE    cust_id   IN  (10001,10004);

结果:
在这里插入图片描述
&&使用子查询进行嵌套&&

SELECT   cust_name,cust_contact
FROM     customers
WHERE    cust_id   IN  (SELECT   cust_id
                        FROM     orders
                        WHERE    order_num IN (SELECT   order_num
                                               FROM     orderitems
                                               WHERE    prod_id = 'TNT2'));

结果:
在这里插入图片描述
当然,解决这个问题不止这一种方法,

SELECT   cust_name,cust_contact
FROM     customers a,orders b,orderitems c
WHERE    c.prod_id = 'TNT2' AND  c.order_num = b.order_num  AND  b.cust_id  =  a.cust_id;

结果也是一样的。

2.作为计算字段使用子查询

使用子查询的另一种方法是创建计算字段。比如,要计算某个特定客户的订单数,我们可以这样查询:

SELECT   COUNT(*) AS  orders 
FROM     orders
WHERE    cust_id = '10001';

结果:
在这里插入图片描述
但如果要查询每个顾客的订单数呢?

SELECT   c.cust_id,
         cust_name,
         (SELECT COUNT(*)
          FROM   orders o
          WHERE  o.cust_id = c.cust_id) AS orders 
FROM     customers c
ORDER BY orders desc;

select中的 orders 是一个计算字段,它是由圆括号中的子查询建立的。该子查询对检索出的每个客户执行一次。
结果:
在这里插入图片描述
创建子查询的技巧:
在语句的复杂性不断增加的情况下,子查询的创建很有技巧性。用子查询建立(和测试)查询的最可靠的方法是逐行进行,这与MySQL处理它们的方法非常相同。

  1. 建立和测试最内层的查询
  2. 用硬编码数据建立和测试外层的查询,并且仅在确认它正常后才嵌入子查询
  3. 再次测试,对于要增加的每个查询,重复这些步骤。

四、经典面试题

问题描述:
本题中用到下面三个关系:
CARD 借书卡:CNO 卡号,NAME 姓名,CLASS 班级;
BOOKS 书籍:BNO 书号,BNAME 书名,AUTHOR 作者,PRICE 价格,QUANTITY数量;
BORROW 借书记录:CNO 借书卡号,BNO 书号,RDATE 还书日期;
备注:规定每人每种书只能借一本;库存册数随借书、还书而改变。

-- 创建借书卡表格
##############################################################
CREATE  TABLE  CARD(
    CNO    INT  NOT NULL PRIMARY KEY,
    NAME   VARCHAR(50),
    CLASS  VARCHAR(20)
)ENGINE = INNODB;
-- 创建图书表格
CREATE   TABLE  BOOKS(
    BNO    INT   NOT NULL PRIMARY KEY,
    BNAME  VARCHAR(50),
    AUTHOR VARCHAR(255),
    PRICE  DECIMAL(4,2),
    QUANTITY INT
)ENGINE = INNODB;
-- 创建借书记录表格
CREATE    TABLE  BORROW(
    CNO    INT,
    BNO    INT,
    RDATE  DATETIME,
    FOREIGN KEY (CNO) REFERENCES CARD(CNO),
    FOREIGN KEY (BNO) REFERENCES BOOKS(BNO)
)ENGINE = INNODB;
#################################################################
-- 填充数据
############################################################################################################
INSERT  INTO  CARD(CNO,NAME,CLASS)  VALUES(2019001,'张长武','c01');
INSERT  INTO  CARD(CNO,NAME,CLASS)  VALUES(2019002,'李云龙','c01');
INSERT  INTO  CARD(CNO,NAME,CLASS)  VALUES(2019003,'特朗普','c03');
INSERT  INTO  CARD(CNO,NAME,CLASS)  VALUES(2019004,'霍金','力01');
INSERT  INTO  CARD(CNO,NAME,CLASS)  VALUES(2019005,'钱三强','力01');
INSERT  INTO  CARD(CNO,NAME,CLASS)  VALUES(2019006,'杨振宁','力03');
INSERT  INTO  CARD(CNO,NAME,CLASS)  VALUES(2019007,'华罗庚','m01');
INSERT  INTO  CARD(CNO,NAME,CLASS)  VALUES(2019008,'陈景润','m02');
INSERT  INTO  CARD(CNO,NAME,CLASS)  VALUES(2019009,'菲尔普斯','m03');
#############################################################################################################
INSERT  INTO  BOOKS(BNO,BNAME,AUTHOR,PRICE,QUANTITY)  VALUES(1001,'水浒','施耐庵',45.20,6);
INSERT  INTO  BOOKS(BNO,BNAME,AUTHOR,PRICE,QUANTITY)  VALUES(1002,'计算机网络基础','张仲利',67.80,7);
INSERT  INTO  BOOKS(BNO,BNAME,AUTHOR,PRICE,QUANTITY)  VALUES(1003,'网络安全概述','李丽',89.90,12);
INSERT  INTO  BOOKS(BNO,BNAME,AUTHOR,PRICE,QUANTITY)  VALUES(1004,'计算方法','王毅',23.20,4);
INSERT  INTO  BOOKS(BNO,BNAME,AUTHOR,PRICE,QUANTITY)  VALUES(1005,'计算方法习题册','张磊',21.00,8);
INSERT  INTO  BOOKS(BNO,BNAME,AUTHOR,PRICE,QUANTITY)  VALUES(1006,'组合数学','Peter Harrington',69.00,3);
INSERT  INTO  BOOKS(BNO,BNAME,AUTHOR,PRICE,QUANTITY)  VALUES(1007,'数据库技术技术及应用','鬼手',39.00,1);
INSERT  INTO  BOOKS(BNO,BNAME,AUTHOR,PRICE,QUANTITY)  VALUES(1008,'统计学习方法','李航',30.00,3);
##############################################################################################################
INSERT  INTO  BORROW(CNO,BNO,RDATE)  VALUES(2019005,1005,'2019-06-20');
INSERT  INTO  BORROW(CNO,BNO,RDATE)  VALUES(2019005,1006,'2019-06-29');
INSERT  INTO  BORROW(CNO,BNO,RDATE)  VALUES(2019005,1003,'2019-02-01');
INSERT  INTO  BORROW(CNO,BNO,RDATE)  VALUES(2019005,1002,'2019-07-02');
INSERT  INTO  BORROW(CNO,BNO,RDATE)  VALUES(2019005,1007,'2019-08-02');
INSERT  INTO  BORROW(CNO,BNO,RDATE)  VALUES(2019005,1004,'2019-06-30');
INSERT  INTO  BORROW(CNO,BNO,RDATE)  VALUES(2019009,1004,'2019-06-26');
INSERT  INTO  BORROW(CNO,BNO,RDATE)  VALUES(2019009,1006,'2019-07-23');
INSERT  INTO  BORROW(CNO,BNO,RDATE)  VALUES(2019006,1004,'2019-03-12');
INSERT  INTO  BORROW(CNO,BNO,RDATE)  VALUES(2019001,1001,'2019-10-27');
INSERT  INTO  BORROW(CNO,BNO,RDATE)  VALUES(2019001,1004,'2019-09-10');
INSERT  INTO  BORROW(CNO,BNO,RDATE)  VALUES(2019002,1001,'2019-08-12');
INSERT  INTO  BORROW(CNO,BNO,RDATE)  VALUES(2019002,1006,'2019-05-20');
INSERT  INTO  BORROW(CNO,BNO,RDATE)  VALUES(2019003,1001,'2019-06-20');
###############################################################################################################
SELECT  * FROM  CARD;
SELECT  * FROM  BOOKS;
SELECT  * FROM  BORROW;
###############################################################################################################
-- 一、找出借书超过5本的读者,输出借书卡号、借书人姓名以及所借图书册数
SELECT   C.CNO,NAME,COUNT(BNO)
FROM     BORROW B INNER JOIN CARD C
ON       B.CNO = C.CNO
GROUP BY CNO
HAVING   COUNT(BNO) > 5;
-- 二、查询借阅了“水浒”一书的读者,输出姓名及班级
SELECT  NAME,CLASS
FROM    CARD
WHERE   CNO  IN (SELECT CNO 
                 FROM   BORROW
                 WHERE  BNO IN  (SELECT  BNO
                                 FROM    BOOKS
                                 WHERE   BNAME = '水浒'));
-- 三、查询过期未还的图书,输出借阅者(卡号)、书号及还书日期
-- current_date()返回当前的日期和时间
SELECT  CNO,BNO,RDATE
FROM    BORROW
WHERE   RDATE < current_date();
-- 四、查询书名包括“网络”关键词的图书,输出书号、书名、作者
SELECT  BNO,BNAME,AUTHOR
FROM    BOOKS
WHERE   BNAME LIKE '%网络%';
-- 五、查询现有图书中价格最高的图书、输出书名及作者
SELECT   BNO,AUTHOR,PRICE
FROM     BOOKS
ORDER BY PRICE DESC LIMIT 0,1;
-- 六、查询当前借了“计算方法”但没有借“计算方法习题集”的读者,输出其借书卡号,并按卡号降序排序输出
SELECT   a.CNO
FROM     BORROW a ,BOOKS b
WHERE    b.BNAME = '计算方法' AND  a.BNO = b.BNO
         AND   NOT   EXISTS(
               SELECT  *
               FROM    BORROW  a1 , BOOKS  b1
               WHERE   a1.BNO  =  b1.BNO   AND   b1.BNAME = '计算方法习题集'  AND   a1.CNO = a.CNO);

-- 七、将“c01”班的同学所借图书的还期延长一周
SELECT   b.CNO,BNO,DATE_ADD(RDATE,INTERVAL 7 DAY)
FROM     BORROW b INNER JOIN  CARD c
WHERE    b.CNO = c.CNO  AND  c.CLASS = 'c01';
-- 八、从BOOKS表中删除当前无人借阅的图书记录
DELETE   FROM   BOOKS  WHERE  BNO = (SELECT    c.BNO
                                     FROM     (SELECT  BNO  FROM  BOOKS  WHERE  BNO  NOT  IN (SELECT BNO
                                                                                                 FROM   BORROW)) AS c);
SELECT * FROM  BOOKS;
-- 九、如果经常按书名查阅图书信息,请建立合适的索引
####################报错###########################
CREATE CLUSTERED INDEX IDX_BOOKS_BNAME ON BOOKS(BNAME)
####################报错###########################
-- 十、在BORROW表上建立一个触发器,完成如下功能:如果读者借阅的书名是"数据库技术及应用",就将该读者的借阅记录保存在BORROW_SAVE表中(注ORROW_SAVE表结构同BORROW
####################-报错-###########################
CREATE   TRIGGER   TR_SAVE  ON   BORROW
FOR  INSERT,UPDATE
AS
IF   @ @ROWCOUNT > 0
INSERT  BORROW_SAVE  SELECT i.*
FROM    INSERTED i,BOOKS b
WHERE   i,BNO = b.BNO
AND   b.BNAME = '数据库技术及应用';
####################-报错-###########################
-- 十一、建立一个视图,显示“力01”班学生的借书信息(只要求显示姓名和书名)
INSERT  INTO  BORROW(CNO,BNO,RDATE)  VALUES(2019004,1001,'2019-07-20');
DELETE   FROM  BORROW WHERE  CNO = 2019004;
#######################################################################
SELECT  NAME,BNAME
FROM    CARD c ,BORROW b,BOOKS o
WHERE   c.CNO = b.CNO AND CLASS = '力01' AND  b.BNO = o.BNO;
#######################################################################
-- 十二、查询当前同时借有“计算方法”和“组合数学”两本书的读者,输出其借书卡号,并按卡号升序排序输
SELECT a.CNO
FROM   (SELECT CNO FROM BORROW WHERE BNO IN (SELECT BNO FROM  BOOKS WHERE BNAME = '计算方法')) AS a,
       (SELECT CNO FROM BORROW WHERE BNO IN (SELECT  BNO  FROM BOOKS  WHERE  BNAME = '组合数学'))AS b
WHERE   a.CNO = b.CNO;
-- 十三、假定在建BOOKS表格时没有定义主码,写出为BOOKS表追加定义主码的语句
ALTER   TABLE  BOOKS  ADD  PRIMARY  KEY(BNO);
-- 十四、将NAME最大列宽增加到60个字符(假定原为50个字符)
ALTER  TABLE  CARD  MODIFY NAME VARCHAR(60);
-- 十五、为该表增加1列NAME(系名),可变长度,最大字符20个
ALTER  TABLE  CARD  ADD  系名 VARCHAR(20);
SELECT  * FROM   CARD;

参考资料:
1.《MySQL必知必会》 Ben Forta;
2.sql面试题(学生表_课程表_成绩表_教师表):https://www.cnblogs.com/qixuejia/p/3637735.html
3.常用数学符号的 LaTeX 表示方法:http://mohu.org/info/symbols/symbols.htm

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章