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


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

Якщо я тоді виконую SELECT, я бачу, що моє випадкове число ідентичне в кожному рядку . Будь-які ідеї, як генерувати унікальні випадкові числа?

Відповіді:


167

Замість rand(), використання newid(), яке перераховується для кожного рядка в результаті. Звичайним способом є використання модуля контрольної суми. Зверніть увагу, що це checksum(newid())може спричинити -2 147 483 648 і призвести до переповнення цілого числа abs(), тому нам потрібно використовувати modulo для значення контрольної суми, перш ніж перетворити його в абсолютне значення.

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, яке не рівномірно ділиться на 10000, деякі числа будуть трохи перевизначені. Шляхом цього можна було б обернути його в скалярний ОДС, який викидає будь-яке число понад 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  

1
@downvoter - Якась конкретна причина? Можливо, ви хотіли натиснути стрілку вгору, ця відповідь чудово працює!
Martin Smith

Здається, усім бракує того, що цей метод НАБАГАТО НАБАГАТО кращий за ефективністю. Я шукав альтернативу NEWID (), і це на місці, дякую!
Копає

Будь-який бажаний діапазон легко впоратися. Наприклад, ABS (CAST (CRYPT_GEN_RANDOM (8) AS BIGINT)% 10001) дає число від 0-10000, тобто діапазон, який би створив код ОП, якби він працював так, як вони сподівались.
bielawski

Яке "те саме" питання? Формула генерує нові значення на рядок (проблема вирішена) і результат знаходиться в межах діапазону, але вони не будуть перекошеними, оскільки є 64 біти насіння та лише 14 біт результату, тому будь-який потенційний перекіс неможливо виявити. Навіть якщо ви згенерували 10 ^ 15 результатів, будь-який перекіс, який, як ви думаєте, виявляєте, все одно знаходитиметься в межах похибки. Це означає, що вам потрібно буде згенерувати 2 ^ 19 результатів, щоб довести, що перекіс насправді існував.
bielawski

9

Хоча я люблю використовувати 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

1

Я перевірив 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 було явно найбільш випадковим, оскільки існує лише шанс .000000001% побачити навіть 1 дублікат при зриванні 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 порядків величини менш випадковим цей метод був не зовсім вдвічі швидшим, для створення 100 мільйонів чисел потрібно лише 23 секунди.

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 Думаючи, що перезапуски могли скласти частину дублікатів, я швидко протестував лише 3 мільйони рядків, що зайняло майже 6-1 / 2 хвилини. Я отримав 2101 дуплін і 2 значення з’явилися 3 рази (.07% та .000067% відповідно), що вказує на те, що перезапуски, мабуть, зіграли свою роль, але випадковість все ще далека від зоряної.
bielawski

Помітивши ще одну відповідь, просто засіяну newid, перетвореною на varbinary, тому я теж спробував це. Це не тільки не швидше, ніж використання контрольної суми, але одне значення з’являється 8 разів у цьому тесті. Чесно кажучи, це все ще було 95,447319% унікальним, що лише трохи гірше, ніж RAND (Контрольна сума (NewId ())) 95,450254% у моєму тесті. Друге виконання дало найгірший випадок із 3-х чисел, що з’явились 5 разів і на 95,452929% різнилися, тому YMMV навіть під час тестування 100M рядків.
bielawski

-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 для кожного рядка окремо є ДУЖЕ, ДУЖЕ ПОГОЛОЮ ІДЕЄЮ, коли потрібно ОНОВИТИ навіть невелику кількість рядків.
дарлове
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.