05 技术内幕 T-SQL 查询读书笔记(第四章)
第四章
子查询:在外部查询内嵌套的内部查询(按照期望值的数量分为,标量子查询 scalar subqueries,多值子查询multivalued subqueries)(按照子查询对外部查询的依赖性分为独立子查询self-contained subqueries和相关子查询 correlated subqueries)
应用一:关系分区问题,使用group by和distinct count 来解决关系分区问题
Eg:NorthWind返回每个美国员工至少为其处理过一个订单的所有客户
独立子查询,逻辑上,可以只为整个外部查询计算一次。物理上,查询优化器会考虑不同的方法来完成相同的任务。
selectCustomerID
fromdbo.Orders
whereEmployeeIDin
(selectEmployeeIDfromdbo.EmployeeswhereCountry=N'USA')
groupbyCustomerID
havingCOUNT(distinctEmployeeID)=
(selectCOUNT(*)fromdbo.EmployeeswhereCountry=N'USA')
应用二:每个月最后日期发生的订单
selectOrderID,CustomerID,EmployeeID,OrderDate
fromdbo.orders
whereOrderDatein
(selectMAX(OrderDate)
fromdbo.Orders
groupbyCONVERT(char(6),OrderDate,112));
相关子查询,逻辑上,子查询会为外部查询的每一行都计算一次,物理上他是一个动态的过程,随情况的变化会有所不同。
附加属性(tiebreaker)问题
Eg:
方案一,基于子查询
createuniqueindexidx_eid_od_oid
ondbo.orders(EmployeeID,OrderDate,OrderID);
createuniqueindexidx_eid_od_rd_oid
ondbo.orders(EmployeeID,OrderDate,RequiredDate,OrderID);
方法一
selectOrderID,CustomerID,EmployeeID,OrderDate,RequiredDate
fromdbo.Ordersaso1
whereOrderDate=
(selectMAX(OrderDate)
fromdbo.Ordersaso2
whereo2.EmployeeID=o1.EmployeeID)
andOrderID=
(selectMAX(OrderID)
fromdbo.Ordersaso2
whereo2.EmployeeID=o1.EmployeeID
ando2.OrderID=o1.OrderID);
方法二
selectOrderID,CustomerID,EmployeeID,OrderDate,RequiredDate
fromdbo.Ordersaso1
whereOrderID=
(selectMAX(OrderID)
fromdbo.Ordersaso2
whereo2.EmployeeID=o1.EmployeeID
andOrderDate=
(selectMAX(OrderDate)
fromdbo.orderso3
whereo3.EmployeeID=o2.EmployeeID));
方法二比方法一的性能略高,但是可读性差。
上述查询的索引准则是在(分组列,排序列,附加属性列)上创建索引
方案二,基于聚合
selectEmployeeID
,cast(SUBSTRING(binstr,1,8)asdatetime)
,CAST(substring(binstr,9,4)asint)
,CAST(substring(binstr,13,10)asNCHAR(10))
,CAST(substring(binstr,23,8)asdatetime)
from (selectEmployeeID,
max(CAST(OrderDateasBINARY(8))--binary(n) n (1,8000) 字节数
+CAST(OrderIDasBINARY(4))
+CAST(CustomerIDasBINARY(10))
+CAST(RequiredDateasbinary(8)))asbinstr
fromdbo.orders
groupbyEmployeeID)d
注意:数组转换为二进制时,只有非负值保持原有顺序。
优点:无论是否有合适的索引,他只扫描一次数据,如果有索引,可能执行有序索引扫描和基于排序的聚合,如果没有,可能执行基于哈希的聚合。
当排序列和附加属性的排序方向相反时。比如,附加属性是min(orderid),可以使用maxint-max()实现。
selectEmployeeID
,cast(SUBSTRING(binstr,1,8)asdatetime)
,CAST(substring(binstr,9,4)asint)
,CAST(substring(binstr,13,10)asNCHAR(10))
,CAST(substring(binstr,23,8)asdatetime)
from (selectEmployeeID,
max(CAST(OrderDateasBINARY(8))--binary(n) n (1,8000) 字节数
+CAST(2147483647-OrderIDasBINARY(4))
+CAST(CustomerIDasBINARY(10))
+CAST(RequiredDateasbinary(8)))asbinstr
fromdbo.orders
groupbyEmployeeID)d
方案三基于top 添加主键作为附加属性,保证top的确定性。
ifexists(select*fromsys.indexeswherename=N'idx_eid_od_i_cid_rd'andobject_ID=object_id('dbo.Orders'))--在目录视图中查找
dropindexdbo.Orders.idx_eid_od_i_cid_rd
createuniqueindexidx_eid_od_i_cid_rd--索引名称包括索引列的缩写
ondbo.Orders(EmployeeID,OrderDate,OrderID)
include(CustomerID,RequiredDate);
ifexists(select*fromdbo.sysindexeswherename=N'idx_oid_qtyd_pid'andid=OBJECT_ID(N'dbo.[Order Details]'))--在兼容视图中查找
dropindexdbo.[Order Details].idx_oid_qtyd_pid
createuniqueindexidx_oid_qtyd_pid
ondbo.[Order Details](OrderID,Quantitydesc,ProductID)
selectorderID,CustomerID,EmployeeID,OrderDate,RequiredDate
fromdbo.Ordersaso1
whereOrderID=
(selecttop(1)OrderID
fromdbo.OrdersasO2
whereO2.EmployeeID=o1.EmployeeID
orderbyOrderDatedesc,OrderIDdesc);
优点:比方案一要快,特别是有多个排序字段(附加属性)时,只需要在order by 之后增加额外的列即可,比方案二要要慢,但是简单。
优点二:可以通过in来扩展
declare@nint
set@n= 2
selectorderID,CustomerID,EmployeeID,OrderDate,RequiredDate
fromdbo.Ordersaso1
whereOrderIDin
(selecttop(@n)OrderID
fromdbo.OrdersasO2
whereO2.EmployeeID=o1.EmployeeID
orderbyOrderDatedesc,OrderIDdesc);
--查看索引深度
selectINDEXPROPERTY(OBJECT_ID('dbo.orders'),'idx_eid_od_i_cid_rd','indexdepth');
优化提示:上述查询是对每一个订单进行一次书页查找,但实际上只需要对每一个员工进行一次索引查找。
selectorderID,CustomerID,o.EmployeeID,OrderDate,RequiredDate
from (
selectdistinctEmployeeID,toporder=(--distinct
selecttop 1 OrderIDfromdbo.Orderso2whereo2.EmployeeID=o1.EmployeeIDorderbyOrderDatedesc,OrderIDdesc)
fromdbo.Orderso1)eo
innerjoindbo.Ordersoono.OrderID=eo.toporderorderby 1;
使用row_number优化
;witha
as
(
selectorderID,CustomerID,EmployeeID,OrderDate,RequiredDate
,row_number()over(partitionbyEmployeeIDorderbyOrderDatedesc,OrderIddesc)asRowNum
fromdbo.Orders
)
select*fromawhereRowNum= 1 orderby 1
(疑问:逻辑读,预读,书页查找,索引深度?逻辑读每次读取多少页,偏移量怎么算,预读的依据是什么?书页查找怎么会发声三次逻辑读?索引深度包括root吗?)
EXISTS 和 IN,等输入列表中包含NULL时,IN实际上会产生一个UNKNOWN的逻辑结果,In (b 、c、NULL)的结果是UNNOWN,在筛选器中UNKOWN与FALSE的处理方式类似,所以in和exist的查询结果产生相同的执行计划。
Not exists 和not in
列包含null时,not in 查询总是返回一个空集,因为谓词val in (val1,val2,……,null)永远不会返回false,而是返回true或者unknown,所以Val not in(val1、val2、……、null)只会返回not true或者not unknown,不返回true
最小缺失值(Missing Value)
use test
go
if OBJECT_ID('dbo.t1') is not null
drop table dbo.t1;
go
create table t1
(
keycol int not null primary key check(keycol>0),
datacol varchar(10) not null
);
insert into t1 values(3,'a');
insert into t1 values(4,'b');
insert into t1 values(6,'c');
insert into t1 values(7,'d');
insert into t1 values(1,'d');
select
case
when not exists( select * from dbo.t1 where keycol = 1 ) then 1
else (select min(keycol+1) from t1 a where not exists( select * from t1 b where b.keycol = a.keycol + 1))
end ;
逆反逻辑(Reverse Logic)在关系分区问题中的应用
谜题:有两个守卫守在两扇门前。一扇门通向黄金和财宝,另一扇通向死亡,但是你不知道哪个是那个。一个门卫总是说真话,另一个总是说假话,但是你分不清谁说谎谁谁诚实。你会怎么问?
答案:你应该问其中一个守卫,“如果我问另一个门卫哪扇门通向黄金,他会指向哪扇门?”
Eg:返回其订单由所有的usa员工处理的消费者=返回没有订单是由非USA员工处理的消费者(双重否定)
行为不当(Misbehaving)的子查询
注意:一个好的实践是在子查询中总是为所有属性限制表名称或别名,即使子查询是独立子查询也应该如此。
不常用的谓词
Any,some,all
表表达式table expression:用作表的子查询(内联表表达式 inline table expression它包括派生表drived tables 和公用表达式 CTE)
派生表是一种从查询表达式派生出虚拟结果表的表表达式。派生表与其他表一样出现在查询的from子句中。派生表仅存在外部查询中。所以优化器不会为他生成独立的计划,编译时,外部查询和内部查询被合并,并生成一个计划。使用派生表既不会降低性能,也不会提高性能,它更多的是为了代码的简化和清晰。
派生表必须是一个有效的表。因此,它必须遵守几条规则:
所有列必须有名称
列名称必须是惟一的
不允许使用order by (除非也指定了top)
不同于标量和多值子查询,派生表不能是相关的;它必须是独立的。但当使用apply运算符时是一个例外。
应用场景:使用列别名(内联列别名,外联列别名)
select OrderYear,COUNT(distinct CostomerID) as NumCusts
from ( select YEAR(OrderDate) as OrderYear,CustomerID from dbo.Orders ) as d
group by OrderYear
派生表可以使用参数,可以嵌套。
CTE
Cte仅存在于外部查询中,对于同一批处理中的其他语句视而不见。With前面必须有;
可以使用参数,多CTE,多引用
Eg:使用cte删除重复的行
use Northwind
go
if OBJECT_ID('dbo.CustomersDups') is not null
drop table dbo.Customersdups
go
;with CrossCustomers as
(
select 1 as c,c1.*
from dbo.Customers as c1,dbo.Customers as c2
)
select ROW_NUMBER() over(order by c) as keyCol,
customerID,CompanyName,ContactName,ContactTitle,Address,
city,Region,PostalCode,country,Phone,Fax
into dbo.CustomersDups
from CrossCustomers
;with JustDups as
(
select * from CustomersDups a
where keyCol <(select MAX(keycol) from CustomersDups b where a.customerID = b.customerID)
)
delete from JustDups;
select * from CustomersDups
cte可以用在诸如视图或者内联udf这样的容器对象中,这种能力允许实现封装,这对于模块化开发是非常重要的。而且,cte不能被嵌套,但是通过容器对象封装cte并在外部cte中查询容器对象,就可以间接嵌套cte
create view dbo.VYearCnt
as
with yearCnt as
(
select year(OrderDate) as OrderYear,
count(distinct CustomerId) as NumCusts
from dbo.Orders
group by year(OrderDate)
)
select * from yearCnt;
go
select * from vyearcnt
drop view vyearcnt
create function dbo.fn_EmpYearCnt(@EmpID as int) returns table
as
return
with EmpYearCnt as
(
select year(OrderDate) as OrderYear,
count(distinct CustomerID) as NumCusts
from dbo.Orders
where EmployeeID = @EmpID
group by year(OrderDate)
)
select * from EmpYearCnt;
select * from dbo.fn_EmpYearCnt(3)
drop function fn_EmpYearCnt
cte递归
;with EmpsCTE as
(
select EmployeeID,ReportsTo,FirstName,LastName from dbo.Employees where EmployeeID = 2
union all
select a.EmployeeID,a.ReportsTo,a.FirstName,a.LastName from dbo.Employees a inner join EmpsCTE b on a.ReportsTo = b.EmployeeID
)
select * from EmpsCTE
option (maxrecursion 2)—疑问,递归会不会出现重复行
05 技术内幕 T-SQL 查询读书笔记(第四章)的更多相关文章
- 《Linux内核设计与实现》第八周读书笔记——第四章 进程调度
<Linux内核设计与实现>第八周读书笔记——第四章 进程调度 第4章 进程调度35 调度程序负责决定将哪个进程投入运行,何时运行以及运行多长时间,进程调度程序可看做在可运行态进程之间分配 ...
- 《Linux内核设计与实现》 第八周读书笔记 第四章 进程调度
20135307 张嘉琪 第八周读书笔记 第四章 进程调度 调度程序负责决定将哪个进程投入运行,何时运行以及运行多长时间,进程调度程序可看做在可运行态进程之间分配有限的处理器时间资源的内核子系统.只有 ...
- 《Linux内核分析》读书笔记(四章)
<Linux内核分析>读书笔记(四章) 标签(空格分隔): 20135328陈都 第四章 进程调度 调度程序负责决定将哪个进程投入运行,何时运行以及运行多长时间,进程调度程序可看做在可运行 ...
- 4 Visual Effects 视觉效果 读书笔记 第四章
4 Visual Effects 视觉效果 读书笔记 第四章 Well, circles and ovals are good, but how about drawing r ...
- STL源码剖析读书笔记--第四章--序列式容器
1.什么是序列式容器?什么是关联式容器? 书上给出的解释是,序列式容器中的元素是可序的(可理解为可以按序索引,不管这个索引是像数组一样的随机索引,还是像链表一样的顺序索引),但是元素值在索引顺序的方向 ...
- 《利用python进行数据分析》读书笔记--第四章 numpy基础:数组和矢量计算
http://www.cnblogs.com/batteryhp/p/5000104.html 第四章 Numpy基础:数组和矢量计算 第一部分:numpy的ndarray:一种多维数组对象 实话说, ...
- 《R语言实战》读书笔记--第四章 基本数据管理
本章内容: 操纵日期和缺失值 熟悉数据类型的转换 变量的创建和重编码 数据集的排序,合并与取子集 选入和丢弃变量 多说一句,数据预处理的时间是最长的……确实是这样的,额. 4.1一个示例 4.2创建新 ...
- 《Linux内核设计与实现》读书笔记 第四章 进程调度
第四章进程调度 进程调度程序可看做在可运行太进程之间分配有限的处理器时间资源的内核子系统.调度程序是多任务操作系统的基础.通过调度程序的合理调度,系统资源才能最大限度地发挥作用,多进程才会有并发执行的 ...
- Getting Started With Hazelcast 读书笔记(第四章)
第四章 分而治之 在指导了如何进行基本使用之后,又再次进入理论模块. Hazelcast的基本策略就是切片分区,默认是271个片.内置一个 partition table记录那个节点是那个分区,并在h ...
- 《TCP/IP 详解 卷一》读书笔记 -----第四章 ARP
1.一个物理层的网络,例如以太网,可以同时被多个不同的网络层所使用.例如网络中的一些主机使用TCP/IP协议,其他主机使用其他的网络协议. 2.设备驱动软件从不关心IP数据报中的目的IP地址.这也是为 ...
随机推荐
- 用jsonp格式的数据进行ajax post请求变成get
因为 dataType 是 jsonp 而不是 json jsonp不支持POST跨域,所以会自动转成GET而关于jsonp为什么不支持post请求,百度到的答案是jsonp为动态的script,没有 ...
- Hadoop平台常用配置及优化建议
当发现作业运行效率不理想时,需要对作业执行进行性能监测,以及对作业本身.集群平台进行优化.优化后的集群可能最大化利用硬件资源,从而提高作业的执行效率.本文记录了在hadoop集群平台搭建以及作业运行过 ...
- Andorid项目创建AVD时,OK按钮无法点亮
经上网查证,发现原因在于CPU/ABI选项无法选择,并显示“No system images installed for this target”,也就是没有适合的系统镜像,通过与安装好了的ADT-b ...
- jquery+ajax实现分页
var curPage = 1; //当前页码 var total,pageSize,totalPage; //总记录数,每页显示数,总页数 getData(1); $("#pagecoun ...
- ubuntu 安装transmission最新版
访问 www.transmissionbt.com 下载最新版 1)下载transmission:# wget https://transmission.cachefly.net/transmi ...
- Docker镜像的管理和创建
1. Docker镜像和Docker容器: Docker镜像实际上是一系列的文件系统,通常的Linux系统一般是两层文件系统,bootfs和rootfs,bootfs就是bootloader ...
- java_Excel 导出
package Demo; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; i ...
- 阿里云推荐码 hut29f
阿里云 推荐码 hut29f,适用于新手首次购买.
- PARENT VIEW INVALIDATE
如果启动display list,parentview 调用invalidate,子view是不会是去调用onDraw的,因为直接调用displaylist了,如果parentview认为子view应 ...
- Highcharts使用教程(2):设置选项
Highcharts使用教程(2):设置选项 使用教程 | 作者:走猫步的鱼 | 2013-12-11 09:33:25 | 阅读 16次 评论 1 概述:JavaScript图表工具Highchar ...