Как я могу получить случайное число, сгенерированное в CTE, чтобы оно не менялось в JOIN? - PullRequest
3 голосов
/ 20 марта 2019

Проблема

Я генерирую случайное число для каждой строки в таблице #Table_1 в CTE, используя эту технику .Затем я присоединяюсь к результатам CTE на другом столе, #Table_2.Вместо получения случайного числа для каждой строки в #Table_1, я получаю новое случайное число для каждой результирующей строки в соединении!

CREATE TABLE #Table_1 (Id INT)

CREATE TABLE #Table_2 (MyId INT, ParentId INT)

INSERT INTO #Table_1
VALUES (1), (2), (3)

INSERT INTO #Table_2
VALUES (1, 1), (2, 1), (3, 1), (4, 1), (1, 2), (2, 2), (3, 2), (1, 3)


;WITH RandomCTE AS
(
    SELECT Id, (ABS(CHECKSUM(NewId())) % 5)RandomNumber
    FROM #Table_1
)
SELECT r.Id, t.MyId, r.RandomNumber
FROM RandomCTE r
INNER JOIN #Table_2 t
    ON r.Id = t.ParentId

Результаты

Id          MyId        RandomNumber
----------- ----------- ------------
1           1           1
1           2           2
1           3           0
1           4           3
2           1           4
2           2           0
2           3           0
3           1           3

Желаемые результаты

Id          MyId        RandomNumber
----------- ----------- ------------
1           1           1
1           2           1
1           3           1
1           4           1
2           1           4
2           2           4
2           3           4
3           1           3

Что я пытался

Я пытался скрыть логику генерации случайных чиселиз оптимизатора, приведя случайное число к VARCHAR, но это не сработало.

Что я не хочу делать

Я бы хотелИзбегайте использования временной таблицы для хранения результатов CTE.

Как создать случайное число для таблицы и сохранить это случайное число в соединении без использования временного хранилища?

Ответы [ 3 ]

2 голосов
/ 20 марта 2019

Это, кажется, делает трюк:

WITH CTE AS(
    SELECT Id, (ABS(CHECKSUM(NewId())) % 5)RandomNumber
    FROM #Table_1),
RandomCTE AS(
    SELECT Id,
           RandomNumber
    FROM CTE
    GROUP BY ID, RandomNumber)
SELECT *
FROM RandomCTE r
INNER JOIN #Table_2 t
    ON r.Id = t.ParentId;

Похоже, что SQL Server осознает, что в момент нахождения вне CTE, RandomNumber фактически просто NEWID() с некоторыми дополнительнымифункции обернуты вокруг него ( DB <> Fiddle ), и, следовательно, он по-прежнему генерирует уникальный идентификатор для каждой строки.Следовательно, предложение GROUP BY во втором CTE заставляет механизм данных определять значение RandomNumber, чтобы оно могло выполнить GROUP BY.

.
1 голос
/ 21 марта 2019

За цитату в этот ответ

Оптимизатор не гарантирует время или количество выполнений скалярные функции. Это давно установленный принцип. Это фундаментальная «свобода действий», позволяющая оптимизатору получить достаточно свободы значительные улучшения в выполнении плана запроса.

Если для вашего приложения важно, чтобы случайное число оценивалось один раз и только один раз, вы должны рассчитать его заранее и сохранить во временной таблице.

Все остальное не гарантируется и поэтому безответственно добавлять в базу кода вашего приложения - поскольку даже если оно работает сейчас, оно может сломаться в результате изменения схемы / изменения плана выполнения / обновления версии / установки CU.

Например, ответ Ламу прерывается, если к #Table_1 (Id)* 1014 добавлен уникальный индекс

0 голосов
/ 20 марта 2019

Как насчет того, чтобы вообще не использовать реальное случайное число?Используйте rand() с начальным числом:

WITH RandomCTE AS (
      SELECT Id,
             CONVERT(INT, RAND(ROW_NUMBER() OVER (ORDER BY NEWID()) * 999999) * 5) as RandomNumber
      FROM #Table_1
     )
SELECT r.Id, t.MyId, r.RandomNumber
FROM RandomCTE rINNER JOIN
     #Table_2 t
     ON r.Id = t.ParentId;

Аргумент начального числа к rand() довольно ужасен.Значения начального числа рядом друг с другом дают аналогичные начальные значения, что является причиной умножения.

Здесь - это db <> fiddle.

...