问题描述如下:
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
示例输出:445rpxlKYPkj1pg4q8nAy7Ab91zxZ8v1
我可以使用Java来做到这一点,但如果您可以帮助我在MS SQL或T-SQL上执行此操作,我将非常感谢。
答案 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
以上是一个通用的随机字符串生成器。您可以根据需要进行修改。如果要求不会改变,您可以简单地使用:
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 */