SQL Server里ORDER BY的歧义性
在今天的文章里,我想谈下SQL Server里非常有争议和复杂的话题:ORDER BY子句的歧义性。
视图与ORDER BY
我们用一个非常简单的SELECT语句开始。
-- A very simple SELECT statement
SELECT * FROM Person.Person
ORDER BY LastName
GO
从刚才列出的代码你可以看到,我们只想从Person.Person表以LastName列排序返回记录。因为我们想能尽可能简单的重用那个SQL语句,最后我们把它放到视图里,如下:
-- This doesn't work
CREATE VIEW v_Persons
AS
SELECT * FROM Person.Person
ORDER BY LastName
GO
但是你会看到,SQL Server不能创建那个视图,只返回一个错误信息:
这个错误信息告诉你,的那个你不使用TOP,OFFSET或FOR XML表达式时,在视图里你不允许使用ORDER BY子句。基于那个错误信息,我们可以通过增加TOP 100 PERCENT子句到视图里在轻松修正问题。
-- Let's make it work!
CREATE VIEW v_Persons
AS
SELECT TOP 100 PERCENT * FROM Person.Person
ORDER BY LastName
GO
现在视图创建没有任何问题!我们对视图执行一个SELECT语句。
SELECT * FROM v_Persons
GO
SELECT语句本身可以执行,但当你看返回的数据时,疯狂的事情发生了:返回的数据没有按LastName列排序——SQL Server按BusinessEntityID——表上的聚集键列排序!

这是SQL Server里的BUG么?不,并不是——它是“故意的”!我们来解释下为什么。首先你要知道ORDER BY子句在SQL(编程语言本身)里用2个不同的上下文:
- 使用ORDER BY子句你可以定义返回给你客户端程序的排序
- 另外ORDER BY子句用来定义从TOP表达式哪些行返回
你必须知道的最重要的事情是,你用视图定义了所谓的集合(Set),行内函数,派生表,子查询和通用表表达式(common table expressions(CTE))。集合是数学上的概念,关系数据库(例如SQL Server)上集合论(Set Theory)的组成。集合本身是没有排序的。因此用视图定义与ORDER BY组合是不允许的——如你刚才所见。如果你尝试这样做,SQL Server不允许你这样做并给你一个错误信息。
当然你可以在与TOP表达式里组合使用ORDER BY。但基本上你在愚弄SQL Server和你自己,因为ORDER BY没有告诉SQL Server要以怎样的排序返回数据给客户端程序。假设你使用TOP 10 PERCENT。表的前10%是什么?你需要确定性的方式里定义排序。
而且因为我们必须使用TOP 100 PERCENT与ORDER BY组合,查询优化器实际上在执行计划里不会引入排序运算符。TOP 100 PERCENT意味着一切,因此如你在下图所看到的,在执行计划里TOP运算符不需要排序输入。

在这个例子里,我们的返回行以从内在数据结构读取的排序。这由SQL Server的存储引擎来决定返回行的排序。这里我们从聚集索引里读取行。因此我们拿到的数据按BusinessEntityID排序,这是索引列里聚集键值。
现在我们修改下视图定义,从Person.Person表值返回10%的行。我们还是指定了ORDER BY子句。
-- Alter the view
ALTER VIEW v_Persons
AS
SELECT TOP 10 PERCENT * FROM Person.Person
ORDER BY LastName
GO
当你现在看结果集时,你会看到返回的行按LastName列排序的。现在才对了,因为你在执行计划里看到了排序运算符(SQL Server 2014里没有出现),因为TOP运算符最后能返回提供输入行的前10%的数据。


当然你可以通过ORDER BY子句在你引用的视图里按不同的排序返回10%的行给你的客户端程序。
SELECT * FROM v_Persons
ORDER BY FirstName
GO
现在当你看执行计划时,你会在计划里看到2个(SQL Server 2014里只有1个)。

第1个(右边)排序运算符为TOP运算符预排序(返回前10%)。第2个(左边)排序运算符用来最后定义的排序,返回给客户端程序。当你通过添加TOP 100 PERCENT来定义的视图里强制ORDER BY——你基本上就在愚弄SQL Server……
没有ORDER BY的TOP
另一个问题是没有ORDER BY子句的TOP表达式不会提供你确定性的结果。我们可以用具体的例子演示下这个问题。假设有下列SELECT语句:
SELECT TOP 1 LastName FROM Person.Person
GO
这个SQL语句用TOP 1表达式返回Person.Person表的第一行——没有用ORDER BY子句定义排序。这个排序是基于执行计划里选择的索引。在这个例子里SQL Server返回你“Abbas”给你作为结果,因为这是执行计划里查询优化器选择非聚集索引里第1条可用记录。

因此从这个查询返回的第1条记录取决于执行计划里选择的索引。如果现在我们把非聚集索引停用呢。
-- Let's deactivate this index
ALTER INDEX [IX_Person_LastName_FirstName_MiddleName] ON Person.Person
DISABLE
GO
然后当你再次执行刚才的SELECT语句,SQL Server返回你Sánchez值,意味只是在执行计划里现在选择的聚集索引的第1条记录。SQL Server从聚集索引里返回了用BusinessEntityID值为1的第1行。


因此你与非确定性记录打交道时:你的结果取决与执行计划里选择的索引!你可以通过增加ORDER BY子句来轻松实现查询结果排序的明确性。在这个情况下ORDER BY子句为TOP表达式使记录确定——这样话在执行计划里你会有Sort(Top N Sort)的运算符。
SELECT TOP 1 LastName FROM Person.Person
ORDER BY LastName
GO


在执行计划里,SQL Server从哪个索引读取行并不重要——Sort(Top N Sort)的运算符在执行计划里会物理预排序行,并从它返回第N行——很简单,是不是?
小结
在SQL(编程语言本身)里ORDER BY子句并不是一个最简单的概念。如你在这篇文章里所学的,ORDER BY使用2个不同的上下文,因此你总要考虑下你要使用哪个上下文。永远不要在视图定义里增加TOP 100 PERCENT来愚弄SQL Server和你自己——它不会在最终的记录集里体现排序。
感谢关注!
参考文章:
https://www.sqlpassion.at/archive/2015/05/25/the-ambiguity-of-the-order-by-in-sql-server/
SQL Server里ORDER BY的歧义性的更多相关文章
- 在SQL Server里为什么我们需要更新锁
今天我想讲解一个特别的问题,在我每次讲解SQL Server里的锁和阻塞(Locking & Blocking)都会碰到的问题:在SQL Server里,为什么我们需要更新锁?在我们讲解具体需 ...
- 在SQL Server里如何进行页级别的恢复
在今天的文章里我想谈下每个DBA应该知道的一个重要话题:在SQL Server里如何进行页级别还原操作.假设在SQL Server里你有一个损坏的页,你要从最近的数据库备份只还原有问题的页,而不是还原 ...
- SQL Server里强制参数化的痛苦
几天前,我写了篇SQL Server里简单参数化的痛苦.今天我想继续这个话题,谈下SQL Server里强制参数化(Forced Parameterization). 强制参数化(Forced Par ...
- SQL Server里的INTERSECT ALL
在上一篇文章里,我讨论了INTERSECT设置操作的基础,它和INNER JOIN的区别,还有为什么需要好的索引设计支持.今天我想谈下SQL Server里并未实现的INTERSECT ALL操作. ...
- SQL Server里Grouping Sets的威力
在SQL Server里,你有没有想进行跨越多个列/纬度的聚集操作,不使用SSAS许可(SQL Server分析服务).我不是说在生产里使用开发版,也不是说安装盗版SQL Server. 不可能的任务 ...
- SQL Server里如何随机记录集
今天的文章,我想给你简单介绍下SQL Server里如何随机记录集. SELECT * FROM Person.Person ORDER BY NEWID() GO 这会引入新的UNIQUEIDENT ...
- 在SQL Server里如何进行数据页级别的恢复
在SQL Server里如何进行页级别的恢复 关键词:数据页修复 在今天的文章里我想谈下每个DBA应该知道的一个重要话题:在SQL Server里如何进行页级别还原操作.假设在SQL Server里你 ...
- SQL Server里Grouping Sets的威力【转】
在SQL Server里,你有没有想进行跨越多个列/纬度的聚集操作,不使用SSAS许可(SQL Server分析服务).我不是说在生产里使用开发版,也不是说安装盗版SQL Server. 不可能的任务 ...
- SQL Server里在文件组间如何移动数据?
平常我不知道被问了几次这样的问题:“SQL Server里在文件组间如何移动数据?“你意识到这个问题:你只有一个主文件组的默认配置,后来围观了“SQL Server里的文件和文件组”后,你知道,有多 ...
随机推荐
- 小白学数据分析----->学习注册转化率
你的注册转化率及格了吗? 注册转化率,一个基本上可以忽略的指标,虽然简单,但是却真实反映渠道,发行商,开发者的实力,以及对待产品的态度. 所谓的注册转化率,其实指的是玩家从下载游戏后,打开激活游戏,注 ...
- 【转】我应该直接学Swift还是Objective-C?
(本文作者Amit Bijlani,由CocoaChina翻译) 当我们发布了Swift语言学习课程之后,收到了很多邮件和私信来问自己是否还需要学习C或者Objective-C.此外,人们似乎还在迷惑 ...
- [C] zlstdint(让VC、TC等编译器自动兼容C99的整数类型)V1.0。支持Turbo C++ 3等DOS下的编译器
作者:zyl910 以前我曾为了让VC++等编译器支持C99的整数类型,便编写了c99int库来智能处理(http://www.cnblogs.com/zyl910/p/c99int_v102.htm ...
- Understanding mysql max_connect_errors
来自:http://mysqlblog.fivefarmers.com/2013/08/08/understanding-max_connect_errors/ Perhaps like many u ...
- [Aaronyang] 写给自己的WPF4.5 笔记24 [与winform交互-flash-DEMO-收尾篇1/6]
=====潇洒的版权线======www.ayjs.net===== Aaronyang ===== AY ====== 安徽 六安 杨洋 ====== 未经允许不许转载 ====== 1.新 ...
- Putty & Ctrl+s 的魔咒
Long long ago“ 某些旧的”哑终端“会在发送过来的数据太多,显示速度跟不上时发送一个Ctrl+s让对方等一下,然后再准备好继续显示时发送一个Ctrl+q.Putty“兼容”了这个特性.也有 ...
- webService调用
1.wsimport 生成客户端 2.axis2 3.通过HttpClient调用webservice 4.使用xfire
- liunx 服务内存消耗100% 怎么处理
一.排查问题 查看内存使用 free 查看进程使用存储状况.看是不是业务进程在消耗存储,如果是就要优化业务代码了 使用top 命令 如果不是怎么办 二.处理办法 1.把没有用到的消耗内存的服务进程 ...
- 用户管理 之 Linux 系统中的超级权限的控制
在Linux操作系统中,root的权限是最高的,也被称为超级权限的拥有者.普通用户无法执行的操作,root用户都能完成,所以也被称之为超级管理用户. 在系统中,每个文件.目录和进程,都归属于某一个用户 ...
- 【转】微信公众账号 Senparc.Weixin.MP SDK 开发教程 索引
微信公众账号 Senparc.Weixin.MP SDK 开发教程 索引 Senparc.Weixin.MP SDK从一开始就坚持开源的状态,这个过程中得到了许多朋友的认可和支持. 目前SDK已经达到 ...