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


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

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

Ответы:


168

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

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

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


1
Этот вопрос / ответ также может быть полезным: stackoverflow.com/a/9039661/47226
Аарон Хоффман

У меня это вообще не работает. Столбец должен быть INT? Ошибка # 1064 каждый раз. Достижение безумных таблеток ...
freeworlder

1
Это красота! Отлично сработано. Любить это. Немного медленная производительность, но все же отличная.
Арвин Амир

25

Если вы используете 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, которое не делится на 10 000 без остатка, некоторые числа будут представлены немного больше. Обойти это можно, заключив его в скалярную UDF, которая отбрасывает любое число, превышающее 60000, и рекурсивно вызывает себя, чтобы получить заменяющий номер.

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  

1
@downvoter - Какая-то конкретная причина? Возможно, вы хотели нажать стрелку вверх, этот ответ работает нормально!
Мартин Смит

Кажется, всем не хватает того, что этот метод НАМНОГО лучше с точки зрения производительности. Я искал альтернативу NEWID (), и это правильно, спасибо!
Digs

С любым желаемым диапазоном легко справиться. Например, ABS (CAST (CRYPT_GEN_RANDOM (8) AS BIGINT)% 10001) возвращает число от 0 до 10000, которое представляет собой диапазон, который код OP создал бы, если бы он работал так, как они надеялись.
bielawski

Какая «такая же» проблема? Формула действительно генерирует новые значения для каждой строки (проблема op решена), и результат находится в пределах диапазона, но они не будут искажены, потому что есть 64 бита начального числа и только 14 битов результата, поэтому любой потенциальный перекос не будет обнаружен. Даже если вы сгенерировали 10 ^ 15 результатов, любой перекос, который, как вы думаете, вы обнаруживаете, все равно будет в пределах погрешности. Это означает, что вам нужно будет сгенерировать 2 ^ 19 результатов, чтобы доказать, что перекос действительно существует.
bielawski

9

Хотя мне нравится использовать КОНТРОЛЬНУЮ СУММ, я считаю, что лучше использовать 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

1

Я протестировал 2 метода рандомизации на основе наборов против RAND (), сгенерировав по 100000000 строк для каждого. Для выравнивания поля вывод представляет собой число с плавающей запятой между 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 было явно наиболее случайным, поскольку вероятность увидеть хотя бы 1 дубликат составляет всего 0,0000001% при извлечении 10 ^ 8 чисел ИЗ набора из 10 ^ 18 чисел. IOW мы не должны были видеть никаких дубликатов, а этого не было! На создание этого набора на моем ноутбуке ушло 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 секунды для генерации 100 миллионов чисел.

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;

PS Думая, что перезагрузки могли быть причиной некоторых дубликатов, я быстро протестировал всего 3M строк, что заняло почти 6-1 / 2 минуты. Я получил 2101 повторений, и 2 значения появлялись 3 раза (0,07% и 0,00000067% соответственно), указывая на то, что перезапуски, вероятно, сыграли свою роль, но случайность все еще далека от звездной.
bielawski 06

Заметив еще один ответ, просто посеянный с помощью newid, преобразованного в varbinary, я тоже попробовал. Это не только не быстрее, чем использование контрольной суммы, но и одно значение появляется 8 раз в этом тесте. Честно говоря, уникальность по-прежнему составляла 95,447319%, что лишь немногим хуже, чем 95,450254% для RAND (Checksum (NewId ())) в моем тесте. Второе выполнение дало наихудший случай, когда 3 числа появлялись 5 раз и отличались на 95,452929%, поэтому YMMV даже при тестировании 100M строк.
bielawski 06

-2
require_once('db/connect.php');

//rand(1000000 , 9999999);

$products_query = "SELECT id FROM products";
$products_result = mysqli_query($conn, $products_query);
$products_row = mysqli_fetch_array($products_result);
$ids_array = [];

do
{
    array_push($ids_array, $products_row['id']);
}
while($products_row = mysqli_fetch_array($products_result));

/*
echo '<pre>';
print_r($ids_array);
echo '</pre>';
*/
$row_counter = count($ids_array);

for ($i=0; $i < $row_counter; $i++)
{ 
    $current_row = $ids_array[$i];
    $rand = rand(1000000 , 9999999);
    mysqli_query($conn , "UPDATE products SET code='$rand' WHERE id='$current_row'");
}

может быть, это не самый правильный и простой способ, но он работает)))
Васо Надирадзе

1
Пожалуйста, внимательно прочтите вопрос, прежде чем начинать отвечать. Между прочим, отправка запроса UPDATE для каждой строки в отдельности - ОЧЕНЬ, ОЧЕНЬ ПЛОХАЯ ИДЕЯ, когда нужно ОБНОВЛЯТЬ даже небольшое количество строк.
darlove
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.