在今天的文章里,我想谈下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个不同的上下文:

  1. 使用ORDER BY子句你可以定义返回给你客户端程序的排序
  2. 另外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的歧义性的更多相关文章

  1. 在SQL Server里为什么我们需要更新锁

    今天我想讲解一个特别的问题,在我每次讲解SQL Server里的锁和阻塞(Locking & Blocking)都会碰到的问题:在SQL Server里,为什么我们需要更新锁?在我们讲解具体需 ...

  2. 在SQL Server里如何进行页级别的恢复

    在今天的文章里我想谈下每个DBA应该知道的一个重要话题:在SQL Server里如何进行页级别还原操作.假设在SQL Server里你有一个损坏的页,你要从最近的数据库备份只还原有问题的页,而不是还原 ...

  3. SQL Server里强制参数化的痛苦

    几天前,我写了篇SQL Server里简单参数化的痛苦.今天我想继续这个话题,谈下SQL Server里强制参数化(Forced Parameterization). 强制参数化(Forced Par ...

  4. SQL Server里的INTERSECT ALL

    在上一篇文章里,我讨论了INTERSECT设置操作的基础,它和INNER JOIN的区别,还有为什么需要好的索引设计支持.今天我想谈下SQL Server里并未实现的INTERSECT ALL操作. ...

  5. SQL Server里Grouping Sets的威力

    在SQL Server里,你有没有想进行跨越多个列/纬度的聚集操作,不使用SSAS许可(SQL Server分析服务).我不是说在生产里使用开发版,也不是说安装盗版SQL Server. 不可能的任务 ...

  6. SQL Server里如何随机记录集

    今天的文章,我想给你简单介绍下SQL Server里如何随机记录集. SELECT * FROM Person.Person ORDER BY NEWID() GO 这会引入新的UNIQUEIDENT ...

  7. 在SQL Server里如何进行数据页级别的恢复

    在SQL Server里如何进行页级别的恢复 关键词:数据页修复 在今天的文章里我想谈下每个DBA应该知道的一个重要话题:在SQL Server里如何进行页级别还原操作.假设在SQL Server里你 ...

  8. SQL Server里Grouping Sets的威力【转】

    在SQL Server里,你有没有想进行跨越多个列/纬度的聚集操作,不使用SSAS许可(SQL Server分析服务).我不是说在生产里使用开发版,也不是说安装盗版SQL Server. 不可能的任务 ...

  9. SQL Server里在文件组间如何移动数据?

    平常我不知道被问了几次这样的问题:“SQL  Server里在文件组间如何移动数据?“你意识到这个问题:你只有一个主文件组的默认配置,后来围观了“SQL Server里的文件和文件组”后,你知道,有多 ...

随机推荐

  1. 从WEB SERVICE 上返回大数据量的DATASET

    前段时间在做一个项目的时候,遇到了要通过WEB SERVICE从服务器上返回数据量比较大的DATASET,当然,除了显示在页面上以外,有可能还要用这些数据在客户端进行其它操作.查遍了网站的文章,问了一 ...

  2. MiniDao普通项目集成方案

    1.导入必要的jar包: 2.spring配置文件增加如下配置: <!-- Hibernate工具栏配置--> <bean id="miniDaoHiberCommonDa ...

  3. libev代码

    就是贴上来: ev.c: /* * libev event processing core, watcher management */ /* this big block deduces confi ...

  4. Chapter 5. Graph Theory:: Fundamentals:: Intermediate

    10457 - Magic Car 题意一开始看起来有点费解,其实最后就是要起点到终点的路径上最大边与最小边之差越小越好.这样我们可以先将边排个序,然后枚举路径上的最小边,之后依次将比它大的边按升序的 ...

  5. Excel 二级下拉菜单

    http://jingyan.baidu.com/article/cd4c2979f31967756f6e6066.html http://hi.baidu.com/chenshake/item/e1 ...

  6. Kernel Function--核函数收集

    转自 http://www.zhizhihu.com/html/y2010/2292.html Kernel Functions Below is a list of some kernel func ...

  7. 每日英语:Success Outside the Dress Code

    Anyone who has felt like the odd duck of the group can take heart from new research from Harvard Bus ...

  8. Navi.Soft30.开放平台.腾讯.开发手册

    1系统简介 1.1功能简述 现在是一个信息时代,并且正在高速发展.以前获取信息的途径非常少,可能只有电视台,收音机等有限的来源,而现在的途径数不胜数,如:QQ,微信,官方网站,个人网站等等 本开发手册 ...

  9. njoj 1251 zlly长了一张包子脸

    njoj 1251 zlly长了一张包子脸 题意: zlly长了一张包子脸.他特别喜欢吃糖果.如今他手头有若干种糖果,每种糖果有个口味值,每种糖果有无数多个.然后娄童鞋也很喜欢吃糖果.他的口味特别广泛 ...

  10. Xcode工程使用CocoaPods管理第三方库新建工程时出现错误

    工程使用CocoaPods管理第三方库,在新的目录update版本的时候出现如下问题   问题1描述: diff: /../Podfile.lock: No such file or director ...