在尝试编写查询时,我发现(很难)SQL Server 在执行查询时解析 SELECT 之前很久就解析查询中的 WHERE。
MSDN 文档说,一般的逻辑解析顺序使得 SELECT 几乎在最后被解析(因此在尝试在其他子句中使用列别名时导致“没有这样的对象 [别名]”错误)。甚至有一个建议允许在任何地方使用别名,但被微软团队否决了,理由是 ANSI 标准合规性问题(这表明这种行为是 ANSI 标准的一部分)。
作为一名程序员(不是 DBA),我发现这种行为有点令人困惑,因为在我看来,它在很大程度上违背了拥有列别名的目的(或者,至少,如果列别名是在查询执行的早期解析),因为您可以实际使用别名的唯一位置是在 ORDER BY 中。作为一名程序员,它似乎错过了一个让查询更强大、更方便和更干的机会。
看起来这是一个如此明显的问题,因此有理由认为,除了 SELECT 和 ORDER BY 之外,还有其他原因决定不允许在任何其他内容中使用列别名,但这些原因是什么?
概括
没有理由不能这样做,但好处很小,而且有些陷阱可能不会立即显现出来。
研究成果
我做了一些研究,发现了一些很好的信息。以下是格林威治标准时间 2012 年 8 月 9 日 17:49 来自可靠的主要来源(希望保持匿名)的直接引用:
我有兴趣进一步研究 SQL-86 标准以及为什么现代 DBMS 不支持别名重用,但还没有时间深入研究它。对于初学者,我不知道从哪里获得文件或如何找出委员会的确切成员。任何人都可以帮忙吗?我还想了解更多有关 SQL Server 的原始 Sybase 产品的信息。
通过这项研究和一些进一步的思考,我开始怀疑在其他子句中使用别名虽然很有可能,但与其他语言功能相比,DBMS 制造商从未如此重视。由于它不是那么大的障碍,查询编写者很容易解决它,因此将精力放在其他改进上并不是最佳的。此外,它将是专有的,因为它显然不是 SQL 标准的一部分(尽管我正在等待确定更多信息),因此将是一个小的改进,破坏了 DBMS 之间的 SQL 兼容性。相比之下,
CROSS APPLY
(实际上只不过是一个允许外部引用的派生表)是一个巨大的变化,虽然专有提供了令人难以置信的表达能力,但其他方式并不容易实现。到处使用别名的问题
如果您允许将 SELECT 项放在 WHERE 子句中,您不仅可以增加查询的复杂性(以及因此找到一个好的执行计划的复杂性),还可能提出完全不合逻辑的东西。尝试:
如果 MyTable 已经有 Y 列,WHERE 子句指的是哪一列呢?解决方案是使用 CTE 或派生表,在大多数情况下,这不会产生额外费用,但可以达到相同的最终结果。CTE 和派生表至少通过允许别名仅使用一次来强制解决歧义。
此外,不在 FROM 子句中使用别名也很有意义。你不能这样做:
这是一个循环引用(从某种意义上说,T2 秘密地引用了来自 T3 的值,在该表出现在 JOIN 列表中之前),而且很难看到。这个怎么样:
你有多少赌注 newid() 函数将被放入执行计划两次,完全出乎意料地使两列显示不同的值?当上面的查询在 CTE 或派生表中使用 N 层时会怎样。我保证问题比你想象的还要严重。关于何时只评估一次或在查询计划中的什么时间点已经存在严重的不一致问题,微软表示不会修复其中一些是因为它们正确地表达了查询代数——如果得到意想不到的结果,请将查询分成几部分。允许链式引用,通过可能很长的链检测循环引用——这些都是非常棘手的问题。引入并行性,您将面临一场噩梦。
注意:在 WHERE 或 GROUP BY 中使用别名不会对 newid() 或 rand() 等函数的问题产生影响。
创建可重用表达式的 SQL Server 方法
CROSS APPLY/OUTER APPLY 是 SQL Server 中创建可在查询中其他任何地方使用的表达式的一种方式(只是在 FROM 子句中不更早):
这做了两件事:
我其实很喜欢 CROSS APPLY。它已成为我忠实的朋友,我一直在使用它。需要部分 UNPIVOT(这需要使用本机语法的 PIVOT/UNPIVOT 或 UNPIVOT/PIVOT)?完成交叉应用。需要一个可以重复使用多次的计算值?完毕。需要严格执行链接服务器上的调用的执行顺序?完成——速度有了惊人的提高。只需要一种类型的行拆分为 2 行或附加条件?完毕。
因此,至少,在 DBMS SQL Server 2005 及更高版本中,您没有进一步的抱怨理由:CROSS APPLY 是您按照您想要的方式进行干燥的方式。
我不能告诉你确切的原因,但我会告诉你重复表达式有一些变通方法,例如使用 CTE、子查询、派生表等来避免重复。
如果您显示带有重复表达式的查询,我们可能会向您展示如何重写它,以便表达式只列出一次。然而,这只是降低了写入/读取查询的复杂性,不太可能改变效率。SQL Server 通常可以很好地识别重复的表达式,并且不会执行两次该工作。也有相反的例外情况,但只有在实际观察到这种情况发生时,您才应该关心效率。我怀疑您编写的大多数重复表达式实际上都折叠为计划中的一个操作。
综上所述,我还将重复我对这个问题的部分回答:
https://dba.stackexchange.com/questions/19762/why-is-the-select-clause-listed-first
这是 Joe Celko 对如何根据标准处理查询的解释(我从我自己的 aspfaq.com 文章中偷了这个,这可能是从 Celko 的新闻组帖子中偷来的):
现在,Celko 是早期版本标准的主要贡献者之一。我不知道你是否会得到这个问题的明确答案
WHY?
,除了猜测。我的猜测是,首先列出实际操作会使解析器很容易准确地知道操作的类型。想象一下一个 20 表的连接,它最终可能是SELECT
orUPDATE
或DELETE
,请记住,这些引擎的代码最初是在字符串解析非常昂贵的时候写的。请注意,如果 SQL 标准规定
FROM
要先出现,供应商可能已经独立决定以不同的顺序解析语法,因此期望编写的子句顺序完全遵守处理 100% 的顺序可能仍然没有意义。时间。类似的事情也是如此
CASE
。例如,我们已经在此站点上看到了场景,例如,以前认为CASE
总是按顺序处理和短路的神话是错误的。这也延伸到其他常见的信念,例如 SQL Server 按编写顺序评估连接,从左到右短路WHERE
子句,或者处理 CTE 一次或按特定顺序处理,即使它们被多次引用。产品可以自由优化他们认为合适的方式,即使它不能准确反映您所说的查询应该如何以声明方式工作。在Entity SQL中,在某些情况下,您可以在查询的其他位置使用来自表达式的别名:
请注意,在这里您必须在子句中定义表达式
GROUP BY
才能在子句中使用它SELECT
。显然可以在 SQL 查询中允许一些这种别名作为可重用表达式。