我正在尝试为从数据集中选择的每行生成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条记录(不确定这是否会对任何人的建议产生影响)。
答案 0 :(得分:24)
OP 在使用rand()
时遇到的问题是由于每次查询一次评估。
如果未指定 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 ,并且种子始终是唯一的,返回值也是唯一的。答案 1 :(得分:15)
您也可以使用:
SELECT DATEADD(s, ABS(CHECKSUM(NewId()) % 43201), CAST('08:00:00' AS Time))
ABS(CHECKSUM(NewId()) % 43201)
会在0
和43200
之间生成一个随机数。请参阅Discussion here。
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)
问题陈述:
现在考虑以下几点:
以下几个方面存在一些含糊之处:
鉴于上述信息,有几种方法可以解释请求:
RAND(CAST(NEWID() AS VARBINARY)) * 43200
ABS(CHECKSUM(NewId()) % 43201)
CAST(43200000 * (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) as int)
因此,我的答案基于以下观点:
如果情况需要独特的时间,则无法通过任何生成真正随机值的方法来保证。我非常喜欢@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:
我在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之间的时间,我们只需要进行一些小的调整:
28800
添加到DATEADD
的第二个参数作为8小时偏移量结果将只改为一行(因为其他行是诊断的):
-- 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)
有几种方法:
NEWID
函数为RAND
提供种子的各种组合。应谨慎使用,因为无法保证NEWID值的分配。使其或多或少均匀分布的最佳方法之一是CHECKSUM
:RAND(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次碰撞。Random
class或RNGCryptoServiceProvider
class。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