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

时间:2016-06-02 05:26:14

标签: sql-server tsql

问题描述如下:

  1. 生成唯一的字母数字字符。
  2. 字符长度应为32。
  3. 可以在当前时间播种唯一数字,以帮助生成数字的唯一性。
  4. 字母字符必须来自此池:abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
  5. 示例输出:445rpxlKYPkj1pg4q8nAy7Ab91zxZ8v1

    我可以使用Java来做到这一点,但如果您可以帮助我在MS SQL或T-SQL上执行此操作,我将非常感谢。

2 个答案:

答案 0 :(得分:3)

首先,您需要将字符串拆分为单独的行。然后,对SELECT进行随机排序ORDER BY NEWID()。最后,使用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

ONLINE DEMO

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

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

答案 1 :(得分:1)

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

对于您的特定情况,我们需要大约183位,或 log2(52)x 32 ,以达到您想要的长度。使用newid()将生成唯一的位序列,但它一次只能执行128位,并且只需连接一系列值,直到有足够的值。然后有一个值来操作,主循环基本上是我们从小学学到的同样长的分工。中间计算保留在varbinary数组中,循环继续,直到获得足够的输出字符。每次迭代都会确定新基数中的另一个低位数字,这可以提前终止,因为它们不会发生变化。如果输出不至少消耗所有newid(),则算法不能保证任何全局唯一性,因此请确保 log2(len(池))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 */
相关问题