sql - 没有 ORDER BY 问题的 SELECT TOP 1 忽略 WHERE 语句

标签 sql sql-server

我们有一个包含唯一代码的表格。为了生成一个新的唯一代码,我们使用了一种在下面的 SQL 语句中找到的方法,并发现了 NOT EXISTS 语句似乎允许存在的行的情况。

没有并发问题,因为这已在沙箱中使用针对 SQL Server 2016 运行的单个查询得到证明。如果我们放置 ORDER BY 语句,它会突然按预期运行。看起来好像没有 ORDER BY 这个查询有条件地忽略了 WHERE 子句。如果所有代码发生冲突,我希望 @code 为 NULL 或保持其初始状态 0。

DECLARE @code int = 0;

    select  @code = Code from (
        SELECT top 1 randoms.Code
        FROM (
            VALUES 
            (CAST((abs(CHECKSUM(newid())) % 1000000) AS INT)),
            (CAST((abs(CHECKSUM(newid())) % 1000000) AS INT)),
            (CAST((abs(CHECKSUM(newid())) % 1000000) AS INT)),
            (CAST((abs(CHECKSUM(newid())) % 1000000) AS INT)),
            (CAST((abs(CHECKSUM(newid())) % 1000000) AS INT))
        ) randoms (Code)
        WHERE NOT EXISTS (SELECT 1 FROM TEST_Codes uc WHERE uc.Code = randoms.Code)
    ) c;


    SELECT 
        c.code,
        ud.*
    FROM (VALUES (@code)) as c(Code)
    LEFT OUTER JOIN TEST_Codes ud
        ON ud.Code = c.Code

此语句将允许返回重复项,这由于 WHERE NOT EXISTS 语句而令人费解。

如果我们将 View c 的定义更改为 ) c ORDER BY c.Code 它突然就可以工作了。这是为什么?

最佳答案

Sql Server 不保证它将执行计算标量和类似表达式的次数。可能 where 中的引用使用的值与所选值不同,但是当您添加订单时,它会具体化它并且每行只计算一次。

如果您是 2014 年或以上,您可以在 query_trace_column_values 上使用扩展事件 session 看到这一切发生。

DECLARE @TestCodes TABLE(Code int)
dbcc traceon(2486);
set statistics xml on;

    select  Code from (
        SELECT randoms.Code
        FROM (
            VALUES 
            (CAST((abs(CHECKSUM(newid())) % 1000000) AS INT)),
            (CAST((abs(CHECKSUM(newid())) % 1000000) AS INT)),
            (CAST((abs(CHECKSUM(newid())) % 1000000) AS INT)),
            (CAST((abs(CHECKSUM(newid())) % 1000000) AS INT)),
            (CAST((abs(CHECKSUM(newid())) % 1000000) AS INT))
        ) randoms (Code)
        WHERE NOT EXISTS (SELECT 1 FROM @TestCodes uc WHERE uc.Code = randoms.Code)
    ) c
     option(recompile);


set statistics xml off;
dbcc traceoff(2486);

enter image description here

enter image description here

Union1005 列是右上角常量扫描的输出。它也在连接谓词中再次被引用。此时它被重新评估并返回一个不同的数字。

您也许可以修改查询并让它只被评估一次,但没有任何保证。唯一 100% 安全的方法是在进行检查之前预先具体化随机数(例如放入临时表),这样您就可以保证它们不会被重新计算和更改。


下面是一个使用 SQL 进行黑客攻击以获得不确定结果的示例。我不会使用它,因为它有缺点,它仍然不能保证任何东西,而且即使它有效,如果你选择前 1 个,你的“随机”数字将不再分布良好。它引入了对较低数字的偏见。

select  Code from (
    SELECT TOP 5 randoms.Code
    FROM (
        VALUES 
        (CAST((abs(CHECKSUM(newid())) % 1000000) AS INT)),
        (CAST((abs(CHECKSUM(newid())) % 1000000) AS INT)),
        (CAST((abs(CHECKSUM(newid())) % 1000000) AS INT)),
        (CAST((abs(CHECKSUM(newid())) % 1000000) AS INT)),
        (CAST((abs(CHECKSUM(newid())) % 1000000) AS INT))
    ) randoms (Code)
    order by Code
    ) T
    WHERE NOT EXISTS (SELECT 1 FROM @TestCodes uc WHERE uc.Code = T.Code)

enter image description here

这实现了它,并且排序输出的值与嵌套循环谓词中使用的值相同。

enter image description here

关于sql - 没有 ORDER BY 问题的 SELECT TOP 1 忽略 WHERE 语句,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42988853/

相关文章:

java - 在 JDBI dropwizard 中使用自定义 where 条件

mysql - SQL 服务器 : How to override Error 0xc02020a1: Data Flow Task 1: Data conversion failed

sql - 如何执行表值函数

mysql - SQL中,如何计算最大并发用户数

c# - C#中的SQL更新语句

sql-server - ssis 子包中的开始和结束日期时间

sql - 如何使用 WHERE 子句优化 SQL 查询

sql-server - 即使 SET PARSEONLY 设置为 ON,SQL Server 也会插入值

sql-server - 从 Windows Azure 连接到远程 SQL Server 2008

sql - 在 PostgreSQL 中从字符串末尾查找子字符串位置