摘要
类似的查询
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进行了一些测试。
Postgres:
SELECT *
FROM elbat t
CROSS JOIN (SELECT uuid_generate_v4() guid) x;
Oracle:
SELECT *
FROM elbat t
CROSS JOIN (SELECT sys_guid() guid
FROM dual) x;
MariaDB:
SELECT *
FROM elbat t
CROSS JOIN (SELECT uuid() guid) x;
MySQL:
SELECT *
FROM elbat t
CROSS JOIN (SELECT uuid() guid) x;
所有其他这些DBMS都会产生我实际期望的结果-结果的所有行中都有一个通用的GUID。
我也玩过更改查询。无济于事。
SELECT
和主键从子查询的物理表中进行选择,而不是从没有FROM
的{{1}}中进行选择。TOP
)。FROM elbat, (SELECT newid() ...)
。在查找文档时,我找不到任何地方都涉及这种行为。
问题
为什么SQL Server的行为与所有其他(经过测试的)DBMS(在这方面)不同,并且有办法获得预期的结果(不使用变量表或(临时)表)?
(注意:我知道我可以使用用CROSS APPLY
初始化的变量,并将其保存在投影列中。但是实际上,当我尝试避免使用此类变量时,问题就出现了。我实际上想寻找一个"Order table randomly but with exceptions"的无变量,仅查询解决方案。)
答案 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强制执行特定行为(交易性能)
希望此说明有助于阐明我们关闭此错误的原因 为“无法解决”。
谢谢
吉姆”
答案 2 :(得分:1)
cte(无递归)只是使带有子查询的查询对我们人类更易读的一种方法。 SQL Server似乎太聪明了,无论我们如何编写查询,都只会添加一个计算列。但是通过这种方式,我使用外部联接欺骗了他,并使用嵌套循环使他联接:
WITH x (guid) AS (
SELECT newid()
)
SELECT *
FROM elbat t
RIGHT JOIN x ON x.guid IS NOT NULL;