使用`newid()`将单行子查询与一列交叉连接会导致每一行具有不同的GUID

时间:2018-07-16 00:30:53

标签: sql sql-server uuid

摘要

类似的查询

SELECT *
       FROM elbat t
            CROSS JOIN (SELECT newid() guid) x;
SQL Server中的

产生一个结果,其中每一行具有不同的GUID,而不是每一行在整个结果中具有相同的GUID。如何对结果的所有行都使用一个GUID(不使用变量表或(临时)表)?

设置

在SQL Server数据库中考虑下表。

CREATE TABLE elbat
             (id integer);

INSERT INTO elbat
            VALUES (1);
INSERT INTO elbat
            VALUES (2);
INSERT INTO elbat
            VALUES (3);
INSERT INTO elbat
            VALUES (4);
INSERT INTO elbat
            VALUES (5);
INSERT INTO elbat
            VALUES (6);

让我们运行以下查询。

SELECT *
       FROM elbat t
            CROSS JOIN (SELECT newid() guid) x;

这里有一个db<>fiddle和一个SQL Fiddle,可以看到它的实际效果。

问题

令我惊讶的是,结果每一行都有不同的GUID。例如:

 id | guid                                
 -: | :-----------------------------------
  1 | ad146af7-9ebd-4521-a440-47c7dea6a1d4
  2 | ce24fbb8-af64-480c-8c46-1e03187642c5
  3 | 14509451-9b1d-49e9-8da2-c691947ae805
  4 | 37a86339-e352-486f-b541-92798540599f
  5 | cbee1a8e-02ce-4915-8d2c-ef5db299d8c8
  6 | d491275b-4ebb-461b-94e2-93b47e7d2348

那让我感到困惑。我希望每一行在整个结果集中都具有相同的GUID。例如:

 id | guid                                
 -: | :-----------------------------------
  1 | cbee1a8e-02ce-4915-8d2c-ef5db299d8c8
  2 | cbee1a8e-02ce-4915-8d2c-ef5db299d8c8
  3 | cbee1a8e-02ce-4915-8d2c-ef5db299d8c8
  4 | cbee1a8e-02ce-4915-8d2c-ef5db299d8c8
  5 | cbee1a8e-02ce-4915-8d2c-ef5db299d8c8
  6 | cbee1a8e-02ce-4915-8d2c-ef5db299d8c8

我当然知道,GUID会随着呼叫的变化而变化。但是我不明白为什么当我交叉加入单个GUID时,行与行之间会发生变化,并且没有将newid()调用放在预计列的列表中。

其他信息

我尝试使用小提琴平台上的所有可用版本以及本地Microsoft SQL Server 2014(12.0.2269.0(X64),Express)进行此操作。结果到处都是一样的(只是GUID当然会改变)。

质疑我对联接的理解,我还用等效的设置和查询对其他DBMS进行了一些测试。

  • (DDL和DML都保持不变。)
  • Postgres:

    SELECT *
           FROM elbat t
                CROSS JOIN (SELECT uuid_generate_v4() guid) x;
    

    SQL Fiddle

  • Oracle:

    SELECT *
           FROM elbat t
                CROSS JOIN (SELECT sys_guid() guid
                                   FROM dual) x;
    

    db<>fiddle

  • MariaDB:

    SELECT *
           FROM elbat t
                CROSS JOIN (SELECT uuid() guid) x;
    

    db<>fiddle

  • MySQL:

    SELECT *
           FROM elbat t
                CROSS JOIN (SELECT uuid() guid) x;
    

    SQL Fiddle

所有其他这些DBMS都会产生我实际期望的结果-结果的所有行中都有一个通用的GUID。

我也玩过更改查询。无济于事。

  • 将子查询放在CTE中。
  • 我尝试使用SELECT和主键从子查询的物理表中进行选择,而不是从没有FROM的{​​{1}}中进行选择。
  • 使用隐式交叉联接(TOP)。
  • 使用FROM elbat, (SELECT newid() ...)

在查找文档时,我找不到任何地方都涉及这种行为。

问题

为什么SQL Server的行为与所有其他(经过测试的)DBMS(在这方面)不同,并且有办法获得预期的结果(不使用变量表或(临时)表)?

(注意:我知道我可以使用用CROSS APPLY初始化的变量,并将其保存在投影列中。但是实际上,当我尝试避免使用此类变量时,问题就出现了。我实际上想寻找一个"Order table randomly but with exceptions"的无变量,仅查询解决方案。)

3 个答案:

答案 0 :(得分:4)

我真的对SQL Server的行为感到惊讶。我没有意识到它会一遍又一遍地重新评估这些子查询。我怀疑原因是一种优化:cross join中的表达式实际上已移至读取数据的节点,因此该函数被一遍又一遍地调用。

无论如何,我认为这是错误的。这样的优化应该认识到newid()是一个易失函数,并相应地进行调整。

经过一些试验,我发现子查询中的order by确实仅对其进行了一次评估。所以,这就是您想要的:

select *
from elbat cross join
     (select top (1) newid() as guid
      order by guid
     ) x;

符合您期望的另一版本:

select *
from elbat cross join
     (select max(newid()) as guid
     ) x;

顺便说一下,此后一版本也可以在select中使用:

select *, (select max(newid())) as guid
from elbat ;

在这种情况下,我希望子查询的每一行都会被评估一次。走吧。

答案 1 :(得分:3)

这里是指向Connect问题的存档的链接(现在已经关闭ala),讨论是否要“修复”此行为。此处转载以保留信息。这是SQL Dev团队在关闭报告为“无法解决”问题上的反馈:

  

“结束循环。 。 。我已经与开发人员讨论了这个问题   球队。最终我们决定不更改当前行为,   出于以下原因:

     

1)优化器不保证执行时间或执行次数   标量函数。这是一个悠久的宗旨。这是   基本的“余地”,使优化程序有足够的自由来获取   大大改善了查询计划的执行。

     

2)这种“每行一次的行为”不是一个新问题,尽管它不是   广泛讨论。我们开始调整育空地区的行为   发布。但是在所有情况下精确定位都非常困难,   到底是什么意思!例如,是否适用于临时行   计算出最终结果的“途中”? -在这种情况下   显然取决于选择的计划。还是仅适用于行   最终会出现在完整结果中吗? -讨厌   递归在这里进行,我相信您会同意的!

     

3)正如我之前提到的,我们默认为“优化性能”-   这对于99%的案例来说是好的。 1%可能会改变的情况   结果相当容易发现-副作用“功能”,例如   NEWID-易于“修复”(因此,交易性能)。这个   长期以来一直默认为“优化性能”   公认。 (是的,这不是编译器选择的立场   常规的编程语言,但事实如此。

     

因此,我们的建议是:

     

a)避免依赖非保证的时间和执行次数   语义。

     

b)避免在表表达式中深入使用NEWID()。

     

c)使用OPTION强制执行特定行为(交易性能)

     

希望此说明有助于阐明我们关闭此错误的原因   为“无法解决”。

     

谢谢

     

吉姆”

https://web.archive.org/web/20160626085155/https://connect.microsoft.com/SQLServer/feedbackdetail/view/350485/bug-with-newid-and-table-expressions

答案 2 :(得分:1)

cte(无递归)只是使带有子查询的查询对我们人类更易读的一种方法。 SQL Server似乎太聪明了,无论我们如何编写查询,都只会添加一个计算列。但是通过这种方式,我使用外部联接欺骗了他,并使用嵌套循环使他联接:

WITH x (guid) AS (
  SELECT newid()
)
SELECT *
FROM elbat t
  RIGHT JOIN x ON x.guid IS NOT NULL;