it-swarm-ru.tech

Как заполнить столбец случайными числами в SQL? Я получаю одинаковое значение в каждой строке

UPDATE CattleProds
SET SheepTherapy=(ROUND((Rand()* 10000),0))
WHERE SheepTherapy IS NULL

Если я затем выполню команду SELECT, то увижу, что мое случайное число идентично в каждой строке . Есть идеи, как генерировать уникальные случайные числа?

67
NibblyPig

Вместо Rand() используйте newid(), который пересчитывается для каждой строки в результате. Обычный способ - использовать модуль контрольной суммы. Обратите внимание, что checksum(newid()) может произвести -2 147 483 648 и вызвать целочисленное переполнение в abs(), поэтому перед преобразованием его в абсолютное значение необходимо использовать модуль по возвращаемому значению контрольной суммы.

UPDATE CattleProds
SET    SheepTherapy = abs(checksum(NewId()) % 10000)
WHERE  SheepTherapy IS NULL

Это генерирует случайное число от 0 до 9999.

141
Andomar

Если вы используете SQL Server 2008, вы также можете использовать

 CRYPT_GEN_RANDOM(2) % 10000

Что кажется несколько проще (он также оценивается один раз для каждой строки как newid - показано ниже)

DECLARE @foo TABLE (col1 FLOAT)

INSERT INTO @foo SELECT 1 UNION SELECT 2

UPDATE @foo
SET col1 =  CRYPT_GEN_RANDOM(2) % 10000

SELECT *  FROM @foo

Возвращает (2 случайных вероятно разные числа)

col1
----------------------
9693
8573

Обдумывая необъяснимое понижение, единственная законная причина, о которой я могу подумать, состоит в том, что, поскольку генерируемое случайное число находится в диапазоне 0-65535, которое не делится равномерно на 10000, некоторые числа будут немного переоценены. Обойти это можно было бы заключить в скалярную UDF, которая выбрасывает любое число свыше 60 000 и рекурсивно вызывает себя, чтобы получить номер замены.

CREATE FUNCTION dbo.RandomNumber()
RETURNS INT
AS
  BEGIN
      DECLARE @Result INT

      SET @Result = CRYPT_GEN_RANDOM(2)

      RETURN CASE
               WHEN @Result < 60000
                     OR @@NESTLEVEL = 32 THEN @Result % 10000
               ELSE dbo.RandomNumber()
             END
  END  
22
Martin Smith

Хотя я люблю использовать CHECKSUM, я чувствую, что лучше использовать NEWID() только потому, что вам не нужно проходить сложную математику для генерации простых чисел.

ROUND( 1000 *Rand(convert(varbinary, newid())), 0)

Вы можете заменить 1000 на любое число, которое вы хотите установить в качестве предела, и вы всегда можете использовать знак плюс для создания диапазона. Допустим, вам нужно случайное число между 100 и 200, вы можете сделать что-то вроде:

100 + ROUND( 100 *Rand(convert(varbinary, newid())), 0)

Собираем это вместе в вашем запросе:

UPDATE CattleProds 
SET SheepTherapy= ROUND( 1000 *Rand(convert(varbinary, newid())), 0)
WHERE SheepTherapy IS NULL
6
Segev -CJ- Shmueli

Я протестировал 2 метода рандомизации на основе множеств с помощью Rand (), сгенерировав по 100 000 000 строк для каждого. Чтобы выровнять поле, результат представляет собой число с плавающей точкой от 0 до 1, чтобы имитировать Rand (). Большая часть кода тестирует инфраструктуру, поэтому я суммирую алгоритмы здесь:

-- Try #1 used
(CAST(CRYPT_GEN_RANDOM(8) AS BIGINT)%500000000000000000+500000000000000000.0)/1000000000000000000 AS Val
-- Try #2 used
Rand(Checksum(NewId()))
-- and to have a baseline to compare output with I used
Rand() -- this required executing 100000000 separate insert statements

Использование CRYPT_GEN_RANDOM было, очевидно, наиболее случайным, поскольку вероятность получения хотя бы одного дубликата при извлечении 10 ^ 8 номеров ИЗ набора из 10 ^ 18 чисел составляет всего 0,000000001%. Мы не должны были видеть дубликаты, а этого не было! Этот набор занял 44 секунды на моем ноутбуке.

Cnt     Pct
-----   ----
 1      100.000000  --No duplicates

Время выполнения SQL Server: время ЦП = 134795 мс, прошедшее время = 39274 мс.

IF OBJECT_ID('tempdb..#T0') IS NOT NULL DROP TABLE #T0;
GO
WITH L0   AS (SELECT c FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS D(c))  -- 2^4  
    ,L1   AS (SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B)    -- 2^8  
    ,L2   AS (SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B)    -- 2^16  
    ,L3   AS (SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B)    -- 2^32  
SELECT TOP 100000000 (CAST(CRYPT_GEN_RANDOM(8) AS BIGINT)%500000000000000000+500000000000000000.0)/1000000000000000000 AS Val
  INTO #T0
  FROM L3;

 WITH x AS (
     SELECT Val,COUNT(*) Cnt
      FROM #T0
     GROUP BY Val
)
SELECT x.Cnt,COUNT(*)/(SELECT COUNT(*)/100 FROM #T0) Pct
  FROM X
 GROUP BY x.Cnt;

Практически на 15 порядков меньше случайности, этот метод был не совсем в два раза быстрее, и потребовалось всего 23 секунды для генерации 100M чисел.

Cnt  Pct
---- ----
1    95.450254    -- only 95% unique is absolutely horrible
2    02.222167    -- If this line were the only problem I'd say DON'T USE THIS!
3    00.034582
4    00.000409    -- 409 numbers appeared 4 times
5    00.000006    -- 6 numbers actually appeared 5 times 

Время выполнения SQL Server: время ЦП = 77156 мс, прошедшее время = 24613 мс.

IF OBJECT_ID('tempdb..#T1') IS NOT NULL DROP TABLE #T1;
GO
WITH L0   AS (SELECT c FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS D(c))  -- 2^4  
    ,L1   AS (SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B)    -- 2^8  
    ,L2   AS (SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B)    -- 2^16  
    ,L3   AS (SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B)    -- 2^32  
SELECT TOP 100000000 Rand(Checksum(NewId())) AS Val
  INTO #T1
  FROM L3;

WITH x AS (
    SELECT Val,COUNT(*) Cnt
     FROM #T1
    GROUP BY Val
)
SELECT x.Cnt,COUNT(*)*1.0/(SELECT COUNT(*)/100 FROM #T1) Pct
  FROM X
 GROUP BY x.Cnt;

Одна только Rand () бесполезна для генерации на основе множеств, поэтому генерация базовой линии для сравнения случайности заняла более 6 часов, и ее пришлось несколько раз перезапускать, чтобы в итоге получить нужное количество выходных строк. Также кажется, что случайность оставляет желать лучшего, хотя это лучше, чем использование контрольной суммы (newid ()) для повторного заполнения каждой строки.

Cnt  Pct
---- ----
1    99.768020
2    00.115840
3    00.000100  -- at least there were comparitively few values returned 3 times

Из-за перезапусков время выполнения не может быть зафиксировано.

IF OBJECT_ID('tempdb..#T2') IS NOT NULL DROP TABLE #T2;
GO
CREATE TABLE #T2 (Val FLOAT);
GO
SET NOCOUNT ON;
GO
INSERT INTO #T2(Val) VALUES(Rand());
GO 100000000

WITH x AS (
    SELECT Val,COUNT(*) Cnt
     FROM #T2
    GROUP BY Val
)
SELECT x.Cnt,COUNT(*)*1.0/(SELECT COUNT(*)/100 FROM #T2) Pct
  FROM X
 GROUP BY x.Cnt;
1
bielawski