tags:

views:

1368

answers:

6

If you wanted to generate a pseudorandom alphanumeric string using T-SQL, how would you do it? How would you exclude characters like dollar signs, dashes, and slashes from it?

A: 

I came across this blog post first, then came up with the following stored procedure for this that I'm using on a current project (sorry for the weird formatting):

CREATE PROCEDURE [dbo].[SpGenerateRandomString]
@sLength tinyint = 10,
@randomString varchar(50) OUTPUT
AS
BEGIN
SET NOCOUNT ON
DECLARE @counter tinyint
DECLARE @nextChar char(1)
SET @counter = 1
SET @randomString = ”

WHILE @counter <= @sLength
BEGIN
SELECT @nextChar = CHAR(48 + CONVERT(INT, (122-48+1)*RAND()))

IF ASCII(@nextChar) not in (58,59,60,61,62,63,64,91,92,93,94,95,96)
BEGIN
SELECT @randomString = @randomString + @nextChar
SET @counter = @counter + 1
END
END
END
Scott A. Lawrence
A: 

I did this in SQL 2000 by creating a table that had characters I wanted to use, creating a view that selects characters from that table ordering by newid(), and then selecting the top 1 character from that view.

CREATE VIEW dbo.vwCodeCharRandom
AS
SELECT TOP 100 PERCENT 
    CodeChar
FROM dbo.tblCharacter
ORDER BY 
    NEWID()

...

SELECT TOP 1 CodeChar FROM dbo.vwCodeCharRandom

Then you can simply pull characters from the view and concatenate them as needed.

EDIT: Inspired by Stephan's response...

select top 1 RandomChar from tblRandomCharacters order by newid()

No need for a view (in fact I'm not sure why I did that - the code's from several years back). You can still specify the characters you want to use in the table.

Mayo
A: 

You can do it aswell with the following line:

select substring(hashbytes('MD5', rand(convert(varbinary, newid()))), 1, 6) as randomstring

Downside is that the word has a maximum of 32 characters and only uses 0-9 and a-f.

EDIT: yes you're right, corrected.

Fabian
When I tried this select, I got the following error message from SQL Server Management Studio Express:Msg 8116, Level 16, State 1, Line 1Argument data type float is invalid for argument 2 of hashbytes function.
Scott A. Lawrence
+1  A: 

Using a guid

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

very short ...

Stefan Steinegger
+1, very easy. Combine with RIGHT/SUBSTRING to truncate it to the needed length.
Johannes Rudolph
+2  A: 

Similar to the first example, but with more flexibility:

-- min length = 8, max length = 12
SET @Length = RAND() * 4 + 8    

-- 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 = DataLength(@CharPool)

SET @LoopCount = 0
SET @RandomString = ''

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

I forgot to mention one of the other features that makes this more flexible. By repeating blocks of characters in @CharPool, you can increase the weighting on certain characters so that they are more likely to be chosen.

Chris Judge
+2  A: 

When generating random data, specially for test, it is very usefull to make the data random, but reproductible. The secret is to use explicit seeds for the random funciton, so that when the test is run again with the same seed, it produces again exactly the same strings. Here is a simplified example of a funciton that generates object names in a reproducible manner:

create 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)

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

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

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

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

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

When running the tests the caller generates a random seed it associates with the test run (saves it in the results table), then passed along the seed, similar to this:

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;
Remus Rusanu
Note that the re-seeding in my example is mostly to ilustrate the point. In practice its enough to seed the RNG once per session, as long as the call sequence is deterministic afterward.
Remus Rusanu