如何生成指定长度的随机字母数字唯一字符



问题描述如下:

  1. 生成唯一的字母数字字符
  2. 字符长度应为32
  3. 唯一的数字可以在当前时间进行种子设定,以帮助生成的数字具有唯一性
  4. 字母表字符必须来自此池:abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

样本输出:445rpxlKYPkj1pg4q8nAy7Ab91zxZ8v1

我可以使用Java来完成这项工作,但如果您能在MS SQL或T-SQL上帮助我完成这项任务,我将不胜感激。

首先,您需要将字符串拆分为单独的行。然后,用ORDER BY NEWID()SELECT进行随机排序。最后,使用FOR XML PATH('')将它们连接起来:

DECLARE @str VARCHAR(100) = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
;WITH E1(N) AS( -- 10 ^ 1 = 10 rows
SELECT 1 FROM(VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(N)
),
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b), -- 10 ^ 2 = 100 rows
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b), -- 10 ^ 4 = 10,000 rows
CteTally(N) AS(
SELECT TOP(LEN(@str)) ROW_NUMBER() OVER(ORDER BY(SELECT NULL))
FROM E4
)
SELECT  (
SELECT TOP(32)
SUBSTRING(@str, N, 1)
FROM CteTally t
ORDER BY NEWID()
FOR XML PATH('')
) AS Result

在线演示

以上内容更多的是一个通用的随机字符串生成器。您可以根据需要对其进行修改。如果需求不会改变,你可以简单地使用这个:

DECLARE @str VARCHAR(100) = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
;WITH E1(N) AS( -- 52 Rows
SELECT 1 FROM( VALUES
(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),
(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),
(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),
(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),
(1),(1),(1),(1),(1),(1),(1),(1),(1),(1),
(1),(1)
)t(N)
),
CteTally(N) AS(
SELECT ROW_NUMBER() OVER(ORDER BY(SELECT NULL))
FROM E1
)
SELECT  (
SELECT TOP(32)
SUBSTRING(@str, N, 1)
FROM CteTally t
ORDER BY NEWID()
FOR XML PATH('')
) AS Result

我使其足够通用,可以处理任何字符池和任何输出长度。其核心思想是取一个随机的字节序列,并使用基本转换算法将长数字转换为新的表示形式,然后使用您想要的字符作为其"数字"转换为字符串。

对于您的特定场景,我们需要大约183位,或log2(52)x 32,才能达到您想要的长度。使用newid()将生成唯一的比特序列,但它一次只能生成128个比特,并且一系列值被简单地级联,直到有足够的值为止。然后有了一个可操作的值,主循环本质上与我们从小学学到的长除法相同。中间计算保持在varbinary阵列中的适当位置,并且循环仅持续到获得足够的输出字符为止。每次迭代都会在新的基数中确定另一个低阶数字,这可能会提前终止,因为它们不会改变。如果输出不消耗至少一个newid()的全部,则该算法不能保证任何全局唯一性,因此请确保log2(len(pool))x输出长度至少为128。

目标基数,最终是字符池的长度,不能超过256。我通过设置@e的128字节最大长度来硬编码一个限制。对于问题@e只需要32字节长,并且可以根据需要向上或向下调整,或者仅定义为varbinary(max)。如果你需要更真正随机的东西,你可以找到另一个熵比特的来源,比如crypt_gen_random()。由于唯一性似乎是主要关注点,因此这个答案符合这一要求。顺便说一句,水池中重复的角色自然会为碰撞打开大门。

这是快速和通用的,它可以很容易地封装在一个函数中。更健壮的实现将处理这些额外的检查。

declare @characterPool varchar(256) =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
declare @outputLength int = 32;
declare @n int = 0; /* counter */
declare @numLoops int = ceiling(log(len(@characterPool)) / log(2) * @outputLength / 128)
declare @e varbinary(128) = 0x; /* entropy */
while @n < @numLoops
begin
set @e = cast(newid() as binary(16)); set @n += 1;
end
declare @b int; /* byte */
declare @d int; /* dividend */
declare @out varchar(128) = '';
declare @outputBase int = len(@characterPool);
declare @entropyBytes int = len(@e);
declare @m int = 0;
while @m < @outputLength
begin
set @b = 0; set @d = 0; set @n = 0;
while @n < @entropyBytes /* big-endian */
begin
set @b = (@b - @d * @outputBase) * 256 + cast(substring(@e, @n + 1, 1) as int);
set @d = @b / @outputBase;
set @e = cast(stuff(@e, @n + 1, 1, cast(@d as binary(1))) as varbinary(128));
set @n += 1;
end
set @out = substring(@characterPool, @b - @d * @outputBase + 1, 1) + @out;
set @m += 1;
end
select @out as "UniqueString"

http://rextester.com/EYAK79470

作为算法的一个简单测试,您只需以十六进制格式分配一个已知值,并确认输出(使用012345678ABCDEF作为字符池)是相同的十六进制值。以同样的方式,这显然适用于base64、二进制和八进制。

更新:主循环可以更快,因为不必迭代超过必要的字节数。我不知道crypt_gen_random()在速度或CPU使用方面与newid()相比如何,所以这一变化甚至可能不是一个净的积极因素,所以我只将其作为一种替代方案来探索。您需要将newid中的字节保留在小端,并将其余字节附加到前面。

declare @e varbinary(1024) = cast(newid() as binary(16));
declare @padBytes int = ceiling(log(len(@characterPool)) / log(2) * @outputLength) - 128;
if @padBytes > 0 set @e = crypt_gen_random(@padBytes) + @e; /* big end plus little end */

最新更新