Неожиданные результаты со случайными числами и типами соединений

16

У меня есть простой скрипт, который получает четыре случайных числа (от 1 до 4), а затем присоединяется обратно, чтобы получить соответствующий номер database_id. Когда я запускаю скрипт с LEFT JOIN, я каждый раз получаю четыре строки назад (ожидаемый результат). Однако, когда я запускаю его с INNER JOIN, я получаю различное количество строк - иногда две, иногда восемь.

Логически, не должно быть никакой разницы, потому что я знаю, что строки с database_ids 1-4 существуют в sys.databases. И поскольку мы выбираем из таблицы случайных чисел с четырьмя строками (в отличие от присоединения к ней), никогда не должно быть больше четырех строк.

Это происходит как в 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;
Дуг Лейн
источник
3
Другой способ получить всегда 4 строки: SELECT TOP (4) d.database_id FROM sys.databases AS d CROSS JOIN (VALUES (1),(2),(3),(4)) AS multi (i) WHERE d.database_id <= 4 ORDER BY CHECKSUM(NEWID()) ;я думаю, что это работает нормально, потому что нет соединения со значением недетерминированной функции.
ypercubeᵀᴹ

Ответы:

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)))

До сих пор выясняю, почему он так поздно это делает, но сейчас читаю этот пост Пола Уайта ( 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), мой план будет выглядеть лучше, но Compute Scalar был исключен, так что я поближе посмотрел

введите описание изображения здесь

Это вычисление выражения для случайного числа после объединения. Будь это ожидаемо, я все же оставляю внутренним мастерам на сайте, но, по крайней мере, поэтому вы получаете переменные результаты в своем объединении.

2014

Для тех, кто играет дома, вышеуказанные планы запросов были сгенерированы из экземпляра 2008 R2. Планы на 2014 год выглядят иначе, но операция Compute Scalar остается после объединения.

Это план запроса на 2014 год с использованием константного выражения.

введите описание изображения здесь

Это план запроса для экземпляра 2014 с использованием выражения newid.

введите описание изображения здесь

Это, по-видимому, является намерением, проблема подключения здесь. Спасибо @paulWhite за знание того, что существовало.

billinkc
источник
1
Правильно, именно так и происходит, но это определенно не ожидается. Результаты не соответствуют T-SQL, который передается, и, следовательно, вопрос.
Брент Озар
Даже замена случайного числа статическим 1 дает оператору соединения без предиката соединения
Джеймс Андерсон,
Похоже, вы к чему-то. Даже использование OPTION (FORCE ORDER) не меняет поведение - случайное число все еще вычисляется последним ...
Иеремия Пешка
При удалении TVF sys.database следующий план аналогичен: gist.github.com/peschkaj/cebdeb98daa4d1f08dc5
Иеремия Пешка,
Это звучит как проблема приоритета оператора
Джеймс Андерсон