原始问题的解释:
问题指出:
- 在上午 8:00 到晚上 8:00 之间生成一个随机时间(即 12 小时窗口)
- 每行应该是不同的(即在所有行中都是唯一的)
- 真实表有大约 2800 条记录
现在考虑以下几点:
- 示例数据仅显示一个日期
- 24 小时有 86,400 秒,因此 12 小时有 43,200 秒
以下领域存在一些歧义:
- 在“每一行都不同”的上下文中,究竟什么是随机的,因为不能保证真正的随机值对于每一行都是不同的。事实上,理论上,真正的随机数对于每一行可能都是相同的。那么是强调“随机”还是“不同”?还是我们真的在谈论不同但不是按顺序排列的(为了呈现随机性而不是实际上是随机的)?
- 如果有超过 2800 行怎么办?如果有 100 万行怎么办?
- 如果可以有超过 43,200 行,如何处理“每行不同”(因为不可能在所有行中都具有唯一性)?
- 日期会有所不同吗?如果是这样,我们真的在谈论“每个日期的每一行都不同”吗?
- 如果“每个日期的每一行都不同”:
- 每个日期的时间可以遵循相同的非顺序模式吗?或者每个日期的模式是否需要不同?
- 任何特定日期都会有超过 43,200 行吗?如果是这样,则每组 43,200 行的时间只能是唯一的。
鉴于上述信息,有几种方法可以解释请求:
- 强调“随机”:日期和行数无关紧要。使用其他答案中显示的三种方法之一
生成非常有可能但不能保证是唯一的真正随机时间:
- @notulysses:
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)
- 强调“每行不同”,总是 <= 43,200 行:如果行数从不超过可用秒数,则很容易保证所有行的唯一时间,无论日期相同或不同,并且看起来是随机排序。
- 强调“每行不同”,可能 > 43,200 行:如果行数可以超过可用秒数,则无法保证所有行的唯一性,但仍然可以保证跨行的唯一性任何特定日期的行,前提是没有特定日期的行数 > 43,200。
因此,我的回答基于以下想法:
- 即使 OP 的行数从不超过 2800,大多数遇到类似随机性需求的其他人更有可能使用更大的数据集(即,对于任何数字,很容易有 100 万行日期:1、5000 等)
- 样本数据在对所有 5 行使用相同的日期时过于简单化,或者即使在此特定情况下所有行的日期都相同,在大多数其他情况下不太可能发生
- 唯一性优于随机性
- 如果每个日期的“看似随机”的秒数排序存在模式,则至少应该在日期间的序列开始处有一个不同的偏移量(当日期按顺序排序时),以呈现随机性在任何一小组日期之间。
回答:
如果情况需要唯一的时间,那么任何生成真正随机值的方法都无法保证。我真的很喜欢CRYPT_GEN_RANDOM
@Vladimir Baranov 的使用,但几乎不可能生成一组独特的值:
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()
以下解决方案使用了我在这个答案中了解到的模乘逆(MMI)的概念:在 SQL Server 中生成看似随机的唯一数字 ID。当然,这个问题没有像我们这里那样严格定义的值范围,每天只有 86,400 个值。因此,我使用了 86400 的范围(作为“模数”)并在在线计算器中尝试了一些“互质”值(作为“整数”)来获得他们的 MMI:
- 13 (MMI = 39877)
- 37 (MMI = 51373)
- 59 (MMI = 39539)
我ROW_NUMBER()
在 CTE 中使用,分区(即分组)CREATED_DATE
作为为一天中的每一秒分配一个值的方法。
但是,虽然按顺序为 0、1、2、... 等秒生成的值看起来是随机的,但在不同的日子里,该特定秒将映射到相同的值。因此,第二个 CTE(名为“WhichSecond”)通过将日期转换为 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 之间的时间,我们只需要做一些小的调整:
- 将范围(作为“模数”)从 86400 更改为它的一半:43200
- 重新计算 MMI(可以使用与“整数”相同的“互质”值):39539(与之前相同)
- 添加
28800
到DATEADD
作为 8 小时偏移量的第二个参数
结果将是仅更改一行(因为其他行是诊断性的):
-- second parameter of the DATEADD() call
28800 + (((parts.ThisSecond % 43200) * 39539) % 43200)
另一种以不太可预测的方式改变每一天的方法是通过传递“WhichSecond”CTERAND()
中的 INT 形式来使用。CREATED_DATE
这将为每个日期提供一个稳定的偏移量,因为对于传入的相同值RAND(x)
将返回相同的值,但对于传入的不同值将返回不同的值。含义:y
x
y
x
兰德(1) = y1
兰德(2) = y2
兰德(3) = y3
兰德(2) = y2
第二次RAND(2)
调用时,它仍然返回与y2
第一次调用时相同的值。
因此,“WhichSecond”CTE 可能是:
(
SELECT cte.ID,
cte.CREATED_DATE,
(RAND(CONVERT(INT, cte.[CREATED_DATE])) * {some number}) + cte.[RowNum]
AS [ThisSecond]
FROM cte
)