Come posso riempire una colonna con numeri casuali in SQL? Ottengo lo stesso valore in ogni riga


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

Se poi eseguo una SELEZIONE vedo che il mio numero casuale è identico in ogni riga . Qualche idea su come generare numeri casuali univoci?

Risposte:


168

Invece di rand(), usa newid(), che viene ricalcolato per ogni riga nel risultato. Il modo usuale è usare il modulo del checksum. Si noti che checksum(newid())può produrre -2,147,483,648 e causare un overflow di numeri interi abs(), quindi è necessario utilizzare modulo sul valore di ritorno del checksum prima di convertirlo in valore assoluto.

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

Questo genera un numero casuale compreso tra 0 e 9999.


1
Questa domanda / risposta può anche essere utile: stackoverflow.com/a/9039661/47226
Aaron Hoffman

Questo non funziona affatto per me. La colonna deve essere INT? Errore # 1064 ogni volta. Raggiungendo le pillole pazze ...
freeworlder

1
Questa è una cosa bella! Molto bene. Lo adoro. Prestazioni un po 'lente, ma comunque eccezionali.
Arvin Amir

25

Se sei su SQL Server 2008 puoi anche usare

 CRYPT_GEN_RANDOM(2) % 10000

Che sembra un po 'più semplice (viene valutato anche una volta per riga così com'è newid- mostrato di seguito)

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

Restituisce (2 numeri casuali probabilmente diversi )

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

Riflettendo sull'inspiegabile downvote, l'unica ragione legittima a cui posso pensare è che, poiché il numero casuale generato è compreso tra 0-65535, che non è uniformemente divisibile per 10.000, alcuni numeri saranno leggermente sovrarappresentati. Un modo per aggirare questo sarebbe avvolgerlo in una UDF scalare che butta via qualsiasi numero superiore a 60.000 e chiama se stesso in modo ricorsivo per ottenere un numero sostitutivo.

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 - Qualche motivo particolare? Forse volevi premere la freccia su questa risposta funziona bene!
Martin Smith,

Quello che sembra mancare a tutti è che questo metodo è MOLTO MOLTO MOLTO migliore per le prestazioni. Ho cercato un'alternativa a NEWID () e questo è perfetto, grazie!
Digs

Qualsiasi intervallo desiderato è facilmente gestibile. Ad esempio ABS (CAST (CRYPT_GEN_RANDOM (8) AS BIGINT)% 10001) restituisce un numero compreso tra 0 e 10000 che è l'intervallo che il codice dell'OP avrebbe generato se avesse funzionato nel modo sperato.
bielawski

Quale 'stesso' problema? La formula genera nuovi valori per riga (problema di op risolto) e il risultato rientra nell'intervallo ma non saranno distorti perché ci sono 64 bit di seme e solo 14 bit di risultato, quindi qualsiasi potenziale inclinazione non sarebbe rilevabile. Anche se hai generato 10 ^ 15 risultati, qualsiasi disallineamento che potresti pensare di rilevare sarebbe comunque nel margine di errore. Significa che dovresti generare 2 ^ 19 risultati per dimostrare che lo skew esisteva effettivamente.
bielawski

9

Anche se adoro usare CHECKSUM, ritengo che un modo migliore di procedere sia quello di utilizzarlo NEWID(), solo perché non è necessario eseguire calcoli complicati per generare numeri semplici.

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

Puoi sostituire il 1000con qualsiasi numero che desideri impostare come limite e puoi sempre utilizzare un segno più per creare un intervallo, diciamo che desideri un numero casuale compreso tra 100e 200, puoi fare qualcosa come:

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

Mettendolo insieme nella tua query:

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

1

Ho testato 2 metodi di randomizzazione basati su set contro RAND () generando 100.000.000 di righe con ciascuno. Per livellare il campo, l'output è un float tra 0-1 per imitare RAND (). La maggior parte del codice sta testando l'infrastruttura, quindi riassumo gli algoritmi qui:

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

L'uso di CRYPT_GEN_RANDOM è stato chiaramente il più casuale poiché c'è solo una probabilità dello 0,000000001% di vedere anche 1 duplicato quando si strappano 10 ^ 8 numeri da un insieme di 10 ^ 18 numeri. IOW non avremmo dovuto vedere alcun duplicato e questo non ne aveva! Questo set ha impiegato 44 secondi per essere generato sul mio laptop.

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

Tempi di esecuzione di SQL Server: tempo CPU = 134795 ms, tempo trascorso = 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;

Con quasi 15 ordini di grandezza meno casuali, questo metodo non era due volte più veloce, impiegando solo 23 secondi per generare 100 milioni di numeri.

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 

Tempi di esecuzione di SQL Server: tempo CPU = 77156 ms, tempo trascorso = 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;

RAND () da solo è inutile per la generazione basata su set, quindi la generazione della linea di base per il confronto della casualità ha richiesto più di 6 ore e ha dovuto essere riavviata più volte per ottenere finalmente il giusto numero di righe di output. Sembra anche che la casualità lasci molto a desiderare, sebbene sia meglio che usare checksum (newid ()) per riseminare ogni riga.

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

A causa dei riavvii, non è stato possibile acquisire il tempo di esecuzione.

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 Pensando che i riavvii avrebbero potuto spiegare alcuni dei duplicati, ho testato rapidamente solo 3M righe che hanno richiesto quasi 6-1 / 2 minuti. Ho ottenuto 2101 dups e 2 valori sono apparsi 3 volte (.07% e .000067% rispettivamente) indicando che i riavvii probabilmente hanno avuto un ruolo, ma la casualità è ancora lontana dall'essere stellare.
bielawski

Avendo notato un'altra risposta appena seminata con newid convertita in varbinary, ho provato anche quella. Non solo non è più veloce dell'utilizzo del checksum, ma un valore appare 8 volte in quel test. Per essere onesti, era ancora unico al 95,447319% che è solo leggermente peggiore del 95,450254% di RAND (Checksum (NewId ())) nel mio test. Una seconda esecuzione ha prodotto un caso peggiore di 3 numeri che appaiono 5 volte e distinti al 95,452929%, quindi YMMV anche durante il test di 100 milioni di righe.
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'");
}

forse non è corretto e il modo più semplice ma funziona)))
Vaso Nadiradze

1
Si prega di leggere attentamente la domanda prima di iniziare a rispondere. A proposito, inviare una query di AGGIORNAMENTO per ogni singola riga separatamente è un'IDEA MOLTO, MOLTO MALE quando si deve AGGIORNARE anche un numero modesto di righe.
darlove
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.