2009-11-01 3 views
1

J'écris une procédure où chaque appel dont il a besoin pour obtenir un seul nombre aléatoire. Cette procédure est appelée depuis notre service web .net.SqlServer Rand() question

J'ai essayé d'implémenter cela en utilisant rand(). Toutefois, lorsque j'ai plusieurs appels à la procédure stockée en quelques millisecondes, j'obtiens beaucoup de collisions en ce que le même nombre aléatoire est généré. S'il y a un espace d'environ 20 ou 30 ms entre les appels suivants, cela semble fonctionner.

Il semble que rand() soit réanimé chaque appel de procédure stockée par SqlServer. D'après ce que je comprends c'est un problème car on devrait semer un générateur de nombres aléatoires une fois et qu'on n'obtient pas une bonne séquence de nombres pseudo-aléatoires si l'on réensemence chaque appel à rand. En outre, il semble que les appels à la même sp qui sont dans les 1 ou 2 millisecondes sont ensemencés avec la même valeur.

Voici l'instruction elle-même dans la procédure stockée.

DECLARE @randomNumber char(9) 

SET @randomNumber = RIGHT('00000' + CAST(CAST(rand()*100000 AS INT) AS VARCHAR(5)),5) 
+ RIGHT('00000' + CAST(CAST(rand()*10000 AS INT) AS VARCHAR(4)),4) 

Quelqu'un at-il une suggestion pour résoudre ce problème? Est-ce que je devrai écrire mon propre générateur de nombres aléatoires qui est ensemencé une fois et enregistrer son état dans une table à travers les appels? Comment SQL Server crée-t-il rand()? Est-ce vraiment aléatoire ou si vous appelez un sp à 1 ou 2 millisecondes l'un de l'autre sur des connexions séparées sera-t-il ensemencé avec la même graine causant une collision?

+1

Avez-vous essayé d'écrire une procédure stockée CLR et utilisez la capacité de nombre aléatoire du framework .NET? – Kuberchaun

Répondre

9

Si vous utilisez SQL Server 2008, vous pouvez utiliser la fonction CRYPT_GEN_RANDOM(). Cela randomiser des données pour chaque ligne, même si vous essayez de calculer des millions de nombres aléatoires dans une exécution de la requête et n'a pas de problèmes d'ensemencement:

SELECT CAST(RIGHT(CAST(CAST(CRYPT_GEN_RANDOM(1) AS INT) AS VARCHAR(100)), 1) AS INT) 

Voici le lien vers l'article BOL:

http://msdn.microsoft.com/en-us/library/cc627408.aspx

+1

+1. C'est nouveau pour moi. Très bien. J'aime ça. – gbn

5

Dans votre exemple, remplacez rand()*10000 avec ABS(CHECKSUM(NEWID())) % 9999

Cependant, pour char (9):

SELECT RIGHT('000000000' + CAST(ABS(CHECKSUM(NEWID()) % 999999999) AS char(9), 9) 

aux semences RAND au hasard ...

RAND(CHECKSUM(NEWID())) 

Edit:

Remarque, RAND est mal implémenté dans SQL Server. Ne l'utilisez pas.

+0

+! Fonctionne, bien que je me demande s'il y a une garantie sur la distribution de newid() ou checksum (newid()). L'adresse MAC n'a-t-elle pas été utilisée dans le calcul de newid()? – Andomar

+0

newid = GUID, donc mêmes statistiques. CHECKSUM = signé 32 bits = 4 milliards. – gbn

+0

On dirait que la meilleure solution, jusqu'à présent. Bien que, je recherche également dans une procédure stockée clr qui utilise RNGCryptoServiceProvider. –

0

Le RAND() function dispose d'un paramètre d'amorçage facultatif que vous pouvez utiliser pour cela. Si vous passez la dernière valeur aléatoire générée comme une graine à l'appel suivant à rand(), vous êtes sûr d'obtenir un nouveau nombre aléatoire.

Merci à gbn pour souligner que la graine est un entier, tandis que rand() renvoie un float. Avec cette connaissance, voici un exemple de travail! Tout d'abord créer une table:

create table RandomNumber (number float) 
insert into RandomNumber values (rand()) 

attraper ensuite un nombre aléatoire et stocker le nouveau numéro dans une transaction:

declare @new float 
begin transaction 
select @new = rand(-2147483648 + 4294967295 * number) 
    from RandomNumber with (updlock, holdlock) 
update RandomNumber set number = @new 
commit transaction 
print 'Next bingo number is: ' + cast(cast(@new*100 as int) as varchar) 

Entier SQL Server varie entre -2147483648 et 2147483647, et un nombre aléatoire est flotter entre 0.0 et 1.0. Donc, -2147483648 + 4294967295 * number devrait couvrir toute la gamme des entiers disponibles.

La transaction garantit qu'une seule connexion à la fois lit et stocke un nouveau numéro. Donc les nombres sont aléatoires même sur différentes connexions à SQL Server. (En passant, j'ai voté pour la réponse de gbn, semble beaucoup plus facile.)

+0

C'est une idée intéressante, mais alors je devrais enregistrer cet état, dans une table et le rechercher pour chaque appel dans le sp. Cela semble cher. Aussi, cela va-t-il produire une bonne séquence de nombres pseudo-aléatoires si je place la graine de cette manière? –

+1

@Andomar: RAND prend une graine int, donc vous semez la même chose pour chaque insertion. Voir une vieille réponse à moi ici: http://stackoverflow.com/questions/1038681/sql-random-number-not-working/1354125#1354125 – gbn

0

Vous pouvez utiliser une table avec juste un champ d'identification pour créer nunbers uniques à utiliser comme semence:

declare 
    @randomNumber char(9), 
    @seed1 int, 
    @seed2 int 

insert into SeedTable() values() 
set @seed1 = scope_identity() 

insert into SeedTable() values() 
set @seed2 = scope_identity() 

set @randomNumber = right('00000' + 
    cast(cast(rand(@seed1) * 100000 as int) as varchar(5)), 5) + 
    right('00000' + 
    cast(cast(rand(@seed2) * 10000 as int) as varchar(4)), 4) 

if (@seed2 > 10000) truncate table SeedTable 
+0

Avez-vous eu l'intention de créer une table appelée SeedTable? – Andomar

+0

@Andomar: Oui, c'est la table avec juste un identifiant dont je parlais. – Guffa

+0

@Guffy: et la table tronquée réinitialise les colonnes d'identité? – Andomar