Несподівані результати з випадковими числами та типами приєднання


16

У мене простий скрипт, який отримує чотири випадкових числа (від 1 до 4), а потім приєднується назад, щоб отримати відповідне число баз даних_id. Коли я запускаю сценарій з НАЛЯГОЮ ПРИЄДНАЙТЕСЯ, я отримую чотири ряди назад кожен раз (очікуваний результат). Однак, коли я запускаю її за допомогою ВНУТРІШНОГО ПРИЄДНАННЯ, я отримую різну кількість рядків - іноді два, іноді вісім.

За логікою, різниці не повинно бути, тому що я знаю, що рядки з 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), мій план виглядає краще, але Скаратор обчислень був усунутий, так що змусило мене подивитися ближче

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

Це обчислення виразу для випадкового числа після з'єднання. Незважаючи на те, що очікується, я все одно залишаюсь на внутрішніх майстрах на сайті, але принаймні, тому ви отримуєте різні результати під час свого приєднання.

2014 рік

Для тих, хто грає вдома, вищевказані плани запитів були створені з екземпляра R2 2008 року. Плани на 2014 рік виглядають по-різному, але операція "Обчислювальна скалярність" залишається після об'єднання.

Це план запитів на 2014 рік, використовуючи постійний вираз

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

Це план запитів для екземпляра 2014 року з використанням виразу newid.

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

Це, мабуть, дизайн, підключення тут. Дякуємо @paulWhite за те, що він знав, що існує.


1
Правильно, саме так - це і відбувається, але точно не очікується. Результати не відповідають T-SQL, який передається, і, таким чином, питання.
Брент Озар

Навіть заміна випадкового числа статичним 1 дає оператору приєднання без присудка приєднання
Джеймс Андерсон

Схоже, ти щось робиш. Навіть використання OPTION (FORCE ORDER) не змінює поведінку - випадкове число все ще обчислюється останнім ...
Jeremiah Peschka

Видалення баз даних sys.databa TVF створює той самий план: gist.github.com/peschkaj/cebdeb98daa4d1f08dc5
Єремія Пешка

Це звучить як випуск пріоритету оператора
Джеймс Андерсон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.