Случайный выбор в SQL Server - PullRequest
0 голосов
/ 25 июня 2018

У меня есть два набора, и для каждого значения в первом наборе я хочу применить несколько случайных значений из второго.Подход, который я выбрал, использует выбор первого с перекрестным применением со второго.Упрощенный MWE выглядит следующим образом:

DROP TABLE IF EXISTS #S;
CREATE TABLE #S (c CHAR(1));
INSERT INTO #S VALUES ('A'), ('B');
DROP TABLE IF EXISTS #T;
WITH idGen(id) AS (
    SELECT 1 
    UNION ALL 
    SELECT id + 1 FROM idGen WHERE id < 1000
) 
SELECT id INTO #T FROM idGen OPTION(MAXRECURSION 0);
DROP TABLE IF EXISTS #R;
SELECT c, id INTO #R FROM #S
CROSS APPLY (
    SELECT id, ROW_NUMBER() OVER (
        /* 
        -- this gives 100% overlap
        PARTITION BY c
        ORDER BY RAND(CHECKSUM(NEWID()))
        */
        -- this gives the expected ~10% overlap
        ORDER BY RAND(CHECKSUM(NEWID()) + CHECKSUM(c))
    ) AS R
    FROM #T 
) t
WHERE t.R <= 100;
SELECT COUNT(*) AS PercentOverlap -- ~10%
FROM #R rA JOIN #R rB
ON rB.id = rA.id AND rB.c = 'B'
WHERE rA.c = 'A';

Хотя это решение работает, мне интересно, почему переход на (закомментированный) метод разделения не работает?Кроме того, есть ли какие-то предостережения, использующие это решение, поскольку кажется, что добавление двух контрольных сумм кажется грязным?

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

1 Ответ

0 голосов
/ 25 июня 2018

RAND() функция - постоянная времени выполнения в SQL Server. Это означает, что обычно оценивается один раз для запроса. Когда вы передаете значение в RAND, это значение служит начальным начальным числом.

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

Также нет смысла переносить NEWID() в CHECKSUM() и RAND(). Простого NEWID() достаточно. Или, что еще лучше, функция, предназначенная для создания случайного числа, например CRYPT_GEN_RANDOM()

Любая версия вашего запроса выглядит немного странно. Я бы написал так:

SELECT c, id INTO #R 
FROM #S
CROSS APPLY 
(
    SELECT TOP(100) -- or #S.SomeField instead of 100
        id
    FROM #T 
    ORDER BY CRYPT_GEN_RANDOM(4) -- generate 4 random bytes, usually it is enough
) AS t
;

Это дает 100 случайных строк из #T для каждой строки из #S.

На самом деле, приведенный выше запрос не очень хорош. Оптимизатор снова видит, что внутренний запрос (внутри CROSS APPLY) не зависит от внешнего запроса, и оптимизирует его. Конечным результатом является то, что случайные строки выбираются только один раз.

Нам нужно что-то, чтобы оптимизатор запускал внутренний запрос для каждой строки из #S. Один из способов будет примерно таким:

SELECT c, id INTO #R 
FROM #S
CROSS APPLY 
(
    SELECT TOP(100) -- or #S.SomeField instead of 100
        id
    FROM #T 
    ORDER BY CRYPT_GEN_RANDOM(4) + CHECKSUM(c)
) AS t
;

Что-то во внутреннем запросе для ссылки на строку из внешнего запроса. Если вместо постоянной TOP(100) поставить TOP(#S.SomeField), то + CHECKSUM(c) не требуется.

Это план для первого варианта. Вы можете видеть, что #T сканируется один раз (читается 1000 строк).

Query 1

Это план для второго варианта. Вы можете видеть, что #T сканируется дважды (считывается 2000 строк).

Query 2

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...