Comment générer un nombre aléatoire pour chaque ligne d'un TSQL Select?

328

J'ai besoin d'un nombre aléatoire différent pour chaque ligne de ma table. Le code apparemment évident suivant utilise la même valeur aléatoire pour chaque ligne.

SELECT table_name, RAND() magic_number 
FROM information_schema.tables 

J'aimerais retirer un INT ou un FLOAT de cela. Le reste de l'histoire est que je vais utiliser ce nombre aléatoire pour créer un décalage de date aléatoire à partir d'une date connue, par exemple 1-14 jours de décalage à partir d'une date de début.

C'est pour Microsoft SQL Server 2000.

MatthewMartin
la source
4
Y a-t-il une solution à cela qui n'utilise pas NEWID ()? Je veux pouvoir générer la même séquence de nombres aléatoires pour une graine donnée.
Rory MacLeod
@Rory Posez cette question comme nouvelle, elle attirera plus d'attention. (Ma réponse serait d'utiliser des tables fixes de nombres aléatoires, par exemple. Par exemple, ce célèbre ensemble standard de nombres aléatoires: rand.org/pubs/monograph_reports/MR1418/index.html )
MatthewMartin
2
Look @ RAND (Transact-SQL)
AminM
RAND a été introduit en 2005, cette question a été posée en 2009, quelles organisations utilisaient encore SQL 2000 car c'était la 1ère version suffisamment bonne pour être utilisée à tout jamais.
MatthewMartin
Rory MacLeod a demandé: "Y a-t-il une solution à cela qui n'utilise pas NEWID ()? Je veux pouvoir générer la même séquence de nombres aléatoires pour une graine donnée." La réponse est oui, mais c'est un peu compliqué. 1. Créez une vue qui renvoie select rand () 2. Créez une UDF qui sélectionne la valeur dans la vue. 3. Avant de sélectionner vos données, amorcez la fonction rand (). 4. Utilisez l'UDF dans votre instruction select. Je
posterai

Réponses:

516

Jetez un œil à SQL Server - Définir des nombres aléatoires basés sur une explication très détaillée.

Pour résumer, le code suivant génère un nombre aléatoire compris entre 0 et 13 inclus avec une distribution uniforme:

ABS(CHECKSUM(NewId())) % 14

Pour changer votre plage, changez simplement le nombre à la fin de l'expression. Soyez extrêmement prudent si vous avez besoin d'une plage comprenant à la fois des nombres positifs et négatifs. Si vous le faites mal, il est possible de compter deux fois le nombre 0.

Un petit avertissement pour les écrous mathématiques dans la salle: il y a un très léger biais dans ce code. CHECKSUM()résulte en des nombres qui sont uniformes sur toute la plage du type de données sql Int, ou au moins aussi proches que mes tests (l'éditeur) peuvent le montrer. Cependant, il y aura un certain biais lorsque CHECKSUM () produit un nombre tout en haut de cette plage. Chaque fois que vous obtenez un nombre entre l'entier maximum possible et le dernier multiple exact de la taille de votre plage souhaitée (14 dans ce cas) avant cet entier maximum, ces résultats sont favorisés par rapport à la partie restante de votre plage qui ne peut pas être produite à partir de ce dernier multiple de 14.

Par exemple, imaginez que la plage entière du type Int n'est que de 19. 19 est le plus grand entier possible que vous pouvez contenir. Lorsque CHECKSUM () donne 14-19, cela correspond aux résultats 0-5. Ces chiffres seraient fortement favorisés sur 6-13, car CHECKSUM () est deux fois plus susceptible de les générer. Il est plus facile de le démontrer visuellement. Voici l'ensemble complet des résultats possibles pour notre gamme entière imaginaire:

Total de contrôle: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Résultat de la plage: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 0 1 2 3 4 5

Vous pouvez voir ici qu'il y a plus de chances de produire certains nombres que d'autres: le biais. Heureusement, la plage réelle du type Int est beaucoup plus grande ... si bien que dans la plupart des cas, le biais est presque indétectable. Cependant, c'est quelque chose à savoir si vous vous retrouvez à le faire pour un code de sécurité sérieux.

SQLMenace
la source
28
Cette page liée avait la solution: ABS (CHECKSUM (NewId ()))% 14
MatthewMartin
7
% 14 renverrait des nombres entre 0 et 13
CoderDennis
7
@Dennis Palmer, ajoutez simplement 1
KM.
59
Nous venons de découvrir un bug génial avec cela. Étant donné que la somme de contrôle renvoie un int et que la plage d'un int est comprise entre -2 ^ 31 (-2,147,483,648) et 2 ^ 31-1 (2,147,483,647), la fonction abs () peut renvoyer une erreur de débordement si le résultat se trouve être exactement -2,147,483,648. ! Les chances sont évidemment très faibles, environ 1 sur 4 milliards, mais nous l'avons exécuté sur une table de rangs ~ 1,8 milliard chaque jour, donc cela se produisait environ une fois par semaine! Le correctif consiste à lancer la somme de contrôle en bigint avant les abdos.
EvilPuppetMaster
17
Je pense que cela devrait dire "une distribution uniforme" et non une "distribution normalisée" - chaque nombre est également probable, ce n'est pas une courbe en cloche. "Normalisé" a une signification mathématique spécifique.
AnotherParker
95

Lorsqu'il est appelé plusieurs fois dans un même lot, rand () renvoie le même numéro.

Je suggère d'utiliser convert ( varbinary, newid()) comme argument de départ:

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

newid() est garanti pour renvoyer une valeur différente chaque fois qu'il est appelé, même dans le même lot, donc l'utiliser comme une graine invitera rand () à donner une valeur différente à chaque fois.

Modifié pour obtenir un nombre entier aléatoire de 1 à 14.

Jeremy Smyth
la source
Comment obtenir un numéro d'un guid ou d'un varbinary? Je mettrai à jour la question pour indiquer que j'espère un entier.
MatthewMartin
1
Vous le multipliez par un nombre et le plancher :) donc si vous voulez cinq chiffres, multipliez par 100000 et convertissez-le en entier. Moche, mais assez simple à faire.
Jeremy Smyth
1
Comme complément supplémentaire - qui vous donnera jusqu'à cinq chiffres - si vous voulez le mettre à zéro, vous devrez utiliser un type de données char et utiliser la réplication pour mettre à zéro jusqu'à 5 chiffres.
Jeremy Smyth
Si vous utilisez la fonction plafond au lieu de plancher, vous n'avez pas besoin d'ajouter 1.
PopeDarren
Même lorsque j'utilise ceci, il y a des moments où RAND () me donne toujours le même résultat. Encore plus étrange, il y a des moments où il passe d'un comportement correct à un comportement incorrect en fonction du nombre de fois que je l'utilise. J'essaie d'implémenter un RANDOM INNER JOIN et si je demande plus de 19 (!!!) lignes, cela commence à me donner toujours le même résultat ...
Johannes Wentu
72
RAND(CHECKSUM(NEWID()))

Ce qui précède va générer un nombre (pseudo-) aléatoire entre 0 et 1, exclusif. S'il est utilisé dans une sélection, car la valeur de départ change pour chaque ligne, il générera un nouveau nombre aléatoire pour chaque ligne (il n'est pas garanti de générer un nombre unique par ligne cependant).

Exemple lorsqu'il est combiné avec une limite supérieure de 10 (produit les nombres 1 à 10):

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

Documentation 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
Aaron Hoffman
la source
39

Génération de nombres aléatoires entre 1000 et 9999 inclus:

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

"+1" - pour inclure les valeurs de limite supérieure (9999 pour l'exemple précédent)

Vova
la source
La limite supérieure est exclusive avec cette méthode, donc si vous voulez inclure le numéro supérieur, vous devez le faireFLOOR(RAND(CHECKSUM(NEWID()))*(10000-1000)+1000)
vaindil
20

Répondre à l'ancienne question, mais cette réponse n'a pas été fournie précédemment, et j'espère que cela sera utile pour quelqu'un qui trouve ces résultats via un moteur de recherche.

Avec SQL Server 2008, une nouvelle fonction a été introduite, CRYPT_GEN_RANDOM(8)qui utilise CryptoAPI pour produire un nombre aléatoire cryptographiquement fort, renvoyé sous la forme VARBINARY(8000). Voici la page de documentation: https://docs.microsoft.com/en-us/sql/t-sql/functions/crypt-gen-random-transact-sql

Donc, pour obtenir un nombre aléatoire, vous pouvez simplement appeler la fonction et la convertir en le type nécessaire:

select CAST(CRYPT_GEN_RANDOM(8) AS bigint)

ou pour obtenir un floatentre -1 et +1, vous pouvez faire quelque chose comme ceci:

select CAST(CRYPT_GEN_RANDOM(8) AS bigint) % 1000000000 / 1000000000.0
Andrei Tanas
la source
13

La fonction Rand () générera le même nombre aléatoire, si elle est utilisée dans une requête SELECT de table. Il en va de même si vous utilisez une valeur de départ pour la fonction Rand. Une autre façon de procéder consiste à utiliser ceci:

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

J'ai obtenu les informations d' ici , ce qui explique très bien le problème.

MicSim
la source
5

Avez-vous une valeur entière dans chaque ligne que vous pourriez passer comme valeur de départ à la fonction RAND?

Pour obtenir un entier entre 1 et 14, je pense que cela fonctionnerait:

FLOOR( RAND(<yourseed>) * 14) + 1
CoderDennis
la source
Cela fonctionne en théorie, mais dans la pratique, j'ai trouvé que le RAND(<seed>)semble ne pas être très aléatoire pour des changements mineurs <seed>. Par exemple, un test rapide que j'ai fait: j'ai laissé <seed>être 184380, 184383, 184386, et les RAND(<seed>)valeurs correspondantes étaient: 0,14912, 0,14917, 0,14923.
ImaginaryHuman072889
Peut-être pour obtenir plus de résultats "apparemment" aléatoires, essayez quelque chose comme:RAND(<seed>)*100000) - FLOOR(RAND(<seed>)*100000)
ImaginaryHuman072889
5

Si vous devez conserver votre graine afin qu'elle génère à chaque fois les "mêmes" données aléatoires, vous pouvez procéder comme suit:

1. Créez une vue qui renvoie 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. Créez un UDF qui sélectionne la valeur dans la vue.

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. Avant de sélectionner vos données, amorcez la fonction rand (), puis utilisez l'UDF dans votre instruction 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
Mitselplik
la source
4

essayez d'utiliser une valeur de départ dans le RAND (seedInt). RAND () ne s'exécutera qu'une fois par instruction, c'est pourquoi vous voyez le même nombre à chaque fois.

pôle Nord
la source
Le plus simple! Bien que les valeurs semblent beaucoup plus dispersées, en utilisant des chiffres du milieu, comme RIGHT(CONVERT(BIGINT, RAND(RecNo) * 1000000000000), 2) (note: je vois RIGHTimplicitement convertir le BIGINTen CHAR, mais pour être rigoureux, vous en auriez un autre CONVERT).
Doug_Ivison
4

Si vous n'avez pas besoin que ce soit un entier, mais tout identifiant unique aléatoire, vous pouvez utiliser newid()

SELECT table_name, newid() magic_number 
FROM information_schema.tables

la source
4

Vous devez appeler RAND () pour chaque ligne. Voici un bon exemple

https://web.archive.org/web/20090216200320/http://dotnet.org.za/calmyourself/archive/2007/04/13/sql-rand-trap-same-value-per-row.aspx

David
la source
Lien mort :( Des copies pourraient être incluses dans la réponse?
jocull
Il met RAND()dans une vue, met une SELECTde cette vue dans une fonction, puis appelle la fonction de n'importe où. Intelligent.
Doug_Ivison
J'ai posté une solution qui résout le problème exactement de la même manière que dans l'article lié, mais ici dans ce blog directement comme réponse il y a cinq articles! Personne ne m'a appelé face à l'envie intelligente hehe
Mitselplik
4
select round(rand(checksum(newid()))*(10)+20,2)

Ici, le nombre aléatoire entrera entre 20 et 30. rounddonnera au maximum deux décimales.

Si vous voulez des nombres négatifs, vous pouvez le faire avec

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

Ensuite, la valeur min sera -60 et max sera -50.

Tirthankar
la source
3

C'est aussi simple que:

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

Et cela mettra un nombre aléatoire entre 0 et 99 dans une table:

CREATE TABLE R
(
    Number int
)

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

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

SELECT * FROM R
Salle Rosjier
la source
2

Le problème que j'ai parfois avec la «réponse» sélectionnée est que la distribution n'est pas toujours uniforme. Si vous avez besoin d'une distribution très uniforme de 1 à 14 au hasard parmi de nombreuses lignes, vous pouvez faire quelque chose comme ça (ma base de données a 511 tables, donc cela fonctionne. Si vous avez moins de lignes que vous n'en avez au hasard, cela ne fonctionne pas. bien):

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

Ce type d'opération fait le contraire des solutions aléatoires normales dans le sens où il maintient les nombres séquencés et randomise l'autre colonne.

Rappelez-vous, j'ai 511 tables dans ma base de données (ce qui n'est pertinent que b / c que nous sélectionnons dans le schéma_information). Si je prends la requête précédente et la place dans une table temporaire #X, puis exécute cette requête sur les données résultantes:

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

J'obtiens ce résultat, me montrant que mon nombre aléatoire est TRÈS uniformément réparti entre les nombreuses lignes:

entrez la description de l'image ici

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

a toujours travaillé pour moi

theteague
la source
2

Utilisez newid ()

select newid()

ou peut-être cela

select binary_checksum(newid())
Chris Klepeis
la source
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;
ichak khoury
la source
désolé @arnt si je n'ai pas bien expliqué,
ichak khoury
désolé @arnt, nous avons ici deux fonctions CTDE_GENERATE_32_BIT_KEY qui génère une clé alphanumérique de 32 bits (peut être étendue pour être plus ou moins) et l'autre appelée CTDE_GENERATE_PUBLIC_KEY qui appelle la première fonction et retourne la clé publique de 32 bits ou vous pouvez retourner une clé privée de 16 bits ... il vous suffit d'appeler select dbo.CTDE_GENERATE_PUBLIC_KEY () comme clé publique; la logique derrière est que nous sélectionnons un caractère dans la liste des caractères alphanumériques 32 fois et les concaténons ensemble afin d'obtenir la clé alphanumérique aléatoire. après la recherche.
ichak khoury
Agréable. Cette explication en fait une bien meilleure réponse. (Quelqu'un marqué pour la suppression,. J'ai voté pour le laisser ouvert et a laissé ce commentaire pour vous)
Arnt
0

Essaye ça:

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

aest le nombre inférieur et ble nombre supérieur

Rutendo
la source
1
Pouvez-vous essayer d'être plus clair en répondant à une question?
Yunus Temurlenk
0
Update my_table set my_field = CEILING((RAND(CAST(NEWID() AS varbinary)) * 10))

Nombre compris entre 1 et 10.

user3478586
la source