计算机三级备考(七)——高级数据库查询

一、重要知识点

第一节 一般数据查询功能扩展

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 *


四、复习题目

  1. 【填空题】 设某数据库中有旅客表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
    
相关推荐
亚历克斯神2 小时前
Flutter for OpenHarmony: Flutter 三方库 mongo_dart 助力鸿蒙应用直连 NoSQL 数据库构建高效的数据流转系统(纯 Dart 驱动方案)
android·数据库·flutter·华为·nosql·harmonyos
加农炮手Jinx2 小时前
Flutter for OpenHarmony:postgres 直连 PostgreSQL 数据库,实现 Dart 原生的高效读写(数据库驱动) 深度解析与鸿蒙适配指南
网络·数据库·flutter·华为·postgresql·harmonyos·鸿蒙
全栈小52 小时前
【数据库】Sql Server 安装教程,一键到底,沉浸式下载安装MSSQL和SSMS
数据库·sqlserver
顶点多余2 小时前
mysql---索引特征 (重要)
数据库·mysql
数据知道2 小时前
MongoDB灾难恢复计划:RTO/RPO目标下的应急响应完整方案
数据库·mongodb·wpf
匀泪2 小时前
云原生(Redis配置)
数据库·redis·缓存
shuair2 小时前
redis执行lua脚本
数据库·redis·lua
Andytoms2 小时前
错误信息:请在mysql配置文件修sql-mode或sql_mode为NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
数据库·sql·mysql
ChaITSimpleLove2 小时前
PostgreSQL(简称 pgsql)数据库的启动与关闭
数据库·postgresql·start·stop·reload·restart