在固定间隔中产生明显的随机时间

时间:2014-04-26 17:04:17

标签: sql sql-server sql-server-2008 tsql random-time-generation

我正在尝试为从数据集中选择的每行生成8:00 AM到8:00 PM 之间的随机时间,但是,我总是得到 < em>相同 每行的随机值 - 我希望每行 不同

表架构&amp;数据:

╔══════╦════════════════╗
║  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   ║
╚══════╩════════════════╝

СurrentSQL语句:

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]

当前结果([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    ║
╚══════╩════════════════╩════════════════╝

期望的结果([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    ║
╚══════╩════════════════╩════════════════╝

有关如何解决此问题的任何想法?以上所有内容仅仅是示例数据 - 我的真实表有大约2800条记录(不确定这是否会对任何人的建议产生影响)。

8 个答案:

答案 0 :(得分:24)

OP 在使用rand()时遇到的问题是由于每次查询一次评估

来自documentation

  

如果未指定 seed ,则SQL Server数据库引擎会随机分配种子值。对于指定的种子值,返回的结果始终相同。

下面介绍的方法会删除优化并抑制此行为,因此{<1}}每行评估一次

rand()
  • newid()生成uniqueidentifier;
  • 类型的唯一值
  • 使用dateadd( second , rand(cast(newid() as varbinary)) * 43200 , cast('08:00:00' as time) ) 转换该值以在rand([seed])函数中用作种子,以从 0生成伪随机cast值1 ,并且种子始终是唯一的,返回值也是唯一的。

SQLFiddle

答案 1 :(得分:15)

您也可以使用:

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

ABS(CHECKSUM(NewId()) % 43201)会在043200之间生成一个随机数。请参阅Discussion here

SQL Fiddle

MS SQL Server 2008架构设置

查询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)

<强> Results

|    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 |

答案 2 :(得分:12)

原始问题的解释:

问题陈述:

  • 在上午8:00到晚上8:00之间生成随机时间(即12小时窗口)
  • 每行应该不同(即所有行都是唯一的)
  • 真实表有大约2800条记录

现在考虑以下几点:

  • 示例数据仅显示单个日期
  • 24小时内有86,400秒,因此12小时内有432秒

以下几个方面存在一些含糊之处:

  • 在每个行&#34;的不同的上下文中,究竟什么是随机的,因为对于每一行,不能保证真正的随机值不同。事实上,真正的随机数 理论上可以是每行的相同的。所以强调&#34;随机&#34;或&#34;不同&#34;?或者我们真的在谈论不同但不按顺序排列(给出随机性的外观而不是实际上是随机的)?
  • 如果有超过2800行怎么办?如果有100万行怎么办?
  • 如果行数超过4300行,如何处理&#34;每行 &#34; (因为不可能在所有行中都具有唯一性)?
  • 日期会有所变化吗?如果是这样,我们真的在谈论&#34;每行每行不同&#34;?
  • 如果&#34;每行每行不同&#34;:
    • 每个日期的时间可以遵循相同的非连续模式吗?或者每个日期的模式是否需要不同?
    • 任何特定日期是否会有超过4300行?如果是这样,那么每组4300行的时间只能是唯一的

鉴于上述信息,有几种方法可以解释请求:

  1. 强调&#34;随机&#34; 日期和行数并不重要。使用其他答案中显示的三种方法之一,生成极有可能但非保证的真正随机时间是唯一的:
    • @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. 强调&#34;每行不同&#34;,始终&lt; = 43,400行:如果行数永远不会超过可用秒数,则很容易保证唯一所有行的时间,无论相同或不同的日期,并且似乎是随机排序的。
  3. 强调&#34;每行不同&#34;,可以是&gt; 43,400行:如果行数超过可用秒数,则无法保证所有行的唯一性,但仍可保证跨行的唯一性任何特定日期,只要没有特定日期> 43,400行。
  4. 因此,我的答案基于以下观点:

    • 即使OP的行数从未超过2800,大多数其他遇到类似随机需求的人也更有可能拥有更大的数据集(即可能容易有100万行,任何数量的日期:1,5000等。)
    • 对于所有5行使用相同日期的样本数据过于简单,或者即使在此特定情况下所有行的日期相同,在大多数其他不太可能发生的情况下也是如此
    • 独特性优于随机性
    • 如果有一个模式,那么看似随机的&#34;对于每个日期的秒数排序,至少应该在日期之间(当按顺序排序日期时)序列开始时有不同的偏移量,以便在任何小的日期分组之间显示随机性。

    答案:

    如果情况需要独特的时间,则无法通过任何生成真正随机值的方法来保证。我非常喜欢@Vladimir Baranov使用CRYPT_GEN_RANDOM,但几乎不可能获得一组独特的值:

    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
    

    将随机值增加到8个字节似乎确实有效:

    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
    

    当然,如果我们生成第二个,那么只有86,400个。缩小范围似乎有所帮助,因为以下情况偶尔会起作用:

    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;
    

    然而,如果唯一性每天需要 (这似乎是这类项目的合理要求,而不是所有日子都是独一无二的),事情会变得有点棘手。但随机数生成器在每个新的一天都不知道要重置。

    如果仅仅具有随机的外观是可以接受的,那么我们可以保证每个日期的唯一性,而不是:

    • 循环/游标构造
    • 在表中保存已使用的值
    • 使用RAND()NEWID()CRYPT_GEN_RANDOM()

    以下解决方案使用我在此答案中了解到的Modular Multiplicative Inverses(MMI)概念:generate seemingly random unique numeric ID in SQL Server。当然,这个问题没有一个明确定义的价值范围,就像我们这里一样,每天只有86,400个。所以,我使用了86400(&#34; Modulo&#34;)的范围并尝试了一些&#34; coprime&#34; online calculator中的值(作为&#34;整数&#34;)来获取他们的MMI:

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

    我在CTE中使用ROW_NUMBER(),按CREATED_DATE进行分区(即分组),作为分配当天每秒的值的方法。

    但是,虽然按秒生成的值0,1,2,......依此类推,但在不同的日子里,特定的秒将映射到相同的值。因此,第二个CTE(名为&#34; WhichSecond&#34;)通过将日期转换为INT(将日期转换为1900-01-01的顺序偏移)然后乘以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;
    

    返回:

    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
    

    如果我们只想生成上午8:00到晚上8:00之间的时间,我们只需要进行一些小的调整:

    1. 将范围(&#34; Modulo&#34;)从86400更改为其中的一半:43200
    2. 重新计算MMI(可以使用相同的&#34;互质&#34;值为&#34;整数&#34;):39539(与之前相同)
    3. 28800添加到DATEADD的第二个参数作为8小时偏移量
    4. 结果将只改为一行(因为其他行是诊断的):

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

      以不太可预测的方式转换每一天的另一种方法是通过在{&lt; 34; WhereSecond&#34;中传递RAND()的INT形式来利用CREATED_DATE。 CTE。这将为每个日期提供稳定的偏移,因为RAND(x)将为传入的y的相同值返回相同的值x,但会返回不同的值y传入了x的不同值。含义:

      兰德(1)= y1
      兰德(2)= y2
      兰德(3)= y3
      兰德(2)= y2

      第二次调用RAND(2)时,它仍返回与第一次调用时相同的y2值。

      因此,&#34;哪个&#34; CTE可能是:

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

答案 3 :(得分:7)

有几种方法:

  • 提前生成一个包含随机数的表,并在需要时使用它。或者从some reputable source
  • 获取此数据
  • 使用NEWID函数为RAND提供种子的各种组合。应谨慎使用,因为无法保证NEWID值的分配。使其或多或少均匀分布的最佳方法之一是CHECKSUMRAND(CHECKSUM(NEWID()))。这种方法的好处是自SQL Server 2000以来可以使用NEWID功能。
  • 而不是NEWID使用某个列的MD5作为RAND的种子:RAND(CHECKSUM(HASHBYTES('MD5', CAST(SomeID AS varbinary(4)))))或者只是行号:RAND(CHECKSUM(HASHBYTES('MD5', CAST(ROW_NUMBER() OVER(ORDER BY ...) AS varbinary(4)))))。此方法至少可用于SQL Server 2005.与NEWID方法的主要区别在于您可以完全控制随机序列。您无法控制NEWID返回的内容,也无法再次从同一个数字重新启动随机序列。如果您使用PARTITION BY提供相同的行数,例如行号,您将获得相同的随机数集。当您需要多次使用相同的随机数序列时,它可能很有用。可以为两个不同的种子获得相同的随机数。我测试了行数从1到1,000,000。 MD5他们都不同。 CHECKSUM MD5导致122次碰撞。此RAND的{​​{1}}会导致246次碰撞。如果使用1到100,000行CHECKSUM进行测试,则发生1次碰撞,CHECKSUM发生3次碰撞。
  • 另一种可能性是在T-SQL中简单地实现您自己的用户定义函数,该函数使用您的首选算法生成随机数。在这种情况下,您可以完全控制一切。通常,伪随机生成器必须在调用之间存储它们的内部状态,因此最终可能会有一个存储此数据的专用表。
  • 您可以使用CLR编写用户定义的函数。在这种情况下,您可以实现自己的生成器,或使用内置于.NET的函数,如Random classRNGCryptoServiceProvider class
  • 最后,由于SQL Server 2008有一个内置函数CRYPT_GEN_RANDOM

我将详细描述最后一种方法,因为我认为它是SQL Server 2008及更高版本的一个非常好的解决方案。对于结果集的每一行调用RAND,而CRYPT_GEN_RANDOM只调用一次。

  

CRYPT_GEN_RANDOM(Transact-SQL)

     

返回Crypto API生成的加密随机数   (CAPI)。输出是指定数量的十六进制数   字节。

此外,RAND应提供比CRYPT_GEN_RANDOM更好的随机值。在分配和加密强度方面更好。例如:

RAND

这会生成4个随机字节(CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) 。我们必须首先将它们明确地转换为varbinary。然后将结果转换为0到1之间的浮点数。

因此,原始查询会这样:

int

这是一个易于复制粘贴和尝试的独立示例(我使用了@Steve Ford的另一个答案的查询):

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

结果如下:

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)

加成

当我阅读原始问题时,我认为没有必要确保所有生成的随机数都是唯一的。 我将问题中的“不同”一词解释为与在使用简单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 时看到的结果的每一行中相同数字相反的模糊相反。 我认为在很多情况下,如果碰少的随机数很少也没关系。在许多情况下,它实际上是正确的行为。

所以,我的理解是,当需要一系列唯一随机数时,它在某种意义上等同于以下任务。 我们有一组值/行,例如,一组唯一ID或一天中的所有86400秒或给定日期的2800行。 我们想要洗牌这些值/行。我们想以随机顺序重新排列这些行。

要对给定的行集进行随机播放,我们只需要SELECT RAND()个随机数(这些随机数可能会有合理的冲突量)。随机数可以通过任何方法生成。像这样:

ORDER BY

或字面意思

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

取决于使用的位置和方式。

答案 4 :(得分:3)

测试一下:

 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

答案 5 :(得分:2)

这是另一个选项,让您可以更好地控制时间的生成方式。 您可以指定随机时间之间的间隔。 它也没有使用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)

旁注:
如果删除上面子查询中的WHERE子句(WHERE d.Id > 0),则会为所有行返回相同的时间值,即与您开始的相同问题

答案 6 :(得分:0)

所有

我以为我会分享我的问题的答案。我无法记住我在哪里找到详细信息 - 我认为这是通过sgeddes提供的链接之一。

我使用以下查询获取上午8点到下午7点55分之间的随机时间(大致)

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

MyRand功能如下:

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

我希望这会有所帮助。我还没有阅读上面的许多回复,如果有人有更好的答案,我会道歉 - 这就是我如何解决它。

由于

答案 7 :(得分:0)

Get Random Time in a given range: Sql Server

SELECT
    X.Value,
    RT.RandomTime,
    DateObject = CONVERT(SMALLDATETIME, CONVERT(DATE, GETDATE())) + CONVERT(SMALLDATETIME, RT.RandomTime)
 FROM (VALUES(101),(204),(77),(54),(75),(66)) X(Value)  /* YOUR TABLE */
 CROSS APPLY(SELECT FromTime = '08:20:00', ToTime = '08:33:00') FT
 CROSS APPLY(SELECT MaxSeconds = DATEDIFF(ss, FT.FromTime, FT.ToTime)) MS
 CROSS APPLY(SELECT RandomTime = CONVERT(TIME, DATEADD(SECOND, (MS.MaxSeconds + 1) * RAND(CONVERT(VARBINARY, NEWID() )) , FT.FromTime))) RT

enter image description here