使用T-SQL生成随机字符串

123
如果您想使用T-SQL生成伪随机的字母数字字符串,应该如何实现?如何从字符串中排除美元符号、破折号和斜杠等字符?

1
大家要小心,这里的大多数解决方案都是随机生成一些非常不当的词语的好方法。 - Jeff Moden
31个回答

250

使用 GUID

SELECT @randomString = CONVERT(varchar(255), NEWID())

非常简短的...


10
+1,非常简单。使用RIGHT/SUBSTRING将其截断为所需长度。 - Johannes Rudolph
2
我喜欢这个-简单而甜美。虽然它没有“可预测性”功能,但非常适合数据生成。 - madhurtanwani
14
不要使用RIGHT/SUBSTRING截断UUID,因为由于UUID的生成方式,它既不唯一也不随机! - ooxi
1
这很好,但如果您使用它,请确保阅读此内容:http://blogs.msdn.com/b/oldnewthing/archive/2008/06/27/8659071.aspx - 请注意,GUID是Microsoft对UUID的实现,无论如何,正如ooxi所提到的,您需要小心地切割UUID。 - Clarence Liu
10
NEWID()函数中的alpha字符是十六进制的,因此你只会得到A-F,而不是其他字母。从这个意义上讲,它是有限制的。 - smoore4
显示剩余3条评论

63

类似于第一个示例,但更加灵活:

-- min_length = 8, max_length = 12
SET @Length = RAND() * 5 + 8
-- SET @Length = RAND() * (max_length - min_length + 1) + min_length

-- define allowable character explicitly - easy to read this way an easy to 
-- omit easily confused chars like l (ell) and 1 (one) or 0 (zero) and O (oh)
SET @CharPool = 
    'abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNPQRSTUVWXYZ23456789.,-_!$@#%^&*'
SET @PoolLength = Len(@CharPool)

SET @LoopCount = 0
SET @RandomString = ''

WHILE (@LoopCount < @Length) BEGIN
    SELECT @RandomString = @RandomString + 
        SUBSTRING(@Charpool, CONVERT(int, RAND() * @PoolLength) + 1, 1)
    SELECT @LoopCount = @LoopCount + 1
END

我忘了提到使这个程序更加灵活的另一个特性。你可以在@CharPool中重复字符块,从而增加某些字符的权重,使它们更可能被选择。


1
+1 这是一个很好的例程,我正在将其作为存储过程的一部分使用。 - Marcello Miorelli
4
不错的解决方案。不幸的是,它在用户自定义函数中无法使用。由于某些原因,它会出现以下错误:"函数内使用了具有副作用的运算符'rand',这是无效的。" - rdans
15
这个函数在 SUBSTRING() 调用中存在一个 bug。应该是 CONVERT(int, RAND() * @PoolLength) + 1(注意添加的 +1)。T-SQL 中的 SUBSTRING 从索引 1 开始,因此当前函数有时会添加一个空字符串(当索引为 0 时),而且永远不会添加 @CharPool 中的最后一个字符。 - Dana Cartwright
@PrathapGangireddy,请提出问题,评论某个人并不是提问的正确方式。 - Dana Cartwright
是的,我在这里发布了我的问题:https://stackoverflow.com/questions/58854667/using-crypt-gen-random-to-generate-unique-serial-numbers - Prathap Gangireddy

48

当生成随机数据,特别是用于测试时,使数据既随机又可重现非常有用。秘密在于为随机函数使用明确的种子,这样当使用相同的种子再次运行测试时,它将再次产生完全相同的字符串。以下是一个简化的函数示例,以可重复的方式生成对象名称:

alter procedure usp_generateIdentifier
    @minLen int = 1
    , @maxLen int = 256
    , @seed int output
    , @string varchar(8000) output
as
begin
    set nocount on;
    declare @length int;
    declare @alpha varchar(8000)
        , @digit varchar(8000)
        , @specials varchar(8000)
        , @first varchar(8000)
    declare @step bigint = rand(@seed) * 2147483647;

    select @alpha = 'qwertyuiopasdfghjklzxcvbnm'
        , @digit = '1234567890'
        , @specials = '_@# '
    select @first = @alpha + '_@';

    set  @seed = (rand((@seed+@step)%2147483647)*2147483647);

    select @length = @minLen + rand(@seed) * (@maxLen-@minLen)
        , @seed = (rand((@seed+@step)%2147483647)*2147483647);

    declare @dice int;
    select @dice = rand(@seed) * len(@first),
        @seed = (rand((@seed+@step)%2147483647)*2147483647);
    select @string = substring(@first, @dice, 1);

    while 0 < @length 
    begin
        select @dice = rand(@seed) * 100
            , @seed = (rand((@seed+@step)%2147483647)*2147483647);
        if (@dice < 10) -- 10% special chars
        begin
            select @dice = rand(@seed) * len(@specials)+1
                , @seed = (rand((@seed+@step)%2147483647)*2147483647);
            select @string = @string + substring(@specials, @dice, 1);
        end
        else if (@dice < 10+10) -- 10% digits
        begin
            select @dice = rand(@seed) * len(@digit)+1
                , @seed = (rand((@seed+@step)%2147483647)*2147483647);
            select @string = @string + substring(@digit, @dice, 1);
        end
        else -- rest 80% alpha
        begin
            declare @preseed int = @seed;
            select @dice = rand(@seed) * len(@alpha)+1
                , @seed = (rand((@seed+@step)%2147483647)*2147483647);

            select @string = @string + substring(@alpha, @dice, 1);
        end

        select @length = @length - 1;   
    end
end
go

在运行测试时,调用者生成一个与测试运行相关联的随机种子(保存在结果表中),然后将种子传递下去,类似于这样:

declare @seed int;
declare @string varchar(256);

select @seed = 1234; -- saved start seed

exec usp_generateIdentifier 
    @seed = @seed output
    , @string = @string output;
print @string;  
exec usp_generateIdentifier 
    @seed = @seed output
    , @string = @string output;
print @string;  
exec usp_generateIdentifier 
    @seed = @seed output
    , @string = @string output;
print @string;  

更新 2016-02-17:请参见下面的评论,原始过程在推进随机种子的方式上存在问题。我已更新代码,并修复了提到的差一问题。


请注意,我示例中的重新生成主要是为了说明该点。实际上,只要调用序列在此之后是确定性的,每个会话只需要对RNG进行一次种子播种即可。 - Remus Rusanu
3
我知道这是一个旧帖子,但是对于种子192804和529126,代码返回相同的字符串。 - davey
2
@RemusRusanu,我也很想看到对戴维评论的回复。 - Alex Gordon
2
种子的公式为 @seed = rand(@seed+1)*2147483647。对于值529126,下一个值为1230039262,然后下一个值是192804。在此之后,序列继续以相同的方式延续。输出应该因为第一个字符而不同,但由于 off-by-one 错误,它并没有区别:SUBSTRING(...,0,...) 对于索引0返回空字符串,并且对于529126,这个错误'隐藏'了生成的第一个字符。解决方法是计算 @dice = rand(@seed) * len(@specials)+1 以使索引基于1。 - Remus Rusanu
1
这个问题源于随机序列一旦达到共同的值,它们就会以相同的方式进行。如果需要,可以通过使 rand(@seed+1) 中的 +1 本身成为随机数,并仅从初始种子确定来避免这种情况。这样,即使序列达到相同的值,它们也会立即分歧。 - Remus Rusanu

42
使用以下代码返回一个短字符串:
SELECT SUBSTRING(CONVERT(varchar(40), NEWID()),0,9)

4
仅返回十六进制字符:0-9和A-F。 - jumxozizi

30

如果您正在运行SQL Server 2008或更高版本,则可以使用新的加密函数crypt_gen_random(),然后使用base64编码将其转换为字符串。这适用于长达8000个字符。

declare @BinaryData varbinary(max)
    , @CharacterData varchar(max)
    , @Length int = 2048

set @BinaryData=crypt_gen_random (@Length) 

set @CharacterData=cast('' as xml).value('xs:base64Binary(sql:variable("@BinaryData"))', 'varchar(max)')

print @CharacterData

这就是我想要的。谢谢!:) - undefined

21

我不是T-SQL专家,但我已经使用了最简单的方式:

select char((rand()*25 + 65))+char((rand()*25 + 65))

这将生成两个字符 (A-Z,在ASCII码中为65-90)。


15
select left(NEWID(),5)

这将返回 guid 字符串的前 5 个字符

Example run
------------
11C89
9DB02

1
尽管这个解决方案不适用于生产系统,因为在进行几千次或更多次操作后很容易出现重复,但它对于在调试或测试某些东西时快速轻松地获取随机字符串非常有用。 - Ian1971
我用这个程序为500个登录生成了一个随机的4位密码,对于这个目的来说它完美地工作。如果是用于大数据和其他目的,请使用更多字符。 - TheTechGuy
1
NEWID()不被认为足够随机以用于安全密码,因此根据您的要求,您需要小心。使用5个字符,在大约1500条记录后会发生冲突。在我的测试中,使用4个字符,您会在55-800条记录中任何地方发生冲突。 - Ian1971
@Ian1971 临时密码还可以使用。假设我为您的 ATM 卡提供了一个四位数的 PIN 码。同样的 PIN 码可能也会发给其他800个用户,但是您使用他的卡和密码的机会非常小。这基本上是一个随机数,对于临时访问来说还是可以的。 - TheTechGuy
让我们从右侧开始,在UUID的第一个八个字符后面有一个连字符,但是从右侧开始,在12之后有一个连字符。因此,right(NEWID(), 10) - Satish Patro

11

对于一个随机字母,您可以使用:

select substring('ABCDEFGHIJKLMNOPQRSTUVWXYZ',
                 (abs(checksum(newid())) % 26)+1, 1)

newid()rand()使用的一个重要区别是,如果您返回多行,newid()会针对每一行分别计算,而rand()则只计算整个查询一次。


8

另一个完整字母表的简单解决方案:

SELECT LEFT(REPLACE(REPLACE((SELECT CRYPT_GEN_RANDOM(16) FOR XML PATH(''), BINARY BASE64),'+',''),'/',''),16);

将两个16替换为所需的长度。

示例结果:

pzyMATe3jJwN1XkB

7
这是一个随机字母数字生成器。
print left(replace(newid(),'-',''),@length) //--@length is the length of random Num.

2
仅返回十六进制字符:0-9和A-F。 - jumxozizi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接