我们在 SQL Server 中遇到了一个有趣的问题。考虑以下重现示例:
CREATE TABLE #test (s_guid uniqueidentifier PRIMARY KEY);
INSERT INTO #test (s_guid) VALUES ('7E28EFF8-A80A-45E4-BFE0-C13989D69618');
SELECT s_guid FROM #test
WHERE s_guid = '7E28EFF8-A80A-45E4-BFE0-C13989D69618'
AND s_guid <> NEWID();
DROP TABLE #test;
请暂时忘记s_guid <> NEWID()
条件似乎完全没用——这只是一个最小的重现示例。由于NEWID()
匹配某个给定常量值的概率极小,因此每次都应评估为 TRUE。
但事实并非如此。运行此查询通常返回 1 行,但有时(非常频繁,10 次中超过 1 次)返回 0 行。我已经在我的系统上使用 SQL Server 2008 复制了它,您可以使用上面链接的小提琴(SQL Server 2014)在线复制它。
查看执行计划表明查询分析器显然将条件拆分为s_guid < NEWID() OR s_guid > NEWID()
:
...这完全解释了为什么它有时会失败(如果第一个生成的 ID 小于给定的 ID 而第二个大于给定的 ID)。
是否允许 SQL Server 计算A <> B
为A < B OR A > B
,即使其中一个表达式是不确定的?如果是,它记录在哪里?还是我们发现了错误?
有趣的是,AND NOT (s_guid = NEWID())
产生相同的执行计划(和相同的随机结果)。
当开发人员想要有选择地排除特定行并使用时,我们发现了这个问题:
s_guid <> ISNULL(@someParameter, NEWID())
作为“捷径”:
(@someParameter IS NULL OR s_guid <> @someParameter)
我正在寻找文档和/或错误确认。该代码并不是所有相关的,因此不需要解决方法。
这是一个有点争议的点,答案是有条件的“是”。
我所知道的最好的讨论是在回答 Itzik Ben-Gan 的 Connect 错误报告Bug with NEWID and Table Expressions时给出的,该讨论已关闭,因为无法修复。Connect 已经停用,所以那里的链接指向一个网络档案。可悲的是,许多有用的材料因 Connect 的消亡而丢失(或变得更难找到)。无论如何,微软的 Jim Hogg 最有用的引述有:
随着时间的推移,这方面的行为发生变化的一个例子是NULLIF 不能正确地使用非确定性函数,例如 RAND()。还有其他类似的情况,例如
COALESCE
使用子查询可能会产生意想不到的结果,这些情况也在逐步解决。吉姆继续说:
这是规范化的结果,它发生在查询编译的早期。两个表达式都编译为完全相同的规范化形式,因此生成了相同的执行计划。
这在此处记录(有点):
用户定义的函数
这不是查询计划将多次执行 NEWID() 并更改结果的唯一查询形式。这令人困惑,但实际上对于 NEWID() 用于密钥生成和随机排序非常重要。
最令人困惑的是,并非所有非确定性函数的实际行为都是这样的。例如 RAND() 和 GETDATE() 每次查询只执行一次。
对于它的价值,如果你看一下这个旧的 SQL 92 标准文档,关于不等式的要求在“
8.2 <comparison predicate>
”部分描述如下:注意:为了完整性,我包括了 7b 和 7h,因为他们谈论的是
<>
比较——我认为行值构造函数与多个值的比较不会在 T-SQL 中实现,除非我只是严重误解了这句话的意思——这是很有可能的这是一堆令人困惑的垃圾。但是如果你想让垃圾箱继续潜水......
我认为1.ii 是适用于这种情况的项目,因为我们正在比较“行值构造函数元素”的值。
基本上,如果 X 和 Y 表示的值不相等,则表示
X <> Y
为真。由于是对该谓词的逻辑等效重写,因此优化器使用它非常酷。X < Y OR X > Y
<>
该标准没有对与比较运算符任一侧的行值构造函数元素的确定性(或您得到的任何东西)相关的定义施加任何限制。用户代码有责任处理一侧的值表达式可能不确定的事实。