Jak mogę wypełnić kolumnę liczbami losowymi w SQL? Otrzymuję tę samą wartość w każdym wierszu


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

Jeśli następnie wykonam SELECT, widzę, że moja liczba losowa jest identyczna w każdym wierszu . Jakieś pomysły, jak wygenerować unikalne liczby losowe?

Odpowiedzi:


167

Zamiast rand()używać newid(), które jest obliczane ponownie dla każdego wiersza wyniku. Zwykłym sposobem jest użycie modulo sumy kontrolnej. Zauważ, że checksum(newid())może to dać -2,147,483,648 i spowodować przepełnienie całkowitoliczbowe abs(), więc musimy użyć modulo na zwracanej wartości sumy kontrolnej przed konwersją jej na wartość bezwzględną.

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

To generuje liczbę losową z zakresu od 0 do 9999.


1
To pytanie / odpowiedź może być również pomocne: stackoverflow.com/a/9039661/47226
Aaron Hoffman

To w ogóle nie działa dla mnie. Czy kolumna musi być INT? Błąd # 1064 za każdym razem. Sięgając po szalone pigułki ...
freeworlder

1
To rzecz piękna! Dobra robota. Kocham to. Trochę wolna wydajność, ale wciąż świetna.
Arvin Amir

25

Jeśli korzystasz z programu SQL Server 2008, możesz również użyć

 CRYPT_GEN_RANDOM(2) % 10000

Co wydaje się nieco prostsze (jest również oceniane raz na wiersz, jak newidjest - pokazane poniżej)

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

Zwroty (2 losowe prawdopodobnie różne liczby)

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

Rozważając niewyjaśnione głosy przeciwne, jedyny uzasadniony powód, jaki przychodzi mi do głowy, jest taki, że ponieważ wygenerowana liczba losowa zawiera się w przedziale 0-65535, co nie jest podzielne równo przez 10000, niektóre liczby będą nieco nadreprezentowane. Sposobem na obejście tego byłoby umieszczenie go w skalarnym UDF, który odrzuca dowolną liczbę powyżej 60 000 i wywołuje się rekurencyjnie, aby uzyskać numer zastępczy.

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 - Czy jest jakiś szczególny powód? Może chciałeś trafić w strzałkę w górę, ta odpowiedź działa dobrze!
Martin Smith

Wydaje się, że wszystkim brakuje tego, że ta metoda jest DUŻO DUŻO lepsza pod względem wydajności. Szukałem alternatywy dla NEWID () i to jest na miejscu, dzięki!
Digs

Z łatwością poradzisz sobie z każdym pożądanym zakresem. Na przykład ABS (CAST (CRYPT_GEN_RANDOM (8) AS BIGINT)% 10001) zwraca liczbę z zakresu 0-10000, czyli zakres, który wygenerowałby kod OP, gdyby działał tak, jak oczekiwali.
bielawski 28.11.18

Który „ten sam” problem? Formuła generuje nowe wartości na wiersz (problem rozwiązany op), a wynik mieści się w zakresie, ale nie zostaną one wypaczone, ponieważ zawiera 64 bity ziarna i tylko 14 bitów wyniku, więc wszelkie potencjalne pochylenie byłoby niewykrywalne. Nawet jeśli wygenerowałeś 10 ^ 15 wyników, jakiekolwiek odchylenie, które mógłbyś pomyśleć, że wykrywasz, nadal mieści się w granicach błędu. Oznacza to, że musisz wygenerować 2 ^ 19 wyników, aby udowodnić, że wypaczenie rzeczywiście istniało.
bielawski 28.11.18

9

Chociaż uwielbiam używać CHECKSUM, czuję, że lepszym sposobem jest użycie NEWID(), tylko dlatego, że nie musisz przechodzić przez skomplikowane obliczenia matematyczne, aby wygenerować proste liczby.

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

Możesz zastąpić 1000dowolną liczbą, którą chcesz ustawić jako limit, i zawsze możesz użyć znaku plusa, aby utworzyć zakres, powiedzmy, że chcesz losową liczbę od 100do 200, możesz zrobić coś takiego:

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

Łącząc to w swoim zapytaniu:

UPDATE CattleProds 
SET SheepTherapy= ROUND( 1000 *RAND(convert(varbinary, newid())), 0)
WHERE SheepTherapy IS NULL

1

Przetestowałem 2 metody randomizacji oparte na zestawach względem RAND (), generując z nich 100 000 000 wierszy. Aby wyrównać pole, wynikiem jest liczba zmiennoprzecinkowa z zakresu 0-1, aby naśladować RAND (). Większość kodu testuje infrastrukturę, więc podsumuję algorytmy tutaj:

-- 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

Używanie CRYPT_GEN_RANDOM było najwyraźniej najbardziej losowe, ponieważ istnieje tylko 0,000000001% szansy na zobaczenie nawet 1 duplikatu podczas wyrywania 10 ^ 8 liczb Z zestawu 10 ^ 18 liczb. IOW, nie powinniśmy byli widzieć żadnych duplikatów, a ten nie miał żadnego! Wygenerowanie tego zestawu na moim laptopie zajęło 44 sekundy.

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

Czas wykonania programu SQL Server: czas procesora = 134795 ms, czas, który upłynął = 39274 ms.

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;

Przy prawie 15 rzędach wielkości mniej losowych ta metoda nie była dwa razy szybsza, a wygenerowanie 100 milionów liczb zajęło tylko 23 sekundy.

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 

Czas wykonania programu SQL Server: czas procesora = 77156 ms, czas, który upłynął = 24613 ms.

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;

Samo RAND () jest bezużyteczne do generowania opartego na zestawie, więc wygenerowanie linii bazowej do porównania losowości zajęło ponad 6 godzin i musiało być kilkakrotnie uruchamiane ponownie, aby ostatecznie uzyskać odpowiednią liczbę wierszy wyjściowych. Wydaje się również, że losowość pozostawia wiele do życzenia, chociaż jest lepsza niż użycie sumy kontrolnej (newid ()) do ponownego wpisania każdego wiersza.

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

Z powodu ponownych uruchomień nie można było zarejestrować czasu wykonania.

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 Myśląc, że ponowne uruchomienie mogło odpowiadać za niektóre z duplikatów, szybko przetestowałem tylko 3M wierszy, co zajęło prawie 6-1 / 2 minuty. Otrzymałem 2101 dups i 2 wartości pojawiły się 3 razy (odpowiednio .07% i .000067%), co wskazuje, że ponowne uruchomienie prawdopodobnie odegrało rolę, ale losowość nadal jest daleka od gwiazd.
bielawski

Zauważyłem, że jedna odpowiedź została właśnie zapoczątkowana przez newid przekonwertowany na varbinary, więc też tego spróbowałem. Nie tylko jest to nie szybsze niż użycie sumy kontrolnej, ale jedna wartość pojawia się 8 razy w tym teście. Aby być uczciwym, nadal było 95,447319% unikalne, co jest tylko nieznacznie gorsze niż RAND (suma kontrolna (NewId ())) 95,450254% w moim teście. Drugie wykonanie dało najgorszy przypadek, w którym 3 liczby pojawiły się 5 razy i były różne w 95,452929%, więc YMMV nawet podczas testowania 100 mln wierszy.
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'");
}

może to nie jest poprawne i najłatwiejsze, ale działa)))
Vaso Nadiradze

1
Przeczytaj uważnie pytanie, zanim zaczniesz odpowiadać. Nawiasem mówiąc, wysłanie zapytania UPDATE dla każdego wiersza z osobna jest BARDZO, BARDZO ZŁYM POMYSŁEM, gdy trzeba ZAKTUALIZOWAĆ nawet niewielką liczbę wierszy.
kochanie
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.