原问题解读:
问题说明:
- 在上午 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 行:如果行数可以超过可用秒数,则无法保证所有的唯一性em> 行,但仍然可以保证任何特定日期的行之间的唯一性,前提是没有特定日期的行数超过 43,200 行。
因此,我的回答基于以下想法:
- 即使 OP 的行数从不超过 2800,大多数其他遇到类似随机性需求的人更有可能使用更大的数据集(即很容易有 100 万行,对于任意数量的日期:1、5000 等)
- 在对所有 5 行使用相同日期的情况下,样本数据过于简单化,或者即使在这种特殊情况下所有行的日期都相同,在大多数其他情况下这种情况不太可能发生
- 独特性优于随机性
- 如果每个日期的秒数“看似随机”排序存在模式,则至少应在各个日期(当日期按顺序排序时)的序列开头有一个不同的偏移量,以提供任何一小组日期之间出现随机性。
答案:
如果情况需要唯一的时间,那么任何生成真正随机值的方法都无法保证这一点。我真的很喜欢@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 的范围(作为“模数”)并在 online calculator 中尝试了一些“互质”值(作为“整数”)来获取它们的 MMI:
- 13 (MMI = 39877)
- 37 (MMI = 51373)
- 59 (MMI = 39539)
我在 CTE 中使用 ROW_NUMBER(),按 CREATED_DATE 分区(即分组)作为为一天中的每一秒分配一个值的方法。
但是,虽然按顺序为 0、1、2、... 等秒生成的值会显得随机,但在不同的日子里,该特定秒将映射到相同的值。因此,第二个 CTE(名为“WhichSecond”)通过将日期转换为 INT(将日期转换为从 1900 年 1 月 1 日开始的顺序偏移量)然后乘以 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”CTE 中传递CREATED_DATE 的INT 形式来使用RAND()。这将为每个日期提供一个稳定的偏移量,因为RAND(x) 将为传入的x 的相同值返回相同的值y,但将为传入的x 的不同值返回不同的值y in. 含义:
兰德(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
)