带有随机数和联接类型的意外结果


16

我有一个简单的脚本,该脚本获取四个随机数(1到4),然后重新加入以获取匹配的database_id数。当我使用LEFT JOIN运行脚本时,每次都会返回四行(预期结果)。但是,当我使用INNER JOIN运行它时,我得到的行数不尽相同-有时是两行,有时是八行。

从逻辑上讲,应该没有什么区别,因为我知道sys.databases中存在具有database_ids 1-4的行。并且由于我们从具有四行的随机数表中选择(而不是与之连接),因此返回的行数绝不能超过四行。

SQL Server 2012和2014中都会发生这种情况。是什么导致INNER JOIN返回不同数量的行?

/* Works as expected -- always four rows */

SELECT rando.RandomNumber, d.database_id
FROM 
  (SELECT 1 + ABS(CHECKSUM(NEWID())) % (4) AS RandomNumber 
   FROM sys.databases WHERE database_id <= 4) AS rando
LEFT JOIN sys.databases d ON rando.RandomNumber = d.database_id;


/* Returns a varying number of rows */

SELECT rando.RandomNumber, d.database_id
FROM 
  (SELECT 1 + ABS(CHECKSUM(NEWID())) % (4) AS RandomNumber 
   FROM sys.databases WHERE database_id <= 4) AS rando
INNER JOIN sys.databases d ON rando.RandomNumber = d.database_id;

/* Also returns a varying number of rows */

WITH rando AS (
  SELECT 1 + ABS(CHECKSUM(NEWID())) % (4) AS RandomNumber
  FROM sys.databases WHERE database_id <= 4
)

SELECT r.RandomNumber, d.database_id
FROM rando AS r
INNER JOIN sys.databases d ON r.RandomNumber = d.database_id;

Answers:


9

通过添加其他SELECT,它将计算标量评估推入计划中更深的位置,并给出连接谓词,顶部的计算标量然后引用较早的一个。

SELECT rando.RandomNumber, d.database_id
FROM 
  (SELECT ( SELECT 1 + ABS(CHECKSUM(NEWID())) % (4)) AS RandomNumber 
   FROM sys.databases WHERE database_id <= 4) AS rando
INNER JOIN sys.databases d ON rando.RandomNumber = d.database_id

|--Compute Scalar(DEFINE:([Expr1071]=[Expr1070]))

|--Compute Scalar(DEFINE:([Expr1070]=(1)+abs(checksum(newid()))%(4)))

仍在研究为什么要等得这么晚才这样做,但目前正在阅读Paul White撰写的这篇文章(https://sql.kiwi/2012/09/compute-scalars-expressions-and-execution-plan-performance.html) 。也许与NEWID不是确定性这一事实有关?


12

这可能会提供一些见解,直到网站上的一位聪明人加入。

我将随机结果放入临时表中,无论连接类型如何,我始终获得4个结果。

/* Works as expected -- always four rows */

DECLARE @Rando table
(
    RandomNumber int
);

INSERT INTO
    @Rando
(
    RandomNumber
)
-- This generates 4 random numbers from 1 to 4, endpoints inclusive
SELECT
    1 + ABS(CHECKSUM(NEWID())) % (4) AS RandomNumber
FROM
    sys.databases
WHERE
    database_id <= 4;

SELECT
    *
FROM
    @Rando AS R;

SELECT
    rando.RandomNumber
,   d.database_id
FROM 
    @Rando AS rando
    LEFT JOIN 
        sys.databases d 
        ON rando.RandomNumber = d.database_id
ORDER BY 1,2;


/* Returns a varying number of rows */

SELECT rando.RandomNumber, d.database_id
FROM 
    @Rando AS rando
    INNER JOIN 
        sys.databases d 
        ON rando.RandomNumber = d.database_id
ORDER BY 1,2;

/* Also returns a varying number of rows */

WITH rando AS 
(
    SELECT * FROM @Rando AS rando
)
SELECT r.RandomNumber, d.database_id
FROM 
    rando AS r
    INNER JOIN 
        sys.databases d 
        ON r.RandomNumber = d.database_id
ORDER BY 1,2;

如果我比较第二个查询和带有表变量的变体之间的查询计划,则可以看到两者之间有一定的区别。红色的X No Join Predicate对我的穴居开发人员的大脑似乎真的很奇怪

在此处输入图片说明

如果我将查询的随机位消除为一个常数 1 % (4),则我的计划看起来更好,但消除了计算标量,因此使我看起来更近了

在此处输入图片说明

它计算加入后的随机数表达式。不管这是意料之中的,我仍然会留在网站上的内部向导中,但是至少这就是为什么您在联接中得到可变结果的原因。

2014年

对于那些在家中玩耍的人,以上查询计划是从2008 R2实例生成的。2014年的计划看起来有所不同,但加入后仍保留“计算标量”操作。

这是使用常量表达式的2014年查询计划

在此处输入图片说明

这是使用newid表达式的2014实例的查询计划。

在此处输入图片说明

这显然是设计使然,此处连接问题。感谢@paulWhite知道它的存在。


1
没错,正是-这就是正在发生的事情,但这绝对不是预期的。结果与传入的T-SQL不匹配,因此不存在问题。
布伦特·奥扎尔

即使使用静态1替换随机数,连接运算符也没有连接谓词
James Anderson

看来您正在做某事。即使使用OPTION(FORCE ORDER)也不会改变行为-随机数仍会最后计算出来……
Jeremiah Peschka 2014年

删除sys.databases TVF,以下将产生相同的计划: gist.github.com/peschkaj/cebdeb98daa4d1f08dc5
Jeremiah Peschka 2014年

这听起来像是运算符的优先级问题
James Anderson
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.