第6章 高级数据查询

6.1般数据査询功能扩展 
6.1.1使用TOP限制结果集 
当使用 SELECT 语句进行查询时,使用 TOP 谓词来限制输出的结果。使用 TOP 谓词的语法格式为: 
TOP n [percent][WITHTIES]——其中,n 为非负整数。 
①TOP n 取查询结果的前 n 行数据; 
②TOP n percent:取查询结果的前 n%行数据; 
③WITHTIES:表示包括最后一行取值并列的结果。 
TOP 谓词写在 SELECT 单词的后边(如果有 DISTINCT 的话,则 TOP 写在 DISTINCT 的后边)、查询列表的前边。 
【例 1】查询单价最高的前三种商品的商品名、商品类别和单价,包括并列的情况。 

SELECT TOP3 WITHTIES GoodsName,GoodsClassName,SaleUnitPrice 
FROM Table_Goods a JOINTable_GoodsClass b  
ON a.GoodsClassID=b.GoodsClassID 
ORDERBY SaleUnitPrice DESC 

注意:在使用 TOP 谓词时,应该与 ORDERBY 子句一起使用,这样的前几名才有意义。当使用 WITHTIES选项时,则要求必须使用 ORDERBY 子句。 
6.1.2使用CASE函数 
可以在查询语句中使用CASE函数,以达到分情况显示不同类型的数据的目的。CASE函数分为简单CASE函数和搜索 CASE 函数两种类型。 
1.简单 CASE 函数 
简单 CASE 函数的语法格式为: 

CASE 测试表达式 
WHEN 简单表达式 1 THEN 结果表达式 1 
WHEN 简单表达式 2 THEN 结果表达式 2 
WHEN 简单表达式 n THEN 结果表达式 n 
[ELSE 结果表达式] 
END 

简单 CASE 函数的执行过程为: 
●计算测试表达式,然后按从上到下的顺序对每个 WHEN 子句的简单表达式进行计算。 
●如果某个简单表达式的值与测试表达式的值相匹配,则返回与第一个取值为 True 的 THEN 对应的结果表达式的值。 
●如果所有的简单表达式的值都不与测试表达式的值相匹配,则当指定了 ELSE 子句时,将返回 ELSE子句中指定的结果表达式的值;若没有指定 ELSE 子句,则返回 NULL 值。 
2.搜索 CASE 函数 
搜索 CASE 函数的语法格式为: 

CASE 
WHEN 布尔表达式 1 THEN 结果表达式 1 
WHEN 布尔表达式 2 THEN 结果表达式 2 
WHEN 布尔表达式 n THEN 结果表达式 n 
[ELSE 结果表达式 n+1] 
END 

搜索 CASE 函数的各个 WHEN 子句的布尔表达式可以使用比较运算符,也可以使用逻辑运搜索 CASE 函数的执行过程同简单 CASE 函数。 
                                  
【例 2】设在 SQL Server 2008 某数据库中有表 SC(Sno,Cno,Grade),其中 Grade 列的类型为 int。
若在查询成绩时,希望将成绩按“优”、“良”、“中”、“及格”和“不及格”形式显示。 

Case 
When Grade between 90 and 100 THEN ‘优’ 
When Grade between 80 and 89 THEN ‘良’ 
When Grade between 70 and 79 THEN ‘中’ 
When Grade between 60 and 69 THEN ‘及格’ 
Else ‘不及格’ 
End 

6.1.3将查询结果保存到新表中 
当使用 SELECT 语句查询数据时,产生的结果是保存在内存中的。如果希望将查询结果永久保存下来,比如保存在一个表中,则可以通过在 SELECT 语句中使用 INTO 子句实现。 
包含 INTO 子句的 SELECT 语句的语法格式为: 
SELECT 查询列表序列 INTO<新表名> 
FROM 数据源 
--其他行过滤、分组等子句 
其中“新表名”是要存放查询结果的表名。这个语句将查询的结果保存在“新表名”所指定的表中。
实际上这个语句包含如下三个功能: 
①根据查询语句列出的列以及其类型创建一个新表; 
②执行查询语句; 
③将查询的结果插人到新表中。 
用 INTO 子句创建的新表可以是永久表(在执行这个语句时所使用的数据库中被物理的创建,并且是存储在磁盘上的表),也可以是临时表(在 tempdb 数据库中创建此表,其生存期是有限的)。临时表又根据其使用范围分为局部临时表和全局临时表两种。 
●局部临时表:通过在表名前加一个“#”来标识.局部临时表的生存期与创建此局部临时表的用户的
连接生存期相同,它只能在创建此局部临时表的当前连接中使用。 
●全局临时表通过在表名前加两个“#”来标识。全局临时表的生存期与创建全局临时表的用户的连
接生存期相同,并且在生存期内可以被所有的连接使用。 
可以对局部临时表和全局临时表中的数据进行查询,其方法同永久表一样。 
【例 3】查询购买了“家用电器”类商品的顾客姓名、所在地址、购买日期、购买数量和购买单叶,
并将查询结果保存到一个新的永久表中,新表名为“家用电器销售表”,新表中各列名用中文。 

SELECT CName 顾客名,Address 地址,SaleDate 购买日期,Quantity 数量,UnitPrice 购买价格 INTO家用电器销售表 
FROM Table_Customer a JOINTable—SaleBill b ON a.CardID=b.CardID 
JOIN Table.SaleBillDetail c ON c.SaleBilllD=b.SaleBilllD 
JOIN Table_Goods d ON d.GoodsID=c.GoodsID 
JOIN Table_GoodsClass e ON e.GoodsClassID=d.GoodsClassID 
WHEREGoodsClassName=‘家用电器’ 

6.2査询结果的并、交、差运算 
数据查询的结果是一个集合,SQL 语言支持对查询的结果再进行并、交、差运算。 
6.2.1并运算 
并运算可将两个或多个查询语句的结果集合并为一个结果集,这个运算可以使用 UNION 运直符实现。
UNION 是一个特殊的运算符,通过它可以实现让两个或更多的查询产生单一的结果集。 
UNION 操作与 JOIN 连接操作不同,UNION 更像是将一个查询结果追加到另一个查询结果中(虽然各数据库管理系统对 UNION 操作略有不同,但基本思想是一样的)。JOIN 操作是水平过合并数据(添加更多的列),而 UNION 是垂直地合并数据(添加更多的行)。 
使用 UNION 谓词的语法格式为: 

SELECT 语句 1 
UNION[ALL] 
SELECT 语句 2 
UNION[ALL] 
…… 
SELECT 语句 

其中:ALL 表示在结果集中包含所有查询语句产生的全部记录,包括重复的记录。如果没有指定 ALL,则系统默认是删除合并后结果集中的重复记录。 
使用 UNION 时,需要注意以下几点: 

●所有要进行 UNION 操作的查询,其 SELECT 列表中列的个数必须相同,而且对应列的语义应该相同。 
●各查询语句中每个列的数据类型必须与其他查询中对应列的数据类型是隐式兼容的,即只要它们能进行隐式转换即可。 
●合并后的结果采用第一个 SELECT 语句的列标题。 
●如果要对查询的结果进行排序,则 ORDERBY 子句应该写在最后一个查询语句之后,且排序的依据列应该是第一个查询语句中出现的列名。 
【例 4】利用集合并运算查询姓李和姓王的客户的姓名和电话,并将查询结果按电话升序排序。 

SELECT Name, Tel FROM Table_ Customer  
WHERE Name LIKE ‘李%’ 
UNION 
SELECT Name, Tel FROM Table_ Customer  
WHERE Name LIKE‘王%’ 
ORDER BY Tel ASC 

6.2.2交运算 
交运算将返回同时在两个集合中出现的记录,即返回两个查询结果集中各个列的值均相同的记录,并用这些记录构成交运算的结果。 
实现交运算的 SQL 运算符为 INTERSECT,其语法格式为: 

SELECT 语句 1 
INTERSECT 
SELECT 语句 2 
INTERSECT 
… 
SELECT 语句 n 

INTERSECT 运算对各查询语句的要求同 UNION 运算。 
【例 5】设有购买表(顾客号,商品号,购买时间)。现要查询顾客 A 与顾客 B 购买的相同商品。 
 
6.2.3差运算 
差运算将返回在第一个集合中有但第二个集合中没有的数据。实现差运算的 SQL 运算符 EXCEPT,其语法格式为: 

SELECT 语句 1 
EXCEPT 
SELECT 语句 2 
EXCEPT 
… 
SELECT 语句 n 

EXCEPT 运算对各查询语句的要求同 UNION 运算。 
【例 6】设有购买表(顾客号,商品号,购买时间)。现要查询 2019 年顾客 A 购买了但顾客 B 没有购买的商品。 
 
6.3相关子査询 
如果一个 SELECT 语习是嵌套在一个 SELECT、INSERT、WDATE 或 DELETE 语句中,则称为子查询或内层查询。子查询语句中也必须包含 SELECT 子句和 FROM 子句,并可以根据需要选择 WHERE 子句、GROUPBY 子句和 HAVING 子句。包含子查询的语句通常采用以下格式中的一种: 
●WHERE 表达式[NOT]IN(子查询) 
●WHERE 表达式比较运算符(子查询) 
●WHERE[NOT]EXISTS(子查询) 

通常,子查询一般用在外层查询的 WHERE 子句或 HAVING 子句中,与比较运算符或逻辑运算符一起构成查询条件。对于返回结果为单值的子查询语句,可以出现在任何允许使用表达式的地方。 
1.使用子查询进行基于集合的测试 
使用子查询进行基于集合的测试时,通过运算符 IN 和 NOT IN,将一个表达式的值与子查询返回的结果集进行比较。其形式为: 
WHERE 表达式 [NOT] IN(子查询) 
这种形式的子查询的语句是分步骤实现的,即先执行子查询,然后在子查询的结果基础上再执行外层查询。子查询返回的结果实际上就是一个集合,外层查询就是在这个集合上使用 IN 运算符进行比较。 
【例 7】查询与“王晓”在同一个地址(区相同)的顾客的姓名和所在地址。前边用自连接的形式实
现了这个查询,现在用子査询实现。 

SELECT Cname,Address FROM Table_Customer 
WHERE Address 
IN( 
SELECT Address FROM Table_Customer WHERE Cname=’王晓,) 
AND Cname!=‘王晓’ 

2.使用子查询进行比较测试 
使用子查询进行比较测试时,通过比较运算符(=、<>、<、>、<=、<=),将一个表达式的值与子查询返回的单值进行比较。如果比较运算的结果为 True,则比较测试返回 True;如果比较运算的结果为 False,则比较测试返回 False。 
使用子查询进行比较测试的形式为: 
WHERE 表达式比较运算符(子查询) 
使用子查询进行的比较测试要求子查询语句必须是返回单值的查询语句。 
【例 8】查询单价最高的商品名称和单价。 

SELECT GoodsName,SaleUnitPrice FROM Table_Goods a 
WHERE SaleUnitPrice =(SELECT MAX(SaleUnitPrice)FROM Table_Goods)

 注意:由于聚合函数不能出现在 WHERE 子句中,因此,当一个列的值与一个聚合函数的结果进行比较时,必须用子查询先得到聚合函数的结果,然后在此结果基础之上再执行外层查询的比较。子查询的查询条件不依赖于外层查询,称这样的子查询为不相关子查询或嵌套子查询。 
3.使用子查询进行存在性测试 
使用子查询进行存在性测试时,通常使用 EXISTS 谓词,其形式为: 
WHERE[NOT]EXISTS(子查询) 
带 EXISTS 谓词的子查询不返回查询的结果,只产生逻辑真值和逻辑假值。 
EXISTS 的含义是:当子查询中有满足条件的数据时,EXISTS 返回真值,否则返回假值。 
NOT EXISTS 的含义是:当子查询中有满足条件的数据时,NOT EXISTS 返回假值,当子查询中不存在满足条件的数据时,NOT EXISTS 返回真值。 
【例 9】查询购买了单价高于 2000 元商品的顾客的会员卡号。 

SELECT DISTINCT CardID FROM Table_SaleBill 
WHERE EXISTS( 
SELECT * FROM Table.SaleBiUDetail 
WHERE SaleBilllD=Table_SaleBill.SaleBilllD 
AND UnitPrice>2000) 

注 1:带 EXISTS 谓词的查询是先执行外层查询,然后再执行内层查询。由外层查询的值决定内层查询的结果;内层查询的执行次数由外层查询的结果数决定。 
注 2:由于带 EXISTS 的子查询只返回真或假值,因此在子查询里不必指定查询列表。所以在有 EXISTS
的子查询中,子查询中的目标列通常都用“*”。 
带 EXISTS 的子查询由于在子查询中要涉及与外层表数据的关联,因此经常将这种形式的子查询称为相关子查询。 
6.4其他形式的子査询 
1.替代表达式的子查询 
替代表达式的子查询是指在 SELECT 语句的选择列表中嵌人一个只返回一个标量值的 SELECT 语句,这个查询语句通常都是通过一个聚合函数来返回一个单值。 
【例 10】查询 G001 顾客的姓名、地址以及该顾客购买商品的总次数。 

SELECTC Name,Address 
(SELECT COUNT(*)FROM Table_SaleBilla 
JOIN Table_Customer b ON a.CardID=b.CardID 
WHERE CustomerlD=‘G001')AS TotalTimes 

FROM Table_Customer 
WHERE CustomerlD=‘G001’ 

2.派生表 
派生表(有时也称为内联视图)是将子查询作为一个表来处理,这个由子查询产生的新表就被称为“派生表”,这很类似于临时表。可以在查询语句中用派生表来建立与其他表的连接关系,在生成派生表后,在查询语句中对派生表的操作与普通表一样。 
使用派生表可以简化查询,从而避免使用临时表,而且相比手动生成临时表的方法性能更优越。 
派生表与其他表一样出现在查询语句的 FROM 子句中。例如: 

SELECT*FROM(SELECT * FROMT 1)AS temp 

这里的 temp 就是派生表。 
【例 11】查询至少买了 G001 和 G002 两种商品的顾客号和顾客名。 
分析:可以将买了 G001 商品的顾客保存在一个派生表中,将买了 G002 商品的顾客保存在另一个派生表中,然后在这两个表中找出顾客号相同的顾客,即查找同时在两个表中出现的顾客,就是至少买了 G001和 G002 两种商品的顾客。具体代码如下: 

SELECTCustomerlD,CName 
FROM(SELECT*FROM Table_SaleBilla 
JOIN Table_SaleBillDetailb ON a.SaleBilllD=b.SaleBilllD 
WHERE GoodsID=‘G001’)AST1 
JOIN(SELECT*FROM Table_SaleBilla 
JOINTable_SaleBillDetailb ON a.SaleBilllD=b.SaleBilllD 
WHERE GoodsID='G002')AS T2ONTl.CardID=T2.CardID 
JOINTable 一 CustomercONc.CardID=Tl.CardID 

6.5其他一些査询功能 
除了前面介绍的查询语句外,SQLServer 还提供了一些实现其他功能的查询语句。本节介绍其中的开窗函数和公共表表达式。 
6.5.1开窗函数 
SQL Server 提供了两种开窗函数:排名开窗函数和聚合开窗函数。 
1.将 OVER 子句与聚合函数结合使用 
OVER 子句用于确定在应用关联的开窗函数之前对行集的分区和排序。 
将 OVER 子句与聚合函数结合使用的语法格式为: 

<OVER_CLAUSE>::= 
OVER([PARTITIONBYvalue_expression,…[n]) 

其中各参数说明如下: 
●PARTITIONBY:将结果集划分为多个分区。开窗函数分别应用于每个分区,并为每个分区计算函数值。 
●value_expression:指定对行集进行分区所依据的列,该列必须是在 FROM 子句中生成的列,而且不能引用选择列表中的表达式或别名。value_expression 可以是列表达式、替代表达式的子查询、标量函数或用户定义的变量。 
可以在单个查询中使用多个开窗函数,每个函数的 OVER 子句在分区和排序上可以不同。 
【例 12】设有订单明细表 OrderDetail,结构如下: 

CREATE TABLE OrderDetail( 
OrderID int NOT NULL 
ProductID int NOT NULL 
OrderQty smallint NOT NULL) 

现要查询订单号、产品号、订购数量,每个订单的总订购数量以及每个产品的订购数量占该订单总订购数量的百分比,百分比保留到小数后 2 位。 
2.将 OVER 子句与排名函数一起使用 
排名函数为分区中的每一行返回一个排名值。根据所用函数的不同,某些行可能与其他行具有相同的排名值。排名函数具有不确定性。 
SQL Server 提供了四个名函数:RANK、DENSE_RANK、NTILE 和 R0W_NUMBER。下面分别介绍这些排名函数。 
(1)RANK()函数。 
使用 RANK()函数的语法格式为: 

RANK()0VER([<partition_by_clause>,...[/i]]  
<order_by_clause>) 

其中各参数含义如下: 
●<partition_by_clause>:将 FROM 子句生成的结果集划分成排名函数适用的分区。 

●<order_by_clause>:指定应用于分区中的行时所基于的排序依据列。 
RANK()函数返回结果集中每行数据在每个分区内的排名。每个分区内行的排名从 1 开始。如果排序时有值相同的行,则这些值相同的行具有相同的排名。RANK()函数并不一定返回连续整数。 
【例 13】本示例使用 OrderDetail 表,查询订单号、产品号、订购数量以及每个产品在每个订单中
的订购数量排名。 
分析:该查询需要用订单号进行分区,用订购数量作为排名依据列。 

SELECT OrderlD,ProductID,OrderQty, 
RANK()OVER 
(PARTITION BY OrderlD ORDER BY OrderQty DESC)AS RANK 
FROM OrderDetail ORDER BY OrderlD 

(2)DENSE_RANK()函数。 
DENSE_RANK()函数与 RANK()函数的作用基本一样,使用方法也一样,唯一的区别是 DENSE_RANK()函数的排名中间没有任何间断,即该函数返回的是一个连续的整数值。 
【例 14】将例 13 的查询改为用 DENSE_RANK()函数实现。 

SELECT OrderlD,ProductID,OrderQty, 
DENSE_RANK()OVER 
(PARTITION BY OrderlD ORDER BY OrderQty DESC)AS DENSE_RANK 
FROM OrderDetail ORDER BY OrderlD 

(3)NTILE()。 
NTILE()函数的作用是将有序分区中的行划分到指定数目的组中,每个组有一个编号,编号从 1 开始。对于每一行,NTILEO 函数将返回此行所属的组的编号。 
NTILE()函数的语法格式如下: 

NTILE(integer_expression) 
OVER([<partition_by_clause>]<order_by_clause>) 

各参数含义同 RANK()函数。 
【例 15】本示例使用 OrderDetail 表。将该表数据按订购数量降序排序,并将该表数据划分到 4 个组中。 
分析:该查询只需将全部数据划分为 4 组,因此不需要进行分区,在 OVER 子句中只需对订购数量进行降序排序即可。 

SELECT OrderlD,ProductID,OrderQty, 
NTILE(4)OVER(ORDER BY OrderQty DESC)AS FourGroups 
FROM Order Detail 

(4)ROW_NUMBER()函数。 
ROW_NUMBER()函数返回结果集中每个分区内行的序列号,每个分区的第一行从 1 开始。 
ROW_NUMBER()函数的语法格式如下: 
ROW_NUMBER() 
OVER([<partition—by—clause>]<order_by_clause>) 
各参数含义同 RANK()函数。 
【例 16】查询“电冰箱”类商品的商品名、销售单价以及该商品在该类商品中的价格排名。 

SELECT GoodsName,SaleUnitPrice, 
ROW_NUMBER()OVER(ORDER BY SaleUnitPrice DESC)AS 'Number' 
FROM Table.GoodsClass C JOIN Table.Goods G 
ON G.GoodsClassID=G.GoodsGlassID 
WHERE GoodsClassName=‘电冰箱’ 

6.5.2公用表表达式 
公用表表达式(CTE)是 SQL Server2005 版本之后引入的一个特性。将査询语句产生的结果集指定一个临时命名的名字,这些命名的结果集就称为公用表表达式。命名后的公用表表达式后就可以在 SELECT、INSERT、UPDATE、DELETE 等语句中被多次引用。公用表表达式还可以包括对自身的引用,这种表达式称为递归公用表表达式。 
使用公用表表达式有如下一些好处: 
⚫  可以定义递归公用表表达式。 
⚫  使数据操作代码更加清晰简洁。 
⚫  GROUPBY 子句可以直接作用在子查询所得的标量列上。 
⚫  可以在一个语句中多次引用公用表表达式。 
公用表表达式的语法格式如下: 

WITH<common_table_expression>[,……n] 

<common_table_expression>::= 
expression_name[(column_name[,……n])] 
AS 
(SELECT 语句) 

各参数说明如下: 
●expression_name:公用表表达式的标识符。expression_name 必须与在同一
WITH<common_table_expression>子句中定义的任何其他公用表表达式的名称不同,但该名可以与基本表或视图名相同。在查询中对 expression_name 的任何引用都会使用公用表表达式。 
●column_name:在公用表表达式中指定列名。在一个 CTE 定义中不允许出现重名的列名。 
●SELECT 语句:指定一个用其结果集填充到公用表表达式的 SELECT 语句。 
【例 17】定义一个统计每个会员的购买商品总次数的 CTE,并利用该 CTE 查询会员卡号和购买商品总次数。 
 

WITH BuyCount(CardID,Counts)AS( 
SELECT CardID,COUNT(*)FROM Table_SaleBill 
GROUP BY CardID) 
SELECT CardID,Counts FROM BuyCount 
ORDER BY Counts 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天道工作室

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值