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

Sha*_*una 16 sql-server alias

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

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

作为一名程序员(不是 DBA),我发现这种行为有些令人困惑,因为在我看来它在很大程度上违背了拥有列别名的目的(或者,至少,如果列别名是在查询执行中更早地解析),因为您可以实际使用别名的唯一地方是在 ORDER BY 中。作为一名程序员,它似乎错过了使查询更强大、更方便和 DRY 的巨大机会。

看起来这是一个如此明显的问题,它有理由认为,除了 SELECT 和 ORDER BY 之外,还有其他原因决定不允许列别名,但这些原因是什么?

Eri*_*ikE 19

概括

没有合乎逻辑的理由不能这样做,但好处很小,而且有一些陷阱可能不会立即显现。

研究成果

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

最初发明 SQL 时,它在 SELECT 子句中没有别名。这是一个严重的缺点,当 ANSI 在 1986 年左右对该语言进行标准化时,该缺点得到了纠正。

该语言旨在“非程序化”——换句话说,描述您想要的数据而不指定如何找到它。因此,据我所知,SQL 实现没有理由不能在处理之前解析整个查询,并允许在任何地方定义别名并在任何地方使用。例如,我看不出以下查询无效的任何原因:

select name, salary + bonus as pay
from employee
where pay > 100000
Run Code Online (Sandbox Code Playgroud)

虽然我认为这是一个合理的查询,但一些基于 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
Run Code Online (Sandbox Code Playgroud)

如果 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
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

CROSS APPLY/OUTER APPLY 是 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
Run Code Online (Sandbox Code Playgroud)

这有两件事:

  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 是您以自己想要的方式进行干燥的方式。


Aar*_*and 14

我不能告诉你确切的原因,但我会告诉你重复表达式有一些解决方法,例如使用 CTE、子查询、派生表等来避免重复。

如果您显示一个带有重复表达式的查询,我们可能会向您展示如何重写它,以便表达式只列出一次。然而,这只是降低了写入/读取查询的复杂性,不太可能对效率产生太大影响。SQL Server 通常非常擅长识别重复的表达式,并且不会执行两次该工作。也有相反的例外,但是当您实际观察到这种情况发生时,您应该只关心效率。我怀疑您编写的大多数重复表达式实际上都被折叠为计划中的一个操作。

话虽如此,我还将重复我对这个问题的部分回答:

https://dba.stackexchange.com/questions/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表连接,可能最终会被一个SELECTUPDATE或者DELETE,记住这些引擎的代码最初写回天,当字符串解析是相当昂贵的。

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

对于诸如CASE. 例如,我们在此站点上看到了一些场景,例如,以前相信的CASE总是按顺序处理和短路的神话是错误的。这也扩展到其他常见的信念,例如 SQL Server 按照它们编写的顺序评估连接,从左到右短路WHERE子句,或者处理一次或以特定顺序处理 CTE,即使它们被多次引用。产品可以自由地优化它们认为合适的方式,即使它不能准确反映您声明查询应该如何以声明方式工作。

  • 另请注意,在查询的不同部分使用或不使用别名的能力是由解析器强制执行的,而不是由优化器或执行引擎强制执行的。引擎实际执行查询的方式不一定反映影响语法的限制。 (2认同)

归档时间:

查看次数:

3293 次

最近记录:

10 年,10 月 前