Come posso generare un numero casuale per ogni riga in un TSQL Select?


328

Ho bisogno di un numero casuale diverso per ogni riga nella mia tabella. Il seguente codice apparentemente ovvio utilizza lo stesso valore casuale per ogni riga.

SELECT table_name, RAND() magic_number 
FROM information_schema.tables 

Mi piacerebbe ottenere un INT o un FLOAT da questo. Il resto della storia userò questo numero casuale per creare un offset di data casuale da una data nota, ad esempio un offset di 1-14 giorni da una data di inizio.

Questo è per Microsoft SQL Server 2000.


4
Esiste una soluzione a questo che non utilizza NEWID ()? Voglio essere in grado di generare la stessa sequenza di numeri casuali per un dato seme.
Rory MacLeod,

@Rory Poni che come nuova domanda, otterrà più attenzione. (La mia risposta sarebbe quella di utilizzare tabelle fisse di numeri casuali, ad esempio. Ad esempio questo famoso set standard di numeri casuali: rand.org/pubs/monograph_reports/MR1418/index.html )
MatthewMartin


RAND è stato introdotto nel 2005, questa domanda è stata posta nel 2009, quali organizzazioni utilizzavano ancora SQL 2000 perché quella era la prima versione abbastanza buona da usare per sempre.
Matthew Martedì

Rory MacLeod ha chiesto: "Esiste una soluzione a ciò che non utilizza NEWID ()? Voglio essere in grado di generare la stessa sequenza di numeri casuali per un dato seme." La risposta è sì, ma è un po 'contorta. 1. Creare una vista che restituisca select rand () 2. Creare un UDF che seleziona il valore dalla vista. 3. Prima di selezionare i dati, eseguire il seeding della funzione rand (). 4. Utilizzare l'UDF nell'istruzione select.
Pubblicherò

Risposte:


516

Dai un'occhiata a SQL Server: imposta numeri casuali basati su una spiegazione molto dettagliata.

Per riassumere, il seguente codice genera un numero casuale compreso tra 0 e 13 inclusi con una distribuzione uniforme:

ABS(CHECKSUM(NewId())) % 14

Per modificare l'intervallo, basta cambiare il numero alla fine dell'espressione. Fai molta attenzione se hai bisogno di un intervallo che includa numeri sia positivi che negativi. Se lo fai in modo sbagliato, è possibile contare due volte il numero 0.

Un piccolo avvertimento per i matti della matematica nella stanza: c'è un leggero pregiudizio in questo codice. CHECKSUM()risulta in numeri che sono uniformi su tutto l'intervallo del tipo di dati Int sql, o almeno così vicino come possono mostrare i miei test (dell'editor). Tuttavia, ci sarà un certo pregiudizio quando CHECKSUM () produce un numero all'estremità superiore di tale intervallo. Ogni volta che si ottiene un numero compreso tra il numero intero massimo possibile e l'ultimo multiplo esatto della dimensione dell'intervallo desiderato (14 in questo caso) prima dell'intero massimo, tali risultati vengono favoriti rispetto alla parte rimanente dell'intervallo da cui non è possibile produrre l'ultimo multiplo di 14.

Ad esempio, immagina che l'intero intervallo del tipo Int sia solo 19. 19 è il numero intero più grande possibile che puoi contenere. Quando CHECKSUM () risulta in 14-19, questi corrispondono ai risultati 0-5. Questi numeri sarebbero fortemente favoriti su 6-13, perché CHECKSUM () ha il doppio delle probabilità di generarli. È più facile dimostrarlo visivamente. Di seguito è riportato l'intero insieme possibile di risultati per il nostro intervallo intero immaginario:

Numero intero di checksum: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Intervallo Risultato: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 0 1 2 3 4 5

Puoi vedere qui che ci sono più possibilità di produrre alcuni numeri rispetto ad altri: il pregiudizio. Per fortuna, l'intervallo effettivo del tipo Int è molto più ampio ... al punto che nella maggior parte dei casi il bias è quasi impercettibile. Tuttavia, è qualcosa di cui tenere conto se ti ritrovi a farlo per un serio codice di sicurezza.


28
Questa pagina collegata aveva la soluzione: ABS (CHECKSUM (NewId ())% 14
MatthewMartin

7
% 14 restituirebbe numeri compresi tra 0 e 13
CoderDennis,

7
@Dennis Palmer, basta aggiungere 1
KM.

59
Abbiamo appena scoperto un bug geniale con questo. Poiché checksum restituisce un int e l'intervallo di un int è compreso tra -2 ^ 31 (-2.147.483.648) e 2 ^ 31-1 (2.147.483.647), la funzione abs () può restituire un errore di overflow se il risultato è esattamente -2.147.483.648 ! Le probabilità sono ovviamente molto basse, circa 1 su 4 miliardi, tuttavia lo gestivamo ogni giorno su una tabella di righe di ~ 1,8 miliardi, quindi avveniva circa una volta alla settimana! La correzione è di lanciare il checksum su bigint prima degli addominali.
EvilPuppetMaster

17
Penso che questo dovrebbe dire "una distribuzione uniforme" non "distribuzione normalizzata" - ogni numero è ugualmente probabile, non è una curva a campana. "Normalizzato" ha un significato matematico specifico.
AnotherParker,

95

Quando viene chiamato più volte in un singolo batch, rand () restituisce lo stesso numero.

Suggerirei di usare convert ( varbinary, newid()) come argomento seed:

SELECT table_name, 1.0 + floor(14 * RAND(convert(varbinary, newid()))) magic_number 
FROM information_schema.tables

newid() è garantito per restituire un valore diverso ogni volta che viene chiamato, anche all'interno dello stesso batch, quindi usarlo come seed richiederà a rand () di dare un valore diverso ogni volta.

Modificato per ottenere un numero intero casuale da 1 a 14.


Come si ottiene un numero da un guid o varbinary? Aggiornerò la domanda per indicare che spero in un numero intero.
Matthew Martedì

1
Lo moltiplichi per un numero e lo indichi :) quindi, se vuoi cinque cifre, moltiplica per 100000 e converti in un int. Brutto, ma abbastanza semplice da fare.
Jeremy Smyth,

1
Come ulteriore addendum - che ti darà fino a cinque cifre - se vuoi zero-pad, dovrai usare un tipo di dati char e usare replicate a zero-pad fino a 5 cifre.
Jeremy Smyth,

Se si utilizza la funzione soffitto invece del pavimento, non è necessario aggiungere 1.
PopeDarren

Anche quando lo uso, ci sono volte in cui RAND () mi dà sempre lo stesso risultato. Ancora più strano, ci sono volte che passa da un comportamento corretto a un comportamento errato a seconda del numero di volte che lo sto usando. Sto cercando di implementare un RANDOM INNER JOIN e se chiedo più di 19 (!!!) righe, inizia a darmi sempre lo stesso risultato ...
Johannes Wentu,

72
RAND(CHECKSUM(NEWID()))

Quanto sopra genererà un numero (pseudo-) casuale tra 0 e 1, esclusivo. Se utilizzato in una selezione, poiché il valore del seme cambia per ogni riga, genererà un nuovo numero casuale per ogni riga (tuttavia non è garantito che generi un numero univoco per riga).

Esempio se combinato con un limite superiore di 10 (produce i numeri da 1 a 10):

CAST(RAND(CHECKSUM(NEWID())) * 10 as INT) + 1

Documentazione Transact-SQL:

  1. CAST(): https://docs.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql
  2. RAND(): http://msdn.microsoft.com/en-us/library/ms177610.aspx
  3. CHECKSUM(): http://msdn.microsoft.com/en-us/library/ms189788.aspx
  4. NEWID(): https://docs.microsoft.com/en-us/sql/t-sql/functions/newid-transact-sql

39

Generazione di numeri casuali tra 1000 e 9999 inclusi:

FLOOR(RAND(CHECKSUM(NEWID()))*(9999-1000+1)+1000)

"+1" - per includere i valori del limite superiore (9999 per l'esempio precedente)


Il limite superiore è esclusivo con questo metodo, quindi se vuoi includere il numero più alto che dovresti fareFLOOR(RAND(CHECKSUM(NEWID()))*(10000-1000)+1000)
vaindil

20

Rispondere alla vecchia domanda, ma questa risposta non è stata fornita in precedenza e, si spera, questo sarà utile per qualcuno che trova questi risultati attraverso un motore di ricerca.

Con SQL Server 2008, è stata introdotta una nuova funzione CRYPT_GEN_RANDOM(8), che utilizza CryptoAPI per produrre un numero casuale crittograficamente forte, restituito come VARBINARY(8000). Ecco la pagina della documentazione: https://docs.microsoft.com/en-us/sql/t-sql/functions/crypt-gen-random-transact-sql

Quindi per ottenere un numero casuale, puoi semplicemente chiamare la funzione e lanciarla sul tipo necessario:

select CAST(CRYPT_GEN_RANDOM(8) AS bigint)

o per ottenere un valore floatcompreso tra -1 e +1, potresti fare qualcosa del genere:

select CAST(CRYPT_GEN_RANDOM(8) AS bigint) % 1000000000 / 1000000000.0

13

La funzione Rand () genererà lo stesso numero casuale, se utilizzata in una query SELECT della tabella. Lo stesso vale se si utilizza un seme per la funzione Rand. Un modo alternativo per farlo è usare questo:

SELECT ABS(CAST(CAST(NEWID() AS VARBINARY) AS INT)) AS [RandomNumber]

Ho ottenuto le informazioni da qui , il che spiega molto bene il problema.


5

Hai un valore intero in ogni riga che potresti passare come seme alla funzione RAND?

Per ottenere un numero intero compreso tra 1 e 14, credo che funzionerebbe:

FLOOR( RAND(<yourseed>) * 14) + 1

Funziona in teoria, ma in pratica ho scoperto RAND(<seed>)che non sembra essere molto casuale per piccoli cambiamenti in <seed>. Ad esempio ho fatto un test rapido: ho lasciato <seed>184380, 184383, 184386 e i RAND(<seed>)valori corrispondenti erano: 0.14912, 0.14917, 0.14923.
ImaginaryHuman072889,

Forse per ottenere altri risultati casuali "apparentemente", provare qualcosa del genere:RAND(<seed>)*100000) - FLOOR(RAND(<seed>)*100000)
ImaginaryHuman072889

5

Se devi conservare il tuo seme in modo che generi sempre gli "stessi" dati casuali, puoi fare quanto segue:

1. Crea una vista che ritorna select rand ()

if object_id('cr_sample_randView') is not null
begin
    drop view cr_sample_randView
end
go

create view cr_sample_randView
as
select rand() as random_number
go

2. Creare un UDF che seleziona il valore dalla vista.

if object_id('cr_sample_fnPerRowRand') is not null
begin
    drop function cr_sample_fnPerRowRand
end
go

create function cr_sample_fnPerRowRand()
returns float
as
begin
    declare @returnValue float
    select @returnValue = random_number from cr_sample_randView
    return @returnValue
end
go

3. Prima di selezionare i dati, eseguire il seeding della funzione rand (), quindi utilizzare l'UDF nell'istruzione select.

select rand(200);   -- see the rand() function
with cte(id) as
(select row_number() over(order by object_id) from sys.all_objects)
select 
    id,
    dbo.cr_sample_fnPerRowRand()
from cte
where id <= 1000    -- limit the results to 1000 random numbers

4

prova a usare un valore seed nel RAND (seedInt). RAND () verrà eseguito solo una volta per istruzione, motivo per cui viene visualizzato lo stesso numero ogni volta.


Più semplice! Anche se i valori sembrano molto più sparsi, usando le cifre a metà, tipo RIGHT(CONVERT(BIGINT, RAND(RecNo) * 1000000000000), 2) (nota: sto vedendo RIGHTimplicitamente convertire il BIGINTin CHAR, ma per essere rigoroso, ne avresti un altro CONVERTlì dentro).
Doug_Ivison,

4

Se non è necessario che sia un numero intero, ma qualsiasi identificatore univoco casuale, è possibile utilizzare newid()

SELECT table_name, newid() magic_number 
FROM information_schema.tables

4

Dead link :( Eventuali copie che potrebbero essere incluse nella risposta?
jocull

Mette RAND()in una vista, mette una SELECTdi quella vista in una funzione, quindi chiama la funzione da qualsiasi luogo. Intelligente.
Doug_Ivison,

Ho pubblicato una soluzione che risolve il problema esattamente come nell'articolo collegato, ma qui in questo blog direttamente come risposta cinque post fa! Nessuno mi ha chiamato faccia di invidia intelligente hehe
Mitselplik,

4
select round(rand(checksum(newid()))*(10)+20,2)

Qui il numero casuale arriverà tra 20 e 30. rounddarà un massimo di due decimali.

Se vuoi numeri negativi puoi farlo con

select round(rand(checksum(newid()))*(10)-60,2)

Quindi il valore minimo sarà -60 e il massimo sarà -50.


3

È facile come:

DECLARE @rv FLOAT;
SELECT @rv = rand();

E questo metterà un numero casuale tra 0-99 in una tabella:

CREATE TABLE R
(
    Number int
)

DECLARE @rv FLOAT;
SELECT @rv = rand();

INSERT INTO dbo.R
(Number)
    values((@rv * 100));

SELECT * FROM R

2

Il problema che a volte ho con la "Risposta" selezionata è che la distribuzione non è sempre uniforme. Se hai bisogno di una distribuzione molto uniforme di 1 - 14 casuali tra molte righe, puoi fare qualcosa del genere (il mio database ha 511 tabelle, quindi funziona. Se hai meno righe di quelle dell'intervallo di numeri casuali, questo non funziona bene):

SELECT table_name, ntile(14) over(order by newId()) randomNumber 
FROM information_schema.tables

Questo tipo di fa il contrario delle normali soluzioni casuali, nel senso che mantiene i numeri sequenziati e randomizza l'altra colonna.

Ricorda, ho 511 tabelle nel mio database (che è pertinente solo in b / c che stiamo selezionando da information_schema). Se prendo la query precedente e la inserisco in una tabella temporanea #X, quindi eseguo questa query sui dati risultanti:

select randomNumber, count(*) ct from #X
group by randomNumber

Ottengo questo risultato, mostrandomi che il mio numero casuale è MOLTO uniformemente distribuito tra le molte righe:

inserisci qui la descrizione dell'immagine


2
select ABS(CAST(CAST(NEWID() AS VARBINARY) AS INT)) as [Randomizer]

ha sempre lavorato per me



1
    DROP VIEW IF EXISTS vwGetNewNumber;
    GO
    Create View vwGetNewNumber
    as
    Select CAST(RAND(CHECKSUM(NEWID())) * 62 as INT) + 1 as NextID,
    'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'as alpha_num;

    ---------------CTDE_GENERATE_PUBLIC_KEY -----------------
    DROP FUNCTION IF EXISTS CTDE_GENERATE_PUBLIC_KEY;  
    GO
    create function CTDE_GENERATE_PUBLIC_KEY()
    RETURNS NVARCHAR(32)
    AS 
    BEGIN
        DECLARE @private_key NVARCHAR(32);
        set @private_key = dbo.CTDE_GENERATE_32_BIT_KEY();
        return @private_key;
    END;
    go

---------------CTDE_GENERATE_32_BIT_KEY -----------------
DROP FUNCTION IF EXISTS CTDE_GENERATE_32_BIT_KEY;  
GO
CREATE function CTDE_GENERATE_32_BIT_KEY()
RETURNS NVARCHAR(32)
AS 
BEGIN
    DECLARE @public_key NVARCHAR(32);
    DECLARE @alpha_num NVARCHAR(62);
    DECLARE @start_index INT = 0;
    DECLARE @i INT = 0;
    select top 1 @alpha_num = alpha_num from vwGetNewNumber;
        WHILE @i < 32
        BEGIN
          select top 1 @start_index = NextID from vwGetNewNumber;
          set @public_key = concat (substring(@alpha_num,@start_index,1),@public_key);
          set @i = @i + 1;
        END;
    return @public_key;
END;
    select dbo.CTDE_GENERATE_PUBLIC_KEY() public_key;

scusa @arnt se non ho spiegato bene,
ichak khoury,

scusa @arnt, abbiamo qui due funzioni CTDE_GENERATE_32_BIT_KEY che genera una chiave alfanumerica a 32 bit (può essere estesa per essere più o meno) e l'altra chiamata CTDE_GENERATE_PUBLIC_KEY che chiama la prima funzione e ritorna indietro chiave pubblica a 32 bit oppure puoi restituire una chiave privata di 16 bit ... devi solo selezionare dbo.CTDE_GENERATE_PUBLIC_KEY () come chiave pubblica; la logica dietro è che selezioniamo un carattere dalla lista dei caratteri alfanumerici 32 volte e li concateniamo insieme per ottenere la chiave alfanumerica casuale. dopo la ricerca.
ichak khoury,

Bello. Questa spiegazione lo rende una risposta molto migliore. (Qualcuno contrassegnato per l'eliminazione;. Ho votato a lasciarla aperta e lasciato quel commento per voi)
Arnt

0

Prova questo:

SELECT RAND(convert(varbinary, newid()))*(b-a)+a magic_number 

Dov'è ail numero inferiore ed bè il numero superiore


1
Puoi provare a essere più chiaro mentre rispondi a una domanda?
Yunus Temurlenk

0
Update my_table set my_field = CEILING((RAND(CAST(NEWID() AS varbinary)) * 10))

Numero tra 1 e 10.

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.