[Snippets] Génération de nombres pseudo-aléatoires

Un participant au forum SQL Server de ce site a demandé comment on peut générer 10000 nombres entiers positifs aléatoires ne contenant pas de zéros, par une requête T-SQL.
Je connaissais la fonction RAND(), qui suffit dans bien des situations car on n’a pas souvent besoin de générer une série de nombres aléatoires, pourtant celle-ci ne s’est pas prêtée au jeu …

Avec une expression de table commune, j’écrivais la requête suivante, dont je pensais qu’elle allait générer 10 nombres aléatoires :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
------------------------------  
- Nicolas SOUQUET - 06/12/2008  
------------------------------  
WITH
  CTE_Random AS
  (
    SELECT 1 indice, RAND() nombre
  UNION ALL
    SELECT indice + 1, RAND() nombre
    FROM CTE_Random
    WHERE indice < 10
  )
SELECT *
FROM CTE_Random

Problème :

indice      nombre
----------- ----------------------
1           0,948787066403352
2           0,810137410843534
3           0,810137410843534
4           0,810137410843534
5           0,810137410843534
6           0,810137410843534
7           0,810137410843534
8           0,810137410843534
9           0,810137410843534
10          0,810137410843534

Seuls le premier nombre généré est différent des autres !

Je me suis alors souvenu de la fonction NEWID(), qui génère un GUID, et qui peut être utilisée pour identifier un ticket par exemple …
L’avantage de celle-ci par rapport à RAND(), c’est qu’elle génère des valeurs exclusivement différentes :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
------------------------------  
- Nicolas SOUQUET - 06/12/2008  
------------------------------  
WITH
  CTE_Random AS
  (
    SELECT 1 indice, NEWID() nombre
  UNION ALL
    SELECT indice + 1, NEWID() nombre
    FROM CTE_Random
    WHERE indice < 10
  )
SELECT *
FROM CTE_Random
indice      nombre
----------- ------------------------------------
1           4BDE02EB-1535-4309-8FF4-9BE0FC9890FC
2           74A8CD0E-5158-42FC-8CB4-0965147FE41F
3           AF760F20-DEFA-4B25-ADB4-745B15C9BE5D
4           0200CCBE-454B-4D34-A755-52AC98384DD0
5           C9D8EA8F-64A7-47B4-A3DB-626EBA3C967B
6           3216696C-3316-46B7-BDD5-7F4EBEAF4F20
7           40F3B9A6-C6EE-4677-9455-2437C09F4930
8           28F01AB5-5924-4163-98A5-593A662D26AC
9           80F1C811-C929-4A86-B1BE-4726E27B43FD
10          8D794438-0A13-4658-9C20-6F546F7E41C8

Dès lors, comment passer d’un GUID à une valeur numérique, ou au moins une représentation numérique de celui-ci ?
Le tableau des transtypages autorisés dans la documentation des fonctions CAST et CONVERT précise que l’on ne peut pas transtyper une donnée de type UNIQUEIDENTIFIER en un type de donnée numérique.
Qu’à cela ne tienne, on peut transtyper le type de données UNIQUEIDENTIFIER, de notre GUID, au type VARBINARY, et le type VARBINARY en type numérique :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
------------------------------  
- Nicolas SOUQUET - 06/12/2008  
------------------------------  
WITH
  CTE_Random AS
  (
    SELECT 1 indice, CAST(CAST(NEWID() AS VARBINARY) AS BIGINT) nombre
  UNION ALL
    SELECT indice + 1, CAST(CAST(NEWID() AS VARBINARY) AS BIGINT) nombre
    FROM CTE_Random
    WHERE indice < 10
  )
SELECT *
FROM CTE_Random
indice      nombre
----------- --------------------
1           -5798444018420898861
2           -8387580781428009187
3           -8290221482251344193
4           -5517060763531941547
5           -5222611935137695036
6           -8084341965211989766
7           -6365110813593533953
8           -4855414462827669452
9           -6272538355892614409
10          -5045499018529999454

Le requis du participant au forum était d’obtenir des nombres entiers positifs, or nous n’avons là que des entiers négatifs.
Utilisons la fonction ABS(), qui retourne la valeur absolue d’un nombre :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
------------------------------  
- Nicolas SOUQUET - 06/12/2008  
------------------------------  
WITH
  CTE_Random AS
  (
      SELECT 1 indice, ABS(CAST(CAST(NEWID() AS VARBINARY) AS BIGINT)) nombre
    UNION ALL
      SELECT indice + 1, ABS(CAST(CAST(NEWID() AS VARBINARY) AS BIGINT)) nombre
      FROM CTE_Random
      WHERE indice < 10
  )
SELECT *
FROM CTE_Random
indice      nombre
----------- --------------------
1           7906050049288686091
2           6497845688783947995
3           7055925192035141370
4           7818470519893116179
5           6240781906318427782
6           6648187948410848759
7           7992501322185322905
8           5480780527582611816
9           9050306324847691808
10          5335687916251733319

Comme nous avons transtypé en BIGINT, nous avons des entiers sur 19 chiffres.

Après avoir extrait de façon aléatoire 10 chiffres de chacun des 19 chiffres générés avec l’aide de la fonction SUBSTRING(), et remplacé les zéros par des entiers générés avec la fonction RAND(), on obtient la requête suivante :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
------------------------------  
- Nicolas SOUQUET - 06/12/2008  
------------------------------  
WITH  
  CTE_RANDOM (Random, Indice) AS
  (
    SELECT REVERSE(REPLACE(SUBSTRING(CAST(ABS(CAST(CAST(NEWID() AS VARBINARY) AS BIGINT)) AS VARCHAR), CAST(RAND() * 10 AS INT), 10), '0', CAST(CAST(RAND() * 10 AS TINYINT) AS CHAR(1)))), 1
  UNION ALL
    SELECT REVERSE(REPLACE(SUBSTRING(CAST(ABS(CAST(CAST(NEWID() AS VARBINARY) AS BIGINT)) AS VARCHAR), CAST(RAND() * 10 AS INT), 10), '0', CAST(CAST(RAND() * 10 AS TINYINT) AS CHAR(1)))), Indice + 1
    FROM CTE_RANDOM
    WHERE Indice < 10000
  )
SELECT Random
FROM CTE_RANDOM
WHERE Random NOT LIKE '0%'
OPTION (MAXRECURSION 0)

la clause OPTION avec l’indicateur de requête MAXRECURSION est nécessaire pour outrepasser la récursivité maximale de 100 autorisée par SQL Server, et obtenir ainsi 10000 nombres aléatoires ne commençant pas par zéro.
En effet comme l’ensemble de nombres est retourné sous forme de chaîne, il peut commencer par zéro.
Or lorsqu’on transtype une chaîne de chiffres commençant par zéro en un type entier, ce(s) zéro est « supprimé », et on obtiendra donc des nombres entiers de moins de 10 chiffres.

ElSuket

Laisser un commentaire