Ms Sql: Генерация Псевдослучайных Данных С Помощью Newid(). Возможности И Подводные Камни

Известно, что встроенная функция newID() широко используется разработчиками не только по прямому назначению — то есть для генерации уникальных первичных ключей, но и как средство генерации массивов псевдослучайных данных.

В составе встроенных функций newID() фактически единственная, которая не только недетерминирована, но и можно сказать «супернедетерминирована», поскольку в отличие от всех остальных способна выдавать новое значение для каждой новой строки, а не одно и то же для всей партии — что делает его чрезвычайно полезным для такой массовой генерации.

Помимо newID(), этим свойством обладает и newSequentialID(), но его использование где-либо кроме установки значения по умолчанию для столбцов типа uniqueidentifier запрещено.

За примерами далеко ходить не придется — ниже код:

  
  
  
  
   

SELECT TOP 100 ABS(CHECKSUM(NEWID())) % 1000 FROM sysobjects A CROSS JOIN sysobjects B

или этот (если контрольная сумма кажется трудоемкой операцией):

SELECT TOP 100 ABS(CONVERT(INT, (CONVERT(BINARY(4), (NEWID()))))) % 1000 FROM sysobjects A CROSS JOIN sysobjects B

Сгенерирует нам таблицу из 100 случайных целых чисел в диапазоне от 0 до 999. Для плавающих чисел вы можете использовать свойство функции rand() для инициализации генератора целым числом:

SELECT TOP 100 RAND(CHECKSUM(NEWID())) FROM sysobjects A CROSS JOIN sysobjects B

В этом случае rand() по сути используется просто как преобразователь диапазона int32 в диапазон [0.1).

Статистическая проверка качества распределения этим методом на количестве записей порядка миллиона показывает, что оно не уступает стандартному использованию rand(), инициализируемому один раз и затем используемому в цикле.

Поэтому вы можете смело его использовать.

Еще один интересный вариант — генерация нормально распределенных данных.

Здесь мы будем использовать метод Бокс-Мюллер :

SELECT TOP 1000 COS(2 * PI() * RAND(BINARY_CHECKSUM(NEWID()))) * SQRT(-2 * LOG(RAND(BINARY_CHECKSUM(NEWID())))) FROM sysobjects A CROSS JOIN sysobjects B CROSS JOIN sysobjects C

Желающие могут проверить, что полученное распределение очень близко к нормальному, построив его график.

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

Вам просто нужно убедиться, что таблицы, которые вы используете в качестве источника строк, имеют достаточную емкость, и либо увеличить количество CROSS JOIN, либо использовать в качестве источника табличные переменные с необходимым количеством строк.

Однако тема не только об этом.

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

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

И вот здесь проявляются особенности поведения SQL-движка, которые сложно объяснить на первый взгляд. Давайте рассмотрим их на примерах, а затем попробуем проанализировать, почему это происходит и как с этим бороться: Для начала просто напишите в подзапросе оператор с newID() и запустите его несколько раз в цикле:

declare @c int = 0 while @c < 5 begin SELECT * FROM ( SELECT ABS(CONVERT(INT, CONVERT(BINARY(4), NEWID()))) % 5 AS RNDIDX FROM (

Теги: #ms sql 2008 #newID() #rand #контрольная сумма #план выполнения #псевдорандом #нормальное распределение #Box-Muller #DataArt #sql #Microsoft SQL Server

Вместе с данным постом часто просматривают: