web-dev-qa-db-fra.com

Génération de temps aléatoire distincte dans l'intervalle fixe

J'essaie de générer une heure aléatoire entre 8h00 et 8h00 PM pour chaque ligne sélectionnée dans un jeu de données. Cependant, je reçois toujours le identique valeur aléatoire pour chaque ligne - Je veux que ce soit différent pour chaque ligne .

Schéma de table et données:

╔══════╦════════════════╗
║  ID  ║  CREATED_DATE  ║
╠══════╬════════════════╣
║ ID/1 ║   26/04/2014   ║
║ ID/2 ║   26/04/2014   ║
║ ID/3 ║   26/04/2014   ║
║ ID/4 ║   26/04/2014   ║
║ ID/5 ║   26/04/2014   ║
╚══════╩════════════════╝

Instruction SQL actuelle:

SELECT [ID]
     , MyFunction.dbo.AddWorkDays(14, [CREATED_DATE]) AS [New Date]
     , CONVERT(VARCHAR, DATEADD(MILLISECOND, CAST(43200000 * Rand() AS INT), CONVERT(TIME, '08:00')), 114) AS [New Time]
FROM [RandomTable]

Résultats actuels ( identique heure pour chaque ligne de la colonne [New Time]):

╔══════╦════════════════╦════════════════╗
║  ID  ║    New Date    ║    New Time    ║
╠══════╬════════════════╬════════════════╣
║ ID/1 ║   10/05/2014   ║    09:41:43    ║
║ ID/2 ║   10/05/2014   ║    09:41:43    ║
║ ID/3 ║   10/05/2014   ║    09:41:43    ║
║ ID/4 ║   10/05/2014   ║    09:41:43    ║
║ ID/5 ║   10/05/2014   ║    09:41:43    ║
╚══════╩════════════════╩════════════════╝

Résultats souhaités ( différent heure pour chaque ligne de la colonne [New Time]):

╔══════╦════════════════╦════════════════╗
║  ID  ║    New Date    ║    New Time    ║
╠══════╬════════════════╬════════════════╣
║ ID/1 ║   10/05/2014   ║    09:41:43    ║
║ ID/2 ║   10/05/2014   ║    15:05:23    ║
║ ID/3 ║   10/05/2014   ║    10:01:05    ║
║ ID/4 ║   10/05/2014   ║    19:32:45    ║
║ ID/5 ║   10/05/2014   ║    08:43:15    ║
╚══════╩════════════════╩════════════════╝

Des idées pour résoudre le problème? Tout ce qui précède n’est que des exemples de données - ma vraie table contient environ 2800 enregistrements ( je ne suis pas sûr que cela changera les suggestions de quiconque ).

29
AMC

Interprétation de la question initiale:

La question dit:

  • Générer une aléatoire heure entre 8h00 et 20h00 PM (c'est-à-dire une fenêtre de 12 heures)
  • Il devrait être différent pour chaque ligne _ (unique sur toutes les lignes)
  • La vraie table a environ 2800 enregistrements

Maintenant, tenez compte des points suivants:

  • Les données de l'échantillon ne montrent qu'une seule date
  • Il y a 86 400 secondes en 24 heures, donc 43 200 secondes en 12 heures

Il y a une certaine ambiguïté dans les domaines suivants:

  • Qu'est-ce qui est exactement aléatoire dans le contexte de "différent pour chaque ligne", étant donné qu'il ne peut pas être garanti que des valeurs réellement aléatoires soient différentes pour chaque ligne? En fait, vraiment nombres aléatoires pourrait théoriquement être le même pour chaque ligne. Est-ce que l'accent est mis sur "aléatoire" ou "différent"? Ou est-ce que nous parlons vraiment de différents mais sans ordre séquentiel (pour donner l'apparence du hasard sans être réellement aléatoire)?
  • Et s'il y a plus de 2800 lignes? Et s'il y a 1 million de lignes?
  • S'il peut y avoir plus de 43 200 lignes, comment gérer "différent pour chaque ligne" (car il n'est pas possible d'avoir unique sur toutes les lignes)?
  • La date variera-t-elle un jour? Si c'est le cas, parlons-nous vraiment de "différent pour chaque ligne par date"?
  • Si "différent pour chaque ligne par date":
    • Les heures de chaque date peuvent-elles suivre le même schéma non séquentiel? Ou le motif doit-il être différent pour chaque date?
    • Y aura-t-il jamais plus de 43 200 lignes pour une date donnée? Si tel est le cas, les heures ne peuvent être que uniques pour chaque ensemble de 43 200 lignes} _.

Compte tenu des informations ci-dessus, il existe plusieurs façons d'interpréter la demande:

  1. Emphase sur "aléatoire": Les dates et le nombre de lignes importent peu. Générez des temps véritablement aléatoires hautement probables, mais non garantis, d'être uniques en utilisant l'une des trois méthodes présentées dans les autres réponses:
    • @notulysses: Rand(CAST(NEWID() AS VARBINARY)) * 43200
    • @ Steve Ford: ABS(CHECKSUM(NewId()) % 43201)
    • @ Vladimir Baranov: CAST(43200000 * (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) as int)
  2. Insiste sur "différent pour chaque ligne", toujours <= 43 200 lignes: Si le nombre de lignes ne dépasse jamais le nombre de secondes disponibles, il est facile de garantir des temps uniques pour toutes les lignes, qu'ils soient identiques ou différents. dates, et semblent être commandés au hasard.
  3. L'accent mis sur "différent pour chaque ligne" peut être> 43 200 lignes: Si le nombre de lignes peut dépasser le nombre de secondes disponibles, il est alors impossible de garantir l'unicité entre (toutes) lignes, mais il serait toujours possible de garantir l'unicité des lignes d'une date donnée, à condition qu'aucune date ne comporte plus de 43 200 lignes.

J'ai donc basé ma réponse sur l'idée que:

  • Même si le nombre de lignes pour le PO ne dépasse jamais les 2800, il est plus probable que la plupart des autres personnes rencontrant un besoin similaire de caractère aléatoire disposeraient d'un ensemble de données plus volumineux (c.-à-d. Qu'il pourrait facilement y avoir 1 million de lignes de dates: 1, 5000, etc.)
  • Soit les données de l'échantillon sont trop simplistes en utilisant la même date pour les 5 lignes, ou même si la date est la même pour toutes les lignes dans ce cas particulier, dans la plupart des autres cas, cela est moins probable
  • L'unicité est à privilégier par rapport au hasard
  • S'il y a un motif dans l'ordre "apparemment aléatoire" des secondes pour chaque date, il devrait y avoir au moins un décalage variable au début de la séquence à travers les dates (lorsque les dates sont ordonnées séquentiellement) pour donner l'apparence du hasard. entre tout petit groupe de dates.

Réponse:

Si la situation nécessite des temps uniques, cela ne peut être garanti par aucune méthode de génération de valeurs réellement aléatoires. J'aime beaucoup l'utilisation de CRYPT_GEN_RANDOM par @Vladimir Baranov, mais il est presque impossible de générer un ensemble unique de valeurs:

DECLARE @Table TABLE (Col1 BIGINT NOT NULL UNIQUE);

INSERT INTO @Table (Col1)
    SELECT CONVERT(BIGINT, CRYPT_GEN_RANDOM(4))
    FROM [master].sys.objects so
    CROSS JOIN [master].sys.objects so2
    CROSS JOIN [master].sys.objects so3;
    -- 753,571 rows

Augmenter la valeur aléatoire à 8 octets semble fonctionner:

DECLARE @Table TABLE (Col1 BIGINT NOT NULL UNIQUE);

INSERT INTO @Table (Col1)
    SELECT CONVERT(BIGINT, CRYPT_GEN_RANDOM(8))
    FROM [master].sys.objects so
    CROSS JOIN [master].sys.objects so2
    CROSS JOIN [master].sys.objects so3;
    -- 753,571 rows

Bien sûr, si nous passons à la seconde, il n’ya que 86 400 d’entre eux. La réduction de la portée semble aider car ce qui suit fonctionne parfois:

DECLARE @Table TABLE (Col1 BIGINT NOT NULL UNIQUE);

INSERT INTO @Table (Col1)
    SELECT TOP (86400) CONVERT(BIGINT, CRYPT_GEN_RANDOM(4))
    FROM [master].sys.objects so
    CROSS JOIN [master].sys.objects so2
    CROSS JOIN [master].sys.objects so3;

Cependant, les choses deviennent un peu plus difficiles si l'unicité a besoin de par jour (ce qui semble être une exigence raisonnable pour ce type de projet, et non unique pour tous les jours). Mais un générateur de nombres aléatoires ne saura pas réinitialiser à chaque nouveau jour.

S'il est acceptable d'avoir simplement l'apparence d'être aléatoire, nous pouvons garantir l'unicité pour chaque date sans:

  • boucle/construction du curseur
  • sauvegarder les valeurs déjà utilisées dans un tableau
  • en utilisant Rand(), NEWID() ou CRYPT_GEN_RANDOM()

La solution suivante utilise le concept de Modular Multiplicative Inverses (MMI) que j'ai appris dans cette réponse: génère un ID numérique unique apparemment aléatoire dans SQL Server . Bien entendu, cette question n’avait pas une fourchette de valeurs aussi définie que celle que nous avons ici, avec seulement 86 400 d’entre elles par jour. J'ai donc utilisé une plage de 86400 (en tant que "Modulo") et essayé quelques valeurs de "coprime" (en tant que "Integer") dans un calculateur en ligne pour obtenir leurs MMI:

  • 13 (MMI = 39877)
  • 37 (MMI = 51373)
  • 59 (MMI = 39539)

J'utilise ROW_NUMBER() dans un CTE, partitionné (c'est-à-dire groupé) par CREATED_DATE comme moyen d'attribuer une valeur à chaque seconde du jour.

Cependant, alors que les valeurs générées pour les secondes 0, 1, 2, etc., etc., apparaissent en séquence, elles apparaissent de manière aléatoire, sur des jours différents, cette seconde en particulier mappant sur la même valeur. Ainsi, le deuxième CTE (nommé "WhichSecond") décale le point de départ de chaque date en convertissant la date en un INT (convertissant les dates en un décalage séquentiel du 1900-01-01), puis en le multipliant par 101.

DECLARE @Data TABLE
(
  ID INT NOT NULL IDENTITY(1, 1),
  CREATED_DATE DATE NOT NULL
);

INSERT INTO @Data (CREATED_DATE) VALUES ('2014-10-05');
INSERT INTO @Data (CREATED_DATE) VALUES ('2014-10-05');
INSERT INTO @Data (CREATED_DATE) VALUES ('2014-10-05');
INSERT INTO @Data (CREATED_DATE) VALUES ('2014-10-05');
INSERT INTO @Data (CREATED_DATE) VALUES ('2014-10-05');
INSERT INTO @Data (CREATED_DATE) VALUES ('2015-03-15');
INSERT INTO @Data (CREATED_DATE) VALUES ('2016-10-22');
INSERT INTO @Data (CREATED_DATE) VALUES ('2015-03-15');

;WITH cte AS
(
  SELECT tmp.ID,
         CONVERT(DATETIME, tmp.CREATED_DATE) AS [CREATED_DATE],
         ROW_NUMBER() OVER (PARTITION BY tmp.CREATED_DATE ORDER BY (SELECT NULL))
                      AS [RowNum]
  FROM   @Data tmp
), WhichSecond AS
(
  SELECT cte.ID,
         cte.CREATED_DATE,
         ((CONVERT(INT, cte.[CREATED_DATE]) - 29219) * 101) + cte.[RowNum]
                      AS [ThisSecond]
  FROM   cte
)
SELECT parts.*,
       (parts.ThisSecond % 86400) AS [NormalizedSecond], -- wrap around to 0 when
                                                         -- value goes above 86,400
       ((parts.ThisSecond % 86400) * 39539) % 86400 AS [ActualSecond],
       DATEADD(
                 SECOND,
                 (((parts.ThisSecond % 86400) * 39539) % 86400),
                 parts.CREATED_DATE
              ) AS [DateWithUniqueTime]
FROM WhichSecond parts
ORDER BY parts.ID;

Résultats:

ID  CREATED_DATE  ThisSecond  NormalizedSecond  ActualSecond  DateWithUniqueTime
1   2014-10-05    1282297     72697             11483         2014-10-05 03:11:23.000
2   2014-10-05    1282298     72698             51022         2014-10-05 14:10:22.000
3   2014-10-05    1282299     72699              4161         2014-10-05 01:09:21.000
4   2014-10-05    1282300     72700             43700         2014-10-05 12:08:20.000
5   2014-10-05    1282301     72701             83239         2014-10-05 23:07:19.000
6   2015-03-15    1298558      2558             52762         2015-03-15 14:39:22.000
7   2016-10-22    1357845     61845             83055         2016-10-22 23:04:15.000
8   2015-03-15    1298559      2559              5901         2015-03-15 01:38:21.000

Si nous voulons générer uniquement des heures entre 8h00 et 20h00, nous n'avons besoin que de quelques ajustements mineurs:

  1. Changez la plage (en "Modulo") de 86400 à la moitié: 43200
  2. Recalculer le MMI (peut utiliser les mêmes valeurs de "coprime" que "Integer"): 39539 (comme auparavant)
  3. Ajoutez 28800 au deuxième paramètre de la DATEADD sous la forme d'un décalage de 8 heures

Le résultat sera un changement en une seule ligne (puisque les autres sont en diagnostic):

-- second parameter of the DATEADD() call
28800 + (((parts.ThisSecond % 43200) * 39539) % 43200)

Un autre moyen de changer chaque jour d'une manière moins prévisible serait d'utiliser la fonction Rand() en passant sous la forme INT de CREATED_DATE dans le CTE "WhichSecond". Cela donnerait un décalage stable pour chaque date puisque Rand(x) renverra la même valeur y pour la même valeur de x passée, mais renverra une valeur différente y pour une valeur différente de x passée. Signification:

Rand (1) = y1
Rand (2) = y2
Rand (3) = y3
Rand (2) = y2 

La deuxième fois que Rand(2) a été appelé, il a toujours renvoyé la même valeur de y2 que lors de son premier appel.

Par conséquent, le CTE "WhichSecond" pourrait être:

(
  SELECT cte.ID,
         cte.CREATED_DATE,
         (Rand(CONVERT(INT, cte.[CREATED_DATE])) * {some number}) + cte.[RowNum]
                      AS [ThisSecond]
  FROM   cte
)
11
Solomon Rutzky

Le problème que OP _ avait lors de l'utilisation de Rand() est dû à son évaluation une fois par requête.

À partir de documentation :

Si graine n'est pas spécifié, le moteur de base de données SQL Server affecte une valeur graine au hasard. Pour une valeur graine spécifiée, le résultat renvoyé est toujours le même.

L'approche décrite ci-dessous supprime l'optimisation et supprime ce problème. Rand() est donc évalué une fois par ligne:

dateadd( second
       , Rand(cast(newid() as varbinary)) * 43200
       , cast('08:00:00' as time) )
  • newid() génère une valeur unique de type uniqueidentifier ;
  • la valeur est convertie avec cast pour être utilisée en tant que graine dans Rand([seed]) function afin de générer une valeur pseudo-aléatoire float de 0 à 1, et en tant que graine est toujours unique, la valeur de retour est également unique.

SQLFiddle

24
potashin

Vous pouvez aussi utiliser:

SELECT DATEADD(s, ABS(CHECKSUM(NewId()) % 43201), CAST('08:00:00' AS Time))

La ABS(CHECKSUM(NewId()) % 43201) génère un nombre aléatoire entre 0 et 43200. Voir Discussion ici .

SQL Fiddle

Configuration du schéma MS SQL Server 2008 :

Requête 1 :

SELECT DATEADD(s, ABS(CHECKSUM(NewId()) % 43201), CAST('08:00:00' AS Time)) AS [RandomTime]
FROM 
( VALUES (1), (2), (3), (4), (5)
) Y(A)
CROSS JOIN
( VALUES (1), (2), (3), (4), (5)
) Z(A)

Résultats:

|    RANDOMTIME    |
|------------------|
| 16:51:58.0000000 |
| 10:42:44.0000000 |
| 14:01:38.0000000 |
| 13:33:51.0000000 |
| 18:00:51.0000000 |
| 11:29:03.0000000 |
| 10:21:14.0000000 |
| 16:38:27.0000000 |
| 09:55:37.0000000 |
| 13:21:13.0000000 |
| 11:29:37.0000000 |
| 10:57:49.0000000 |
| 14:56:42.0000000 |
| 15:33:11.0000000 |
| 18:49:45.0000000 |
| 16:23:28.0000000 |
| 09:00:05.0000000 |
| 09:20:01.0000000 |
| 11:26:23.0000000 |
| 15:26:23.0000000 |
| 10:38:44.0000000 |
| 11:46:30.0000000 |
| 16:00:59.0000000 |
| 09:29:18.0000000 |
| 09:09:19.0000000 |
15
Steve Ford

Il y a plusieurs méthodes:

  • Générez un tableau avec des nombres aléatoires à l'avance et utilisez-le à tout moment. Ou prenez ces données de une source réputée .
  • Différentes combinaisons qui utilisent NEWID function pour fournir une valeur de départ pour Rand. Il convient de l'utiliser avec prudence, car il n'y a aucune garantie quant à la distribution des valeurs NEWID. Une des meilleures méthodes pour le rendre plus ou moins uniformément distribué est via la variable CHECKSUM: Rand(CHECKSUM(NEWID())). La bonne chose à propos de cette méthode est que la fonction NEWID est disponible depuis SQL Server 2000.
  • Au lieu de NEWID, utilisez, par exemple, MD5 d'une colonne en tant que valeur de départ pour Rand: Rand(CHECKSUM(HASHBYTES('MD5', CAST(SomeID AS varbinary(4))))) ou simplement le numéro de ligne: Rand(CHECKSUM(HASHBYTES('MD5', CAST(ROW_NUMBER() OVER(ORDER BY ...) AS varbinary(4))))). Cette méthode est disponible depuis au moins SQL Server 2005. La principale différence avec la méthode NEWID est que vous avez le contrôle total sur la séquence aléatoire. Vous ne pouvez pas contrôler ce que NEWID renvoie et vous ne pouvez pas redémarrer la séquence aléatoire à partir du même nombre. Si vous fournissez les mêmes ensembles de numéros de ligne, par exemple, en utilisant PARTITION BY, vous obtiendrez les mêmes ensembles de nombres aléatoires. Cela peut être utile dans les cas où vous devez utiliser plusieurs fois la même séquence de nombres aléatoires. Il est possible d'obtenir le même nombre aléatoire pour deux graines différentes. Je l'ai testé pour des numéros de ligne compris entre 1 et 1 000 000. MD5 d'entre eux sont tous différents. CHECKSUM of MD5 entraîne 122 collisions. Rand de cette CHECKSUM résultent en 246 collisions. Lorsqu’il a été testé avec des numéros de ligne compris entre 1 et 100 000, CHECKSUM a eu 1 collision, Rand a eu 3 collisions.
  • Une autre possibilité consiste simplement à implémenter dans T-SQL votre propre fonction définie par l'utilisateur, qui génère un nombre aléatoire à l'aide de votre algorithme préféré. Dans ce cas, vous avez le contrôle total de tout. Habituellement, les générateurs pseudo-aléatoires doivent stocker leur état interne entre les invocations. Vous pouvez donc vous retrouver avec une table dédiée qui stocke ces données.
  • Vous pouvez écrire votre fonction définie par l'utilisateur à l'aide de CLR. Dans ce cas, vous pouvez implémenter votre propre générateur ou utiliser des fonctions intégrées à .NET, telles que Random class ou RNGCryptoServiceProvider class .
  • Enfin, depuis SQL Server 2008, il existe une fonction intégrée CRYPT_GEN_RANDOM .

Je vais décrire la dernière méthode en détail, car je pense que c'est une très bonne solution pour SQL Server 2008 et versions ultérieures. CRYPT_GEN_RANDOM est appelé pour chaque ligne du jeu de résultats, par opposition à Rand, appelé une seule fois.

CRYPT_GEN_RANDOM (Transact-SQL)

Renvoie un nombre aléatoire cryptographique généré par Crypto API (CAPI). La sortie est un nombre hexadécimal du nombre spécifié de Octets.

De plus, CRYPT_GEN_RANDOM devrait fournir des valeurs aléatoires bien meilleures que Rand. Mieux en termes de distribution et de crypto-force. Exemple:

(CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5)

Cela génère 4 octets aléatoires sous la forme varbinary. Nous devons explicitement les convertir en int en premier. Le résultat est ensuite transformé en un nombre flottant compris entre 0 et 1.

Donc, la requête originale aimerait ceci:

SELECT ID AS [ID]
     , MyFunction.dbo.AddWorkDays(14, S.CREATED_DATE) AS [New Date]
     , CONVERT(VARCHAR, DATEADD(MILLISECOND, 
     CAST(43200000 * (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) as int),
     CONVERT(TIME, '08:00')), 114) AS [New Time]
FROM RandomTable

Voici un exemple autonome facile à copier-coller et à essayer (j'ai utilisé la requête d'une autre réponse de @ Steve Ford):

SELECT DATEADD(millisecond, 
    CAST(43200000 * (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) as int), 
    CAST('08:00:00' AS Time)) AS [RandomTime]
FROM 
    ( VALUES (1), (2), (3), (4), (5)
    ) Y(A)
    CROSS JOIN
    ( VALUES (1), (2), (3), (4), (5)
    ) Z(A)

Voici le résultat:

RandomTime
10:58:24.7200000
19:40:06.7220000
11:04:29.0530000
08:57:31.6130000
15:03:14.9470000
09:15:34.9380000
13:46:43.1250000
11:27:00.8940000
14:42:23.6100000
15:07:56.2120000
11:39:09.8830000
08:16:44.3960000
14:23:38.4820000
17:28:31.7440000
16:29:31.4320000
09:09:15.0210000
12:31:09.8370000
11:23:09.8430000
15:35:45.5480000
17:42:49.3390000
08:07:05.4930000
18:17:16.2980000
11:49:08.2010000
10:20:21.7620000
15:56:58.6110000

Une addition

Lorsque j'ai lu la question initiale, je ne pensais pas qu'il était vraiment nécessaire de veiller à ce que tous les nombres aléatoires générés soient uniques. J'ai interprété le mot "différent" dans la question comme un vague opposé pour voir le même nombre. dans chaque ligne du résultat que vous voyez lorsque vous utilisez une simple SELECT Rand(). Je pense que dans de nombreux cas, peu importe s'il y a peu de nombres aléatoires en collision. Dans de nombreux cas, le comportement serait correct.

Donc, si j'ai bien compris, la nécessité d'une séquence de uniques nombres aléatoires équivaut en quelque sorte à la tâche suivante. Nous avons un ensemble de valeurs/lignes, par exemple, un ensemble d’ID uniques ou toutes les 86 400 secondes d’un jour ou 2800 lignes pour un jour donné. Nous voulons mélanger ces valeurs/rangées. Nous voulons réorganiser ces lignes dans un ordre aléatoire.

Pour mélanger un ensemble de lignes donné, nous avons simplement besoin de ORDER BY nombres aléatoires (ces nombres aléatoires peuvent avoir un nombre raisonnable de collisions ici). Des nombres aléatoires peuvent être générés par n'importe quelle méthode. Quelque chose comme ça:

ROW_NUMBER() OVER ([optional PARTITION BY ...] ORDER BY CRYPT_GEN_RANDOM(4)) 

ou littéralement

SELECT ...
FROM ...
ORDER BY CRYPT_GEN_RANDOM(4)

selon où et comment il est utilisé.

7
Vladimir Baranov

Testez ceci:

 Declare @t table(ID int,CREATED_DATE datetime)
insert into @t values
 (1 ,  '04/26/2014'),
 (2 ,  '04/26/2014'),
 (3 ,  '04/26/2014'),
 (4 ,  '04/26/2014')

 ;WITH CTE AS
 (
   SELECT *,CONVERT(VARCHAR, DATEADD(SECOND, Rand(CAST(NEWID() AS VARBINARY)) * 43200, 
   CAST('08:00:00' AS TIME)),114) AS [New Time] FROM @t WHERE ID=1
   UNION ALL
   SELECT *,CONVERT(VARCHAR, DATEADD(SECOND, Rand(CAST(NEWID() AS VARBINARY)) * 43200, 
   CAST('08:00:00' AS TIME)), 114)  FROM @t WHERE ID>1 AND ID<=5
 )
 SELECT * FROM CTE
3
KumarHarsh

Voici une autre option qui vous donne un peu plus de contrôle sur la façon dont l’heure est générée. Vous pouvez spécifier l’intervalle entre les heures aléatoires. Elle n’utilise pas non plus la fonction Rand.

DECLARE @StartTime  VARCHAR(10) = '08:00',
        @EndTime    VARCHAR(10) = '20:00',
        @Interval   INT = 5 --(In Seconds)

WITH times AS(
    SELECT CONVERT(TIME, @StartTime) AS t
    UNION ALL
    SELECT DATEADD(SECOND, @Interval, t)
    FROM times
    WHERE t < @EndTime
)

SELECT *, 
(SELECT TOP 1 t FROM times WHERE d.Id > 0 ORDER BY NEWID())
FROM #data d
option (maxrecursion 0)

Sur une note de côté:
Si vous supprimez la clause WHERE dans la sous-requête ci-dessus (WHERE d.Id > 0), la même valeur d’heure est renvoyée pour toutes les lignes, c’est-à-dire le même problème que celui avec lequel vous avez commencé

2
Spock

Tout,

Je pensais partager la réponse à ma question. Je ne me souviens pas exactement où j'ai trouvé les détails - je pense que c'était via l'un des liens fournis par sgeddes.

J'ai utilisé la requête suivante pour obtenir une heure aléatoire entre 8h et 19h55 (environ)

SELECT convert(varchar,CONVERT(varchar, DATEADD(ms, dbo.MyRand(335 ,830) * 86400, 0), 114),114)

La fonction MyRand est ci-dessous:

SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
CREATE FUNCTION dbo.myRand(@Min INT, @Max INT) RETURNS decimal(18,15) AS
BEGIN
DECLARE @BinaryFloat BINARY(8)
SELECT @BinaryFloat = CAST(Id AS BINARY) FROM vwGuid

DECLARE
@PartValue TINYINT,
@Mask TINYINT,
@Mantissa FLOAT,
@Exponent SMALLINT,
@Bit TINYINT,
@Ln2 FLOAT,
@BigValue BIGINT,
@RandomNumber FLOAT

SELECT
@Mantissa = 1,
@Bit = 1,
@Ln2 = LOG(2),
@BigValue = CAST(@BinaryFloat AS BIGINT),
@Exponent = (@BigValue & 0x7ff0000000000000) / EXP(52 * @Ln2)

WHILE @Part <= 8
BEGIN
SELECT
@PartValue = CAST(SUBSTRING(@BinaryFloat, @Part, 1) AS TINYINT),
@Mask =

WHILE @Mask > 0
BEGIN
IF @PartValue & @Mask > 0
SET @Mantissa = @Mantissa + EXP(-@Bit * @Ln2)

SELECT
@Mask = @Mask / 2
END
END

SET @RandomNumber = CASE @Exponent WHEN 0 THEN 0 ELSE CAST(@Exponent AS FLOAT) / 2047 END

RETURN CAST((@RandomNumber * (@Max - @Min)) + @Min AS DECIMAL(18,15))

END
GO
END

J'espère que ça aide. Comme je n'ai pas lu beaucoup des réponses ci-dessus, je vous prie de m'excuser si quelqu'un a une meilleure réponse.

Merci

0
AMC