407

I need a different random number for each row in my table. The following seemingly obvious code uses the same random value for each row.

SELECT table_name, RAND() magic_number 
FROM information_schema.tables 

I'd like to get an INT or a FLOAT out of this. The rest of the story is I'm going to use this random number to create a random date offset from a known date, e.g. 1-14 days offset from a start date.

This is for Microsoft SQL Server 2000.

Luke Girvin
  • 13,221
  • 9
  • 64
  • 84
MatthewMartin
  • 32,326
  • 33
  • 105
  • 164
  • 6
    Is there a solution to this that does not use NEWID()? I want to be able to generate the same sequence of random numbers for a given seed. – Rory MacLeod Apr 30 '10 at 15:54
  • 2
    @Rory Ask that as new question, it will get more attention. (My answer would be to use fixed tables of random numbers, eg. For example this famous standard set of random number: http://www.rand.org/pubs/monograph_reports/MR1418/index.html ) – MatthewMartin Apr 30 '10 at 16:37
  • 3
    Look @ [RAND (Transact-SQL)](http://technet.microsoft.com/en-us/library/ms177610.aspx) – AminM Mar 30 '14 at 08:32
  • 1
    RAND was introduced in 2005, this question was asked in 2009, which organizations still used SQL 2000 because that was the 1st version good enough to use forever. – MatthewMartin Mar 30 '14 at 14:43
  • 1
    Rory MacLeod asked, "Is there a solution to this that does not use NEWID()? I want to be able to generate the same sequence of random numbers for a given seed." The answer is yes, but its a bit convoluted. 1. Create a view that returns select rand() 2. Create a UDF that selects the value from the view. 3. Before selecting your data, seed the rand() function. 4. Use the UDF in your select statement. I'll post a full example below – Mitselplik May 11 '15 at 17:05

21 Answers21

609

Take a look at SQL Server - Set based random numbers which has a very detailed explanation.

To summarize, the following code generates a random number between 0 and 13 inclusive with a uniform distribution:

ABS(CHECKSUM(NewId())) % 14

To change your range, just change the number at the end of the expression. Be extra careful if you need a range that includes both positive and negative numbers. If you do it wrong, it's possible to double-count the number 0.

A small warning for the math nuts in the room: there is a very slight bias in this code. CHECKSUM() results in numbers that are uniform across the entire range of the sql Int datatype, or at least as near so as my (the editor) testing can show. However, there will be some bias when CHECKSUM() produces a number at the very top end of that range. Any time you get a number between the maximum possible integer and the last exact multiple of the size of your desired range (14 in this case) before that maximum integer, those results are favored over the remaining portion of your range that cannot be produced from that last multiple of 14.

As an example, imagine the entire range of the Int type is only 19. 19 is the largest possible integer you can hold. When CHECKSUM() results in 14-19, these correspond to results 0-5. Those numbers would be heavily favored over 6-13, because CHECKSUM() is twice as likely to generate them. It's easier to demonstrate this visually. Below is the entire possible set of results for our imaginary integer range:

Checksum Integer: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Range Result:     0 1 2 3 4 5 6 7 8 9 10 11 12 13  0  1  2  3  4  5

You can see here that there are more chances to produce some numbers than others: bias. Thankfully, the actual range of the Int type is much larger... so much so that in most cases the bias is nearly undetectable. However, it is something to be aware of if you ever find yourself doing this for serious security code.

Scott
  • 3,663
  • 8
  • 33
  • 56
SQLMenace
  • 132,095
  • 25
  • 206
  • 225
  • 29
    This linked page had the solution: ABS(CHECKSUM(NewId())) % 14 – MatthewMartin Jun 25 '09 at 17:35
  • 8
    % 14 would return numbers between 0 and 13 – CoderDennis Jun 25 '09 at 17:39
  • 8
    @Dennis Palmer, just add 1 – KM. Jun 25 '09 at 17:48
  • 4
    Just to expand on the bias bit, if you're generating numbers in a very large range, the bias gets bigger. For example if you're using 2/3 of the Integer number space, you're twice as likely to get numbers in the lower half of the produced number space than in the upper half, so even for even some ad-hoc problems this could have a big impact. Also, I believe that if you're % number is a power of 2 then there should be no bias... – Jeff Apr 28 '14 at 23:45
  • 84
    We just discovered a genius bug with this. Because checksum returns an int, and the range of an int is -2^31 (-2,147,483,648) to 2^31-1 (2,147,483,647), the abs() function can return an overflow error if the result happens to be exactly -2,147,483,648! The chances are obviously very low, about 1 in 4 billion, however we were running it over a ~1.8b row table every day, so it was happening about once a week! Fix is to cast the checksum to bigint before the abs. – EvilPuppetMaster Jan 29 '16 at 01:11
  • 2
    I was hitting @EvilPuppetMaster 's bug with a much smaller dataset - only 350k rows. The trigger was wrapping an outer select around it and asking for AVG() of the random number column. Also instead of a static Mod (e.g. 14) I was using `COUNT ( * ) OVER ( PARTITION BY 1 )`. Anyway @EvilPuppetMaster your fix rocks! Thanks and stay Evil. My code is now: `ABS ( CAST ( CHECKSUM ( NewId() ) AS bigint ) ) % COUNT ( * ) OVER ( PARTITION BY 1 ) + 1 AS [Random Row Number]` – Mike Honey Jun 29 '16 at 00:41
  • 6
    @EvilPuppetMaster, @MikeHoney Instead of casting to `bigint`, you could just perform the `%` operation first (it returns the same magnitude remainder whether positive or negative), then perform the `abs` operation: `abs(checksum(newid()) % 14)`. This should give the same result without the cast. – neizan Oct 19 '16 at 12:56
  • 20
    I think that this should say "a uniform distribution" not "normalized distribution" -- each number is equally likely, it's not a bell curve."Normalized" has a specific mathematical meaning. – AnotherParker May 20 '17 at 06:56
  • Note that if used in a `WHERE` clause, the expression is still only evaluated once for the entire row set, as is the original issue that OP faced. For example, instead of omitting 90% of records in the table, 90% of the time it omits 100% of records (Anchorman movie reference) with this query: `select * from @Test where ABS(CHECKSUM(NEWID())) % 100 >= 90` – Elaskanator Feb 06 '19 at 18:11
  • In that case you might want to try the `SELECT TOP(@n) PERCENT *` [syntax](https://stackoverflow.com/questions/848872) where `@n` is a value from 0 to 100 that you want as a percent *inclusion*. But you then need to provide an `ORDER BY` clause (hint: use `ORDER BY NEWID()`). Also note that, unless `@n` is exactly 0, at least one row will always be returned. – Elaskanator Feb 06 '19 at 18:19
  • It's actually not a uniform distribution, because the number of 32-bit numbers is not evenly divisible by 14. Some numbers are more probable than others. – vy32 Sep 09 '20 at 03:22
115

When called multiple times in a single batch, rand() returns the same number.

I'd suggest using convert(varbinary,newid()) as the seed argument:

SELECT table_name, 1.0 + floor(14 * RAND(convert(varbinary, newid()))) magic_number 
FROM information_schema.tables

newid() is guaranteed to return a different value each time it's called, even within the same batch, so using it as a seed will prompt rand() to give a different value each time.

Edited to get a random whole number from 1 to 14.

Liam
  • 27,717
  • 28
  • 128
  • 190
Jeremy Smyth
  • 23,270
  • 2
  • 52
  • 65
  • How do you get a number out of a guid or varbinary? I'll update the question to indicate I'm hoping for an integer. – MatthewMartin Jun 25 '09 at 17:26
  • 1
    You multiply it by a number and floor it :) so if you want five digits, multiply by 100000, and convert to an int. Ugly, but simple enough to do. – Jeremy Smyth Jun 25 '09 at 17:27
  • 1
    As a further addendum - that will give you _up to_ five digits - if you want to zero-pad it, you'll have to use a char datatype, and use replicate to zero-pad up to 5 digits. – Jeremy Smyth Jun 25 '09 at 17:28
  • If you use the ceiling function instead of floor, you don't have to add 1. – PopeDarren Sep 24 '18 at 16:24
  • 1
    Even when I use this, there are times that RAND() always gives me the same result. Even stranger, there are times that it jumps from a correct to an incorrect behaviour depending on the number of times I am using it. I am trying to implement a RANDOM INNER JOIN and if I ask for more than 19 (!!!) rows, it starts giving me always the same result... – Johannes Wentu May 02 '19 at 14:18
  • @JohannesWentu do you think in possibility of Sql Server is *maybe* reusing the previous plans, so you get the same results? Maybe you can use a Sql Hint to force avoid this. – Click Ok Apr 29 '20 at 15:18
96
RAND(CHECKSUM(NEWID()))

The above will generate a (pseudo-) random number between 0 and 1, exclusive. If used in a select, because the seed value changes for each row, it will generate a new random number for each row (it is not guaranteed to generate a unique number per row however).

Example when combined with an upper limit of 10 (produces numbers 1 - 10):

CAST(RAND(CHECKSUM(NEWID())) * 10 as INT) + 1

Transact-SQL Documentation:

  1. CAST(): https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql
  2. RAND(): http://msdn.microsoft.com/en-us/library/ms177610.aspx
  3. CHECKSUM(): http://msdn.microsoft.com/en-us/library/ms189788.aspx
  4. NEWID(): https://learn.microsoft.com/en-us/sql/t-sql/functions/newid-transact-sql
naXa stands with Ukraine
  • 35,493
  • 19
  • 190
  • 259
Aaron Hoffman
  • 6,604
  • 8
  • 56
  • 61
47

Random number generation between 1000 and 9999 inclusive:

FLOOR(RAND(CHECKSUM(NEWID()))*(9999-1000+1)+1000)

"+1" - to include upper bound values(9999 for previous example)

Volodymyr
  • 1,209
  • 1
  • 15
  • 26
  • 1
    The upper bound is exclusive with this method, so if you want to include the top number you would need to do `FLOOR(RAND(CHECKSUM(NEWID()))*(10000-1000)+1000)` – vaindil Jul 23 '18 at 15:36
30

Answering the old question, but this answer has not been provided previously, and hopefully this will be useful for someone finding this results through a search engine.

With SQL Server 2008, a new function has been introduced, CRYPT_GEN_RANDOM(8), which uses CryptoAPI to produce a cryptographically strong random number, returned as VARBINARY(8000). Here's the documentation page: https://learn.microsoft.com/en-us/sql/t-sql/functions/crypt-gen-random-transact-sql

So to get a random number, you can simply call the function and cast it to the necessary type:

select CAST(CRYPT_GEN_RANDOM(8) AS bigint)

or to get a float between -1 and +1, you could do something like this:

select CAST(CRYPT_GEN_RANDOM(8) AS bigint) % 1000000000 / 1000000000.0
Andrei
  • 1,015
  • 1
  • 11
  • 19
  • 1
    Nice actually, when you’re worried about `NEWID()` running low or being semantically unfitting, or just need a positive integer from `ABS(CAST(CRYPT_GEN_RANDOM(4)AS INT))`. – dakab Oct 15 '20 at 06:47
  • NOTE: `CRYPT_GEN_RANDOM` cannot be called from within a db function. `"Invalid use of a side-effecting operator 'Crypt_Gen_Random' within a function."` Perhaps it writes to some system table. – jk7 Jun 08 '23 at 00:30
  • That is also true for NEWID() and RAND(). – jk7 Jun 08 '23 at 01:02
15

The Rand() function will generate the same random number, if used in a table SELECT query. Same applies if you use a seed to the Rand function. An alternative way to do it, is using this:

SELECT ABS(CAST(CAST(NEWID() AS VARBINARY) AS INT)) AS [RandomNumber]

Got the information from here, which explains the problem very well.

Community
  • 1
  • 1
MicSim
  • 26,265
  • 16
  • 90
  • 133
7

Do you have an integer value in each row that you could pass as a seed to the RAND function?

To get an integer between 1 and 14 I believe this would work:

FLOOR( RAND(<yourseed>) * 14) + 1
CoderDennis
  • 13,642
  • 9
  • 69
  • 105
  • This works in theory, but in practice I've found the `RAND()` doesn't appear to be very random for minor changes in ``. For example a quick test I did: I let `` be 184380, 184383, 184386, and the corresponding `RAND()` values were: 0.14912, 0.14917, 0.14923. – ImaginaryHuman072889 Oct 21 '19 at 11:53
  • Maybe to get some more "seemingly" random results, try something like: `RAND()*100000) - FLOOR(RAND()*100000)` – ImaginaryHuman072889 Oct 21 '19 at 12:01
7
select round(rand(checksum(newid()))*(10)+20,2)

Here the random number will come in between 20 and 30. round will give two decimal place maximum.

If you want negative numbers you can do it with

select round(rand(checksum(newid()))*(10)-60,2)

Then the min value will be -60 and max will be -50.

jps
  • 20,041
  • 15
  • 75
  • 79
Tirthankar
  • 71
  • 1
  • 1
6

If you need to preserve your seed so that it generates the "same" random data every time, you can do the following:

1. Create a view that returns select rand()

if object_id('cr_sample_randView') is not null
begin
    drop view cr_sample_randView
end
go

create view cr_sample_randView
as
select rand() as random_number
go

2. Create a UDF that selects the value from the view.

if object_id('cr_sample_fnPerRowRand') is not null
begin
    drop function cr_sample_fnPerRowRand
end
go

create function cr_sample_fnPerRowRand()
returns float
as
begin
    declare @returnValue float
    select @returnValue = random_number from cr_sample_randView
    return @returnValue
end
go

3. Before selecting your data, seed the rand() function, and then use the UDF in your select statement.

select rand(200);   -- see the rand() function
with cte(id) as
(select row_number() over(order by object_id) from sys.all_objects)
select 
    id,
    dbo.cr_sample_fnPerRowRand()
from cte
where id <= 1000    -- limit the results to 1000 random numbers
Mitselplik
  • 1,079
  • 1
  • 12
  • 16
5

try using a seed value in the RAND(seedInt). RAND() will only execute once per statement that is why you see the same number each time.

northpole
  • 10,244
  • 7
  • 35
  • 58
  • Simplest! Though the values seem a lot more scattered, using digits from the middle of that, like `RIGHT(CONVERT(BIGINT, RAND(RecNo) * 1000000000000), 2)` (note: I'm seeing `RIGHT` implicitly convert the `BIGINT` to `CHAR`, but to be rigorous, you'd have another `CONVERT` in there). – Doug_Ivison Nov 11 '15 at 17:06
5

If you don't need it to be an integer, but any random unique identifier, you can use newid()

SELECT table_name, newid() magic_number 
FROM information_schema.tables
5

You would need to call RAND() for each row. Here is a good example

https://web.archive.org/web/20090216200320/http://dotnet.org.za/calmyourself/archive/2007/04/13/sql-rand-trap-same-value-per-row.aspx

David
  • 5,356
  • 2
  • 26
  • 39
  • Dead link :( Any copies that could be included into the answer? – jocull Oct 15 '14 at 18:14
  • He puts `RAND()` into a view, puts a `SELECT` of that view into a function, and then calls the function from anywhere. Clever. – Doug_Ivison Nov 11 '15 at 16:55
  • I posted a solution that solves the problem in exactly the same way as in the linked article, but here in this blog directly as an answer five posts ago! No one called me clever *envy face* hehe – Mitselplik Jun 05 '16 at 03:57
4

The problem I sometimes have with the selected "Answer" is that the distribution isn't always even. If you need a very even distribution of random 1 - 14 among lots of rows, you can do something like this (my database has 511 tables, so this works. If you have less rows than you do random number span, this does not work well):

SELECT table_name, ntile(14) over(order by newId()) randomNumber 
FROM information_schema.tables

This kind of does the opposite of normal random solutions in the sense that it keeps the numbers sequenced and randomizes the other column.

Remember, I have 511 tables in my database (which is pertinent only b/c we're selecting from the information_schema). If I take the previous query and put it into a temp table #X, and then run this query on the resulting data:

select randomNumber, count(*) ct from #X
group by randomNumber

I get this result, showing me that my random number is VERY evenly distributed among the many rows:

enter image description here

Trevor
  • 4,620
  • 2
  • 28
  • 37
4

It's as easy as:

DECLARE @rv FLOAT;
SELECT @rv = rand();

And this will put a random number between 0-99 into a table:

CREATE TABLE R
(
    Number int
)

DECLARE @rv FLOAT;
SELECT @rv = rand();

INSERT INTO dbo.R
(Number)
    values((@rv * 100));

SELECT * FROM R
brasofilo
  • 25,496
  • 15
  • 91
  • 179
3

Use newid()

select newid()

or possibly this

select binary_checksum(newid())
Pang
  • 9,564
  • 146
  • 81
  • 122
Chris Klepeis
  • 9,783
  • 16
  • 83
  • 149
3
select ABS(CAST(CAST(NEWID() AS VARBINARY) AS INT)) as [Randomizer]

has always worked for me

theteague
  • 413
  • 4
  • 10
3

If you want to generate a random number between 1 and 14 inclusive.

SELECT CONVERT(int, RAND() * (14 - 1) + 1)

OR

SELECT ABS(CHECKSUM(NewId())) % (14 -1) + 1
Useme Alehosaini
  • 2,998
  • 6
  • 18
  • 26
ckb
  • 31
  • 1
2
    DROP VIEW IF EXISTS vwGetNewNumber;
    GO
    Create View vwGetNewNumber
    as
    Select CAST(RAND(CHECKSUM(NEWID())) * 62 as INT) + 1 as NextID,
    'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'as alpha_num;

    ---------------CTDE_GENERATE_PUBLIC_KEY -----------------
    DROP FUNCTION IF EXISTS CTDE_GENERATE_PUBLIC_KEY;  
    GO
    create function CTDE_GENERATE_PUBLIC_KEY()
    RETURNS NVARCHAR(32)
    AS 
    BEGIN
        DECLARE @private_key NVARCHAR(32);
        set @private_key = dbo.CTDE_GENERATE_32_BIT_KEY();
        return @private_key;
    END;
    go

---------------CTDE_GENERATE_32_BIT_KEY -----------------
DROP FUNCTION IF EXISTS CTDE_GENERATE_32_BIT_KEY;  
GO
CREATE function CTDE_GENERATE_32_BIT_KEY()
RETURNS NVARCHAR(32)
AS 
BEGIN
    DECLARE @public_key NVARCHAR(32);
    DECLARE @alpha_num NVARCHAR(62);
    DECLARE @start_index INT = 0;
    DECLARE @i INT = 0;
    select top 1 @alpha_num = alpha_num from vwGetNewNumber;
        WHILE @i < 32
        BEGIN
          select top 1 @start_index = NextID from vwGetNewNumber;
          set @public_key = concat (substring(@alpha_num,@start_index,1),@public_key);
          set @i = @i + 1;
        END;
    return @public_key;
END;
    select dbo.CTDE_GENERATE_PUBLIC_KEY() public_key;
ichak khoury
  • 217
  • 2
  • 11
  • 1
    sorry @arnt if i did not explain well, – ichak khoury Jun 28 '18 at 07:02
  • 1
    sorry @arnt , we have here two functions **CTDE_GENERATE_32_BIT_KEY** that generates a 32 bit alphanumeric key (can be extended to be more or less) and the other one called **CTDE_GENERATE_PUBLIC_KEY** that calls the first function and return back public key of 32 bit or you can return a private key of 16 bit ... you just need to call **select dbo.CTDE_GENERATE_PUBLIC_KEY() as a public key;** the logic behind is that we select one character from the alphanumeric character list 32 times and concatenate them together in order to get the random alphanumeric key. after research. – ichak khoury Jun 28 '18 at 07:13
  • 1
    Nice. That explanation makes it a much better answer. (Someone flagged it for deletion; I voted to leave it open and left that comment for you.) – arnt Jun 28 '18 at 07:15
2
Update my_table set my_field = CEILING((RAND(CAST(NEWID() AS varbinary)) * 10))

Number between 1 and 10.

Pang
  • 9,564
  • 146
  • 81
  • 122
user3478586
  • 307
  • 4
  • 15
1

Try this:

SELECT RAND(convert(varbinary, newid()))*(b-a)+a magic_number 

Where a is the lower number and b is the upper number

Captain Jack Sparrow
  • 971
  • 1
  • 11
  • 28
Rutendo
  • 39
  • 2
1

If you need a specific number of random number you can use recursive CTE:

;WITH A AS (
        SELECT 1 X, RAND() R
    UNION ALL
        SELECT X + 1, RAND(R*100000) --Change the seed
        FROM A
        WHERE X < 1000 --How many random numbers you need
    )
SELECT
    X
    , RAND_BETWEEN_1_AND_14 = FLOOR(R * 14 + 1)
FROM A
OPTION (MAXRECURSION 0) --If you need more than 100 numbers