为什么查询的解析方式不允许大多数子句中使用列别名?


16

在尝试编写查询时,我发现(困难的方式)SQL Server在执行查询时在解析SELECT之前很久就解析了查询中的WHERE。

MSDN文档说,一般逻辑解析顺序是这样的:SELECT被解析几乎最后(因此导致“没有这样的对象[别名]”试图使用在其他条款列别名时误差)。甚至有人建议允许在任何地方使用别名,这被Microsoft团队否决,理由是ANSI标准合规性问题(这表明此行为是ANSI标准的一部分)。

作为程序员(不是DBA),我发现这种行为有些令人困惑,因为在我看来,它在很大程度上违反了使用列别名的目的(或者至少可以使列别名的功能更加强大)。在查询执行的较早阶段进行了解析),因为您真正可以使用别名的唯一位置是ORDER BY。作为程序员,似乎缺少使查询变得更强大,更便捷和更干燥的巨大机会。

看起来这是一个显而易见的问题,因此,还有其他原因决定除SELECT和ORDER BY之外的任何内容都不应使用列别名,但是这些原因是什么?

Answers:


19

摘要

没有逻辑上的理由无法做到这一点,但是这样做的好处很小,而且有些陷阱可能不会立即显现出来。

研究成果

我做了一些研究,发现了一些很好的信息。以下是格林尼治标准时间2012-08-09 17:49从可靠的主要来源(希望保持匿名)的直接报价:

首次发明SQL时,SELECT子句中没有别名。这是一个严重的缺陷,大约在1986年被ANSI标准化时已得到纠正。

该语言旨在成为“非过程性”语言,换句话说,是在不指定查找方式的情况下描述所需的数据。因此,据我所知,没有理由为什么SQL实现无法在处理整个查询之前解析整个查询,并允许在任何地方定义别名并在任何地方使用别名。例如,我看不到以下查询无效的任何原因:

select name, salary + bonus as pay
from employee
where pay > 100000

尽管我认为这是一个合理的查询,但是由于某些与实现相关的原因,某些基于SQL的系统可能会对别名的使用施加限制。听到SQL Server这样做我并不感到惊讶。

我对对SQL-86标准的进一步研究以及现代DBMS为什么不支持别名重用感兴趣,但是还没有时间进行深入研究感兴趣。对于初学者来说,我不知道从哪里获取文档或不知道究竟由谁组成委员会。有人可以帮忙吗?我还想了解更多有关SQL Server的原始Sybase产品的信息。

从这项研究和一些进一步的思考中,我开始怀疑在其他子句中使用别名虽然很可能,但与其他语言功能相比,对于DBMS制造商来说从来没有这么优先。由于不是很大的障碍,查询编写者很容易解决它,因此在其他方面投入精力并不是最佳选择。另外,它将是专有的,因为它显然不是SQL标准的一部分(尽管我正在等待确定更多信息),因此将是一个较小的改进,破坏了DBMS之间的SQL兼容性。相比之下,CROSS APPLY(实际上只不过是允许外部引用的派生表)是一个巨大的变化,尽管专有提供了令人难以置信的表达能力,但其他方式却难以实现。

在各处使用别名的问题

如果允许将SELECT项放在WHERE子句中,则不仅会激增查询的复杂性(以及发现良好执行计划的复杂性),而且可能会提出完全不合逻辑的内容。尝试:

SELECT X + 5 Y FROM MyTable WHERE Y = X

如果MyTable已经具有Y列,WHERE子句引用的是该怎么办?解决方案是使用CTE或派生表,在大多数情况下,它们无需花费额外费用,但可以获得相同的最终结果。CTE和派生表至少通过允许仅使用一次别名来强制解决歧义。

同样,在FROM子句中不使用别名是很有意义的。您不能这样做:

SELECT
   T3.ID + (SELECT Min(Interval) FROM Intervals WHERE IntName = 'T') CalcID
FROM
   Table1 T
   INNER JOIN Table2 T2
      ON T2.ID = CalcID
   INNER JOIN Table3 T3
      ON T2.ID = T3.ID

这是一个循环引用(在这个意义上,T2被秘密地参照从T3的值,在此之前已经表中JOIN列表被呈现),并织补难以看清。这个怎么样:

INSERT dbo.FinalTransaction
SELECT
   newid() FinalTransactionGUID,
   'GUID is: ' + Convert(varchar(50), FinalTransactionGUID) TextGUID,
   T.*
FROM
   dbo.MyTable T

您想打赌newid()函数将被两次放入执行计划中,完全出乎意料地使两列显示不同的值是多少?如果在CTE或派生表中使用了N个级别的查询,该怎么办?我保证这个问题比您想象的要严重。有已经什么时候的事情仅计算一次,或者在查询计划什么时候严重不一致的问题,微软已经表示,它不会修复其中一些是因为它们正确表达了查询代数-如果获得意外结果,请将查询分解为多个部分。允许链接引用,通过可能很长的此类链来检测循环引用,这是非常棘手的问题。引入并行性,您将面临噩梦。

注意:在WHERE或GROUP BY中使用别名不会对newid()或rand()之类的函数产生影响。

创建可重用表达式的SQL Server方法

交叉应用/外部应用是SQL Server中创建可在查询中其他任何地方使用的表达式的一种方法(只是在FROM子句中不早):

SELECT
   X.CalcID
FROM
   Table1 T
   INNER JOIN Table3 T3
      ON T.ID = T3.ID
   CROSS APPLY (
      SELECT
         T3.ID + (SELECT Min(Interval) FROM Intervals WHERE IntName = 'T') CalcID
   ) X
   INNER JOIN Table2 T2
      ON T2.ID = X.CalcID

这有两件事:

  1. 使CROSS APPLY中的所有表达式都获得一个“命名空间”(表别名,在此为X),并在该命名空间内唯一。
  2. 不仅使CalcID来自X,而且使在表T1和T3联接时为什么不能使用X的任何内容变得无处不在,这是显而易见的,因为尚未引入X。

我实际上很喜欢CROSS APPLY。它已经成为我忠实的朋友,并且我一直都在使用它。是否需要部分UNPIVOT(使用本机语法需要PIVOT / UNPIVOT或UNPIVOT / PIVOT)?完成与交叉申请。需要一个可以多次重用的计算值吗?做完了 是否需要严格执行链接服务器上的调用的执行顺序?完成-惊人的速度提高。仅需要将一种类型的行拆分为2行或有附加条件?做完了

因此至少,在DBMS SQL Server 2005及更高版本中,您没有其他抱怨的理由:CROSS APPLY是您以所需的方式进行干燥的方式。


14

我无法告诉您确切的原因,但是我会告诉您,存在一些重复表达式的解决方法,例如,使用CTE,子查询,派生表等来避免重复。

如果您显示的查询带有重复的表达式,我们可能会向您展示如何重新编写查询,以便该表达式仅列出一次。但是,这只会降低写/读查询的复杂性,不太可能在效率上做出很大的改变。SQL Server通常非常擅长识别表达式是否重复,并且不会重复执行该工作。还有一些例外,但是您只应在实际观察到这种情况时才考虑效率。我怀疑您编写的大多数重复表达式确实在计划中仅折叠为一个操作。

综上所述,我还将从这个问题中重复我的部分回答:

/dba/19762/why-is-the-select-clause-listed-first


这是Joe Celko对如何根据标准处理查询的解释(我从我自己的aspfaq.com文章中窃取了该内容,该文章可能是从Celko的新闻组帖子中窃取了报价):

至少从理论上讲,这是SELECT在SQL中的工作方式。真正的产品将在可能的情况下优化事物。

从FROM子句开始,并从所有联接,联合,交集以及任何其他表构造函数中构建工作表。使用AS选项,您可以为此工作表命名,然后必须将其用于其余的包含查询。

转到WHERE子句,并删除不符合条件的行;也就是说,不要测试为TRUE(拒绝UNKNOWN和FALSE)。WHERE子句应用于FROM子句中的工作。

转到可选的GROUP BY子句,进行分组,并将每个分组减少为一行,用新的分组表替换原始工作表。分组表的行必须是分组特征:(1)分组列(2)有关分组的统计信息(即聚合函数)(3)函数或(4)由这三个项目组成的表达式。

转到可选的HAVING子句,并将其应用于分组的工作表;如果没有GROUP BY子句,则将整个表视为一组。

转到SELECT子句,然后在列表中构造表达式。这意味着在完成所有其他子句之后,将完成SELECT中的标量子查询,函数调用和表达式。AS运算符也可以为SELECT列表中的表达式命名。这些新名称立即全部存在,但是在执行WHERE子句之后;因此,您不能在SELECT列表或WHERE群集中使用它们。

嵌套查询表达式遵循您对块结构化语言(如C,Pascal,Algol等)所期望的通常的作用域规则。即,最里面的查询可以引用包含它们的查询中的列和表。

这意味着SELECT的列不能超过GROUP BY。但是它当然可以具有更少的列。

现在,Celko是早期版本标准的主要贡献者之一。WHY?除了猜测之外,我不知道您是否会获得该问题的明确答案。我的猜测是,首先列出实际操作将使解析器非常容易准确地知道操作的类型。想象一个20张桌子的联接最终可能是a SELECTUPDATE or DELETE,并记住这些引擎的代码最初是在字符串解析非常昂贵的年代写回的。

请注意,如果SQL标准规定 FROM要首先,则供应商可能已经独立决定以不同的顺序解析语法,因此期望编写的子句顺序完全服从处理100%的顺序仍然是没有道理的。时间。

诸如此类的事情也是如此CASE。例如,在此站点上我们已经看到了一些场景,例如,以前认为CASE始终有序和短路的神话是错误的。而且这还扩展到其他普遍的信念,例如SQL Server按照写入顺序评估连接,从左到右短路WHERE子句,或一次或以一定顺序处理CTE,即使多次引用它们也是如此。产品可以自由地优化其外观,即使它不能完全反映您声明查询应以声明方式工作的方式。


2
还要注意,在查询的不同部分中使用或不使用别名的能力是由解析器而不是优化器或执行引擎强制实施的。引擎实际执行查询的方式不一定反映影响语法的限制。
阿龙贝特朗

2

Entity SQL中,在某些情况下,您可以在查询中其他位置使用表达式的别名:

select k1, count(t.a), sum(t.a)
from T as t
group by t.b + t.c as k1

请注意,此处必须在GROUP BY子句中定义表达式才能在子句中使用它SELECT

显然,可以在SQL查询中允许使用这种别名作为可重用的表达式。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.