一、重要知识点
第一节 一般数据查询功能扩展
1. SELECT 语句完整语法
sql
SELECT [DISTINCT] [TOP n] select_list
[INTO new_table]
[FROM table_source]
[WHERE search_condition]
[GROUP BY group_by_expression]
[HAVING search_condition]
[ORDER BY order_expression [ASC | DESC]]
[COMPUTE expression]
各子句执行顺序(逻辑顺序):
plain
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
2. 使用 TOP 限制结果集
语法:
sql
TOP n [PERCENT] [WITH TIES]
| 写法 | 含义 |
|---|---|
| TOP n | 返回前 n 行 |
| TOP n PERCENT | 返回前 n% 行 |
| WITH TIES | 包括最后一行取值并列的结果(需配合 ORDER BY 使用) |
示例------查询单价最高的前三种商品(含并列):
sql
SELECT TOP 3 WITH TIES GoodsName, GoodsClassName, SaleUnitPrice
FROM Table_Goods a
JOIN Table_GoodsClass b ON a.GoodsClassID = b.GoodsClassID
ORDER BY SaleUnitPrice DESC
注意:
WITH TIES必须与ORDER BY一起使用,否则语法错误。
3. 使用 CASE 函数
CASE 函数是一种多分支表达式,用于分情况显示不同类型的数据。
两种类型:
① 简单 CASE 函数(将某个表达式与一组值比较)
sql
CASE 表达式
WHEN 值1 THEN 结果1
WHEN 值2 THEN 结果2
...
[ELSE 结果n]
END
② 搜索 CASE 函数(对每个 WHEN 后跟布尔表达式,更灵活)
sql
CASE
WHEN 布尔表达式1 THEN 结果表达式1
WHEN 布尔表达式2 THEN 结果表达式2
...
WHEN 布尔表达式n THEN 结果表达式n
[ELSE 结果表达式n+1]
END
示例------按销售量对商品分类:
sql
SELECT a.GoodsID,
商品销售类别 = CASE
WHEN COUNT(b.GoodsID) > 10 THEN '热门商品'
WHEN COUNT(b.GoodsID) BETWEEN 5 AND 10 THEN '一般商品'
WHEN COUNT(b.GoodsID) BETWEEN 1 AND 4 THEN '难销商品'
ELSE '滞销商品'
END
FROM Table_Goods a
LEFT JOIN Table_SaleBillDetail b ON a.GoodsID = b.GoodsID
GROUP BY a.GoodsID
4. 将查询结果保存到新表
语法:
sql
SELECT 查询列表序列 INTO <新表名>
FROM 数据源
[WHERE ...]
[GROUP BY ...]
临时表与永久表的区别:
| 表名写法 | 类型 | 作用域 |
|---|---|---|
| #表名 | 局部临时表 | 仅当前连接可见,连接断开自动删除 |
| ##表名 | 全局临时表 | 所有连接可见,所有引用连接断开后删除 |
| 表名 | 永久表 | 长期存在于数据库中 |
示例:
sql
-- 将满足条件的顾客存入局部临时表
SELECT * INTO #HD_Customer
FROM Table_Customer
WHERE ...
重点:
INTO子句位于SELECT列表之后、FROM子句之前,顺序不能颠倒。
第二节 查询结果的并、交、差运算
1. 并运算(UNION)
将多个查询结果合并为一个结果集。
语法:
sql
SELECT 语句1
UNION [ALL]
SELECT 语句2
UNION [ALL]
...
UNION 与 UNION ALL 的区别:
| 关键字 | 说明 |
|---|---|
| UNION | 合并结果,自动去除重复行 |
| UNION ALL | 合并结果,保留所有重复行性能更高 |
使用 UNION 的注意事项:
-
各 SELECT 中列数必须相同,且语义相同
-
对应列的数据类型必须隐式兼容 (如
char(20)与varchar(40)) -
合并后结果采用第一个 SELECT 语句的列标题
-
若需排序,
ORDER BY写在最后一个 SELECT 之后,排序依据使用第一个 SELECT 中的列名
2. 交运算(INTERSECT)
返回同时在两个集合中出现的记录(自动去重)。
语法:
sql
SELECT 语句1
INTERSECT
SELECT 语句2
INTERSECT
...
SELECT 语句n
3. 差运算(EXCEPT)
返回第一个集合中有而第二个集合中没有的记录(自动去重)。
语法:
sql
SELECT 语句1
EXCEPT
SELECT 语句2
EXCEPT
...
SELECT 语句n
三种集合运算对比:
plain
集合A = {1, 2, 3, 4} 集合B = {3, 4, 5, 6}
A UNION B → {1, 2, 3, 4, 5, 6} 并集(去重)
A INTERSECT B → {3, 4} 交集
A EXCEPT B → {1, 2} 差集(A中有B中没有)
第三节 相关子查询
子查询是包含在另一条 SELECT 语句里的 SELECT 语句。
-
外层的 SELECT 叫外层查询
-
内层的 SELECT 叫内层查询(子查询)
-
子查询总是写在圆括号中
包含子查询的三种主要格式:
sql
-- 格式1:集合成员测试(IN)
WHERE expression [NOT] IN (subquery)
-- 格式2:比较测试(ANY / ALL)
WHERE expression 比较运算符 [ANY | ALL] (subquery)
-- 格式3:存在性测试(EXISTS)
WHERE [NOT] EXISTS (subquery)
1. 使用子查询进行集合成员测试(IN)
示例------查询与"王晓"住在同一地址的顾客:
sql
SELECT Cname, Address
FROM Table_Customer
WHERE Address IN (
SELECT Address FROM Table_Customer WHERE Cname = '王晓'
)
AND Cname != '王晓'
2. 使用子查询进行比较测试(ANY / ALL)
示例------查询单价最高的商品名称和单价:
sql
SELECT GoodsName, SaleUnitPrice
FROM Table_Goods
WHERE SaleUnitPrice = (
SELECT MAX(SaleUnitPrice) FROM Table_Goods
)
ANY 与 ALL 的含义:
| 写法 | 含义 |
|---|---|
| > ANY (子查询) | 大于子查询结果中任意一个值(即大于最小值) |
| > ALL (子查询) | 大于子查询结果中所有值(即大于最大值) |
| "=ANY (子查询) | 等于子查询结果中任意一个值,等价于 IN |
| != ALL (子查询) | 不等于子查询结果中所有值,等价于 NOT IN |
3. 使用子查询进行存在性测试(EXISTS)
EXISTS 子查询如果返回至少一行,则结果为 TRUE;否则为 FALSE。
示例------查询购买了单价高于 2000 元商品的顾客会员卡号:
sql
SELECT DISTINCT CardID
FROM Table_SaleBill
WHERE EXISTS (
SELECT * FROM Table_SaleBillDetail
WHERE SaleBillID = Table_SaleBill.SaleBillID
AND UnitPrice > 2000
)
相关子查询的执行原理: EXISTS 子查询引用了外层查询的表(
Table_SaleBill),外层每取一行,内层子查询就执行一次,这种子查询称为相关子查询。
第四节 其他形式的子查询
1. 替代表达式的子查询
在 SELECT 的选择列表中嵌入一个只返回**标量值(单行单列)**的子查询。
示例:
sql
SELECT Cname, Address,
(SELECT COUNT(*) FROM Table_SaleBill b
WHERE b.CardID = a.CardID) AS TotalTimes
FROM Table_Customer a
WHERE CustomerID = 'C001'
2. 派生表(内联视图)
将子查询作为一个临时表处理,产生的新表称为"派生表",必须为其指定别名。
示例------查询至少买了 G001 和 G002 两种商品的顾客:
sql
SELECT CustomerID, CName
FROM (
SELECT * FROM Table_SaleBill a
JOIN Table_SaleBillDetail b ON a.SaleBillID = b.SaleBillID
WHERE GoodsID = 'G001'
) AS T1
JOIN (
SELECT * FROM Table_SaleBill a
JOIN Table_SaleBillDetail b ON a.SaleBillID = b.SaleBillID
WHERE GoodsID = 'G002'
) AS T2 ON T1.CardID = T2.CardID
JOIN Table_Customer c ON c.CardID = T1.CardID
第五节 其他一些查询功能
1. 开窗函数(Window Function)
基本概念:
-
在 SQL Server 中,一组行被称为一个窗口
-
开窗函数与聚合函数相似,但聚合函数每组只返回一个值 ,开窗函数可以为每组返回多个值
-
在聚合函数后增加
OVER关键字,即成为开窗函数
调用格式:
sql
函数名(列) OVER (选项)
① 与聚合函数结合使用(PARTITION BY 分组)
sql
-- 查询每门课程信息,同时显示该学期课程的汇总统计
SELECT Cno, CName, Semester, Credit,
SUM(Credit) OVER (PARTITION BY Semester) AS 'Total',
AVG(Credit) OVER (PARTITION BY Semester) AS 'Avg',
MIN(Credit) OVER (PARTITION BY Semester) AS 'Min',
MAX(Credit) OVER (PARTITION BY Semester) AS 'Max'
FROM Course
关键区别: 普通
GROUP BY会折叠行(每组一行),而OVER(PARTITION BY ...)不折叠行,每行仍然保留,同时附加分组的聚合结果。
② 与排名函数结合使用
| 排名函数 | 说明 |
|---|---|
| RANK() | 排名不连续(并列第1后直接跳到第3) |
| DENSE_RANK() | 排名连续(并列第1后仍为第2) |
| ROW_NUMBER() | 返回每个分区内的序列号,从1开始,无并列 |
| NTILE(n) | 将行划分到 n 个组中,返回该行所属组的编号 |
sql
-- 对每个订单内的商品按购买数量排名
SELECT OrderID, ProductID, OrderQty,
RANK() OVER (PARTITION BY OrderID ORDER BY OrderQty DESC) AS RANK
FROM OrderDetail
ORDER BY OrderID
注意: 排名函数具有不确定性 ,排名从 1 开始,不一定是连续整数(
RANK函数)。
2. 公用表表达式(CTE)
CTE(Common Table Expression):将查询结果集指定一个临时名字,这些命名的结果集就是公用表表达式,可以在后续查询中直接引用。
语法格式:
sql
WITH <CTE名称> [(列名1, 列名2, ...)]
AS
(
SELECT 语句
)
-- 紧接着使用 CTE
SELECT ... FROM <CTE名称> ...
示例------统计每个会员购买商品的总次数:
sql
-- 定义 CTE
WITH BuyCount (CardID, Counts) AS (
SELECT CardID, COUNT(*)
FROM Table_SaleBill
GROUP BY CardID
)
-- 使用 CTE
SELECT CardID, Counts
FROM BuyCount
ORDER BY Counts
CTE 与派生表的区别:
派生表:嵌套在 FROM 子句中,只能使用一次
CTE :定义在查询之前,在后续查询中可多次引用 ,且支持递归查询
二、重点题型
1. 集合运算的语义辨析
plain
查询两个集合的公共元素(交集):
✅ INTERSECT
✅ WHERE ... IN (SELECT ...)(子查询实现)
查询A有B没有的元素(差集):
✅ EXCEPT
✅ WHERE ... NOT IN (SELECT ...)
查询全部不重复元素(并集):
✅ UNION(去重)
✅ UNION ALL(不去重,保留所有行)
2. INTO 子句的位置
sql
-- ✅ 正确:INTO 在 SELECT 列表后、FROM 之前
SELECT 课程号, COUNT(*) 选课人数
INTO 选课情况表
FROM 选课表
GROUP BY 课程号
-- ❌ 错误:INTO 不能放在 FROM 后面
SELECT 课程号, COUNT(*) 选课人数
FROM 选课表 INTO 选课情况表 -- 语法错误
GROUP BY 课程号
3. LEFT JOIN 查询"没有"关系的行
sql
-- 查询没有选课的学生:用 LEFT JOIN 后筛选右表为 NULL 的行
SELECT 姓名, 所在系
FROM 学生表 a
LEFT JOIN 选课表 b ON a.学号 = b.学号
WHERE b.学号 IS NULL -- ✅ 筛选右表为 NULL,即没有选课的学生
-- ❌ 错误写法:WHERE a.学号 IS NULL(主表主键不可能为 NULL)
4. 四种排名函数对比
plain
数据:分数 100, 100, 90, 80
RANK() → 1, 1, 3, 4 (跳号)
DENSE_RANK() → 1, 1, 2, 3 (不跳号)
ROW_NUMBER() → 1, 2, 3, 4 (强制唯一,无并列)
NTILE(2) → 1, 1, 2, 2 (均分为2组)
5. 临时表作用域速记
plain
#表名 → 局部临时表 → 仅创建该表的【当前连接】可访问
##表名 → 全局临时表 → 所有连接均可访问
表名 → 永久表 → 长期存在,所有连接均可访问
三、易错点
易错点1:UNION 与 UNION ALL 混淆
-
❌ UNION 保留所有重复行
-
✅ UNION 去除 重复行;UNION ALL 保留所有重复行,性能更优
易错点2:EXCEPT 与 INTERSECT 的方向
-
❌
A EXCEPT B返回两个集合都没有的数据 -
✅
A EXCEPT B返回A中有、B中没有 的数据;A INTERSECT B返回两者共有的数据
易错点3:INTO 子句位置错误
-
❌
SELECT ... FROM ... INTO 新表名 GROUP BY ... -
✅
SELECT ... INTO 新表名 FROM ... GROUP BY ...(INTO 紧跟在 SELECT 列表之后)
易错点4:LEFT JOIN 过滤空值方向
-
❌
WHERE a.学号 IS NULL(主表字段不会为 NULL) -
✅
WHERE b.学号 IS NULL(右表字段为 NULL,说明左表记录在右表中无匹配)
易错点5:#临时表的作用域
-
❌
#Temp表在所有连接中均可访问 -
✅
#Temp是局部临时表 ,只有创建它的连接才可以访问
易错点6:RANK 与 DENSE_RANK 区别
-
❌ RANK() 排名总是连续整数
-
✅ RANK() 有并列时会跳号 (如 1,1,3);DENSE_RANK() 不跳号(如 1,1,2)
易错点7:EXISTS 子查询的返回值
-
❌ EXISTS 子查询返回的是具体数据行
-
✅ EXISTS 只关心子查询是否返回至少一行 ,结果为 TRUE 或 FALSE,与 SELECT 列无关(通常写
SELECT *)
四、复习题目
- 【填空题】 设某数据库中有旅客表A(旅客编号,城市)和旅客表B(旅客编号,城市),请补全查询语句,使其能查询所有旅客所在的全部不重复城市。
sql
SELECT 城市 FROM 旅客表A
( )
SELECT 城市 FROM 旅客表B
- 【选择题】 设有购买表(顾客号,商品号,购买时间),现要查询顾客A与顾客B购买的相同商品。下列语句中能够实现该查询要求的是( )。
Ⅰ. WHERE 顾客号='A' AND 商品号 IN (SELECT 商品号 FROM 购买表 WHERE 顾客号='B')
Ⅱ. 顾客号='A' EXCEPT 顾客号='B'(差集,A有B没有)
Ⅲ. 顾客号='A' INTERSECT 顾客号='B'(交集,共同购买)
Ⅳ. 顾客号='A' UNION 顾客号='B'(并集,全部商品)
A. 仅Ⅰ和Ⅱ B. 仅Ⅰ和Ⅲ C. 仅Ⅰ和Ⅳ D. 仅Ⅲ
- 【选择题】 设有选课表(学号,课程号,成绩),现要统计每门课程的选课人数,并将结果保存到新表。下列语句中正确的是( )。
A. SELECT 课程号, COUNT(*) 选课人数 FROM 选课表 INTO 选课情况表 GROUP BY 课程号
B. SELECT 课程号, COUNT(*) 选课人数 INTO 选课情况表 FROM 选课表 GROUP BY 课程号
C. SELECT 课程号, COUNT(*) FROM 选课表 INTO 选课情况表(课程号,选课人数)GROUP BY 课程号
D. SELECT 课程号, COUNT(*) INTO 选课情况表(课程号,选课人数)FROM 选课表 GROUP BY 课程号
- 【选择题】 设有学生表(学号,姓名,所在系)和选课表(学号,课程号,成绩)。现要查询没选课的学生姓名和所在系。下列语句中能够实现的是( )。
A. LEFT JOIN ... WHERE a.学号 IS NULL
B. LEFT JOIN ... WHERE b.学号 IS NULL
C. RIGHT JOIN ... WHERE a.学号 IS NULL
D. RIGHT JOIN ... WHERE b.学号 IS NULL
- 【选择题】 设在 SQL Server 2008 中,用户 U1 创建了
#Temp表。下列关于#Temp表的说法中,正确的是( )。
A. 在所有用户 U1 发起的连接中,都可以查询 #Temp 表数据
B. 只有在创建 #Temp 表的连接中才可以查询 #Temp 表数据
C. 在创建 #Temp 表的连接未断开时,DB1 数据库的所有用户都可以查询
D. 在创建 #Temp 表的连接断开时,所有用户仍可以查询
- 【综合查询题】 设图书馆系统有三个表:BORROWER(借书证号,姓名,系名)、LOANS(借书证号,图书登记号,借书日期)、BOOKS(图书登记号,索书号,书名)。
(1)检索至少借了 5 本书的同学的借书证号、姓名、系名和借书数。
sql
-- 先是在LOANS表格中对于大于等于五本书的同学
-- 后面再将两个表连接起来
SELECT
a.借书证号,
BORROWER.姓名,
BORROWER.系名,
a.借书数
FROM (
SELECT
借书证号,
COUNT(图书登记号) AS 借书数
FROM LOANS
WHERE 借书数>=5
GROUP BY 借书证号) as a
left join BORROWER
ON a.借书证号=BORROWER.借书证号;
(2)检索借书和王丽同学所借图书中的任意一本相同的学生姓名、系名、书名和借书日期。
sql
SELECT