101

Is there a succinct way to retrieve a random record from a sql server table?

I would like to randomize my unit test data, so am looking for a simple way to select a random id from a table. In English, the select would be "Select one id from the table where the id is a random number between the lowest id in the table and the highest id in the table."

I can't figure out a way to do it without have to run the query, test for a null value, then re-run if null.

Ideas?

Jeff Atwood
  • 63,320
  • 48
  • 150
  • 153
Jeremy
  • 9,023
  • 20
  • 57
  • 69
  • 2
    Are you sure you want to take this approach? Unit test data should not be random - in fact, you should be guaranteed to get the same results no matter how many times you execute the unit test. Having random data might violate this fundamental principle of unit testing. – rein May 08 '09 at 10:27
  • theres a couple of methods here http://www.brettb.com/SQL_Help_Random_Numbers.asp – Mesh Oct 10 '08 at 13:49
  • The link above from @Mesh is no longer active . – Robert Sievers Oct 18 '18 at 14:25

6 Answers6

177

Is there a succinct way to retrieve a random record from a sql server table?

Yes

SELECT TOP 1 * FROM table ORDER BY NEWID()

Explanation

A NEWID() is generated for each row and the table is then sorted by it. The first record is returned (i.e. the record with the "lowest" GUID).

Notes

  1. GUIDs are generated as pseudo-random numbers since version four:

    The version 4 UUID is meant for generating UUIDs from truly-random or pseudo-random numbers.

    The algorithm is as follows:

    • Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively.
    • Set the four most significant bits (bits 12 through 15) of the time_hi_and_version field to the 4-bit version number from Section 4.1.3.
    • Set all the other bits to randomly (or pseudo-randomly) chosen values.

    A Universally Unique IDentifier (UUID) URN Namespace - RFC 4122

  2. The alternative SELECT TOP 1 * FROM table ORDER BY RAND() will not work as one would think. RAND() returns one single value per query, thus all rows will share the same value.

  3. While GUID values are pseudo-random, you will need a better PRNG for the more demanding applications.

  4. Typical performance is less than 10 seconds for around 1,000,000 rows — of course depending on the system. Note that it's impossible to hit an index, thus performance will be relatively limited.

Sklivvz
  • 30,601
  • 24
  • 116
  • 172
  • Exactly what I was looking for. I had a feeling it was simpler than I was making it. – Jeremy Oct 10 '08 at 13:53
  • 1
    You are assuming that NEWID produces pseudorandom values. There is a good chance it will produced sequential values. NEWID just produces unique values. RAND, however, produces pseudo random values. – Skizz Oct 10 '08 at 13:54
  • I'm running it on a heavily indexed table with 1,671,145 rows, and it takes 7 seconds to return. The table is pretty optimal too - it's virtually the heart of our database so it's taken care of. – Tom Ritter Oct 10 '08 at 13:57
  • @ÂviewAnew. 1.6 million rows and 7 secs on a select that doesn't (and can't) hit an index is not bad. – Sklivvz Oct 10 '08 at 14:08
  • 7
    @Skizz, rand does not work like that. A SINGLE random value is generated before the SELECT. So if you try "SELECT TOP 10 RAND()... " you always get the same value – Sklivvz Oct 10 '08 at 14:10
  • Guess I'm just used to that table always being extremely fast then. Feel free to clean up the comments. – Tom Ritter Oct 10 '08 at 14:11
  • Selecting a row at random for 1.6 million rows is a pretty crazy attempt. 7 seconds is actually pretty impressive. If a random record from a set that large is required, I would approach the problem in a different manner. A – Ian Patrick Hughes Jan 19 '11 at 16:07
  • @Skizz NEWID does not produce any sequential values (were you thinking of NEWSEQUENTIALID?). It is merely a pseudo-random number, see: http://en.wikipedia.org/wiki/Globally_Unique_Identifier#Algorithm – Sklivvz Jan 19 '11 at 21:29
  • SQL Server 2008+ has `CRYPT_GEN_RANDOM` which, unlike `RAND`, is evaluated per row. This might be more appropriate given [GUIDs are designed to be unique, not random](http://blogs.msdn.com/b/oldnewthing/archive/2012/05/23/10309199.aspx) though [has some quirks on non patched versions](http://connect.microsoft.com/SQLServer/feedback/details/654809/crypt-gen-random-erroneously-treated-as-constant) – Martin Smith Jun 03 '12 at 19:28
  • @TomRitter - You can use `TABLESAMPLE` on large tables to avoid having to scan the whole thing. – Martin Smith Aug 26 '12 at 10:33
  • I read MSDN article and I think the @jangali way is most proper for big tables. – QMaster Sep 22 '16 at 11:15
  • `NEWID()` always is returning the same value: http://data.stackexchange.com/codegolf/query/764445 – sergiol Nov 28 '17 at 15:59
29

On larger tables you can also use TABLESAMPLE for this to avoid scanning the whole table.

SELECT  TOP 1 *
FROM YourTable
TABLESAMPLE (1000 ROWS)
ORDER BY NEWID()

The ORDER BY NEWID is still required to avoid just returning rows that appear first on the data page.

The number to use needs to be chosen carefully for the size and definition of table and you might consider retry logic if no row is returned. The maths behind this and why the technique is not suited to small tables is discussed here

Martin Smith
  • 438,706
  • 87
  • 741
  • 845
  • I found this on Microsoft's website: You can use TABLESAMPLE to quickly return a sample from a large table when either of the following conditions is true: The sample does not have to be a truly random sample at the level of individual rows. Rows on individual pages of the table are not correlated with other rows on the same page. – Mark Entingh Apr 21 '17 at 18:21
  • 1
    @MarkEntingh - In the case of `TOP 1` it doesn't matter if rows on the same page are correlated or not. You're only picking one of them. – Martin Smith Apr 21 '17 at 18:23
  • Could this be used to choose say the TOP @X (50 or set before) and where TABLESAMPLE (@Rows ROWS) based on a Count from a table? – PerPlexSystem Aug 24 '23 at 00:13
10

Also try your method to get a random Id between MIN(Id) and MAX(Id) and then

SELECT TOP 1 * FROM table WHERE Id >= @yourrandomid

It will always get you one row.

Neil N
  • 24,862
  • 16
  • 85
  • 145
Sklivvz
  • 30,601
  • 24
  • 116
  • 172
  • 2
    -1, This would only work when there are no missing ID's between min and max. If one is deleted then that same ID is generated by the random function, you will get zero records back. – Neil N Mar 26 '10 at 18:57
  • 6
    @Neil, not really - it will get you the first row with an Id greater than the random number if there are missing Ids. The problem here is that the probability of each row coming out is not constant. But then again this suffices in most cases. – Sklivvz Jan 19 '11 at 21:31
  • 1
    +1. For unit testing that should hit different values that is good enough - if you requie a real random, then this is something else. But in the OP context it should be good enough. – TomTom Jun 09 '12 at 09:08
7

If you want to select large data the best way that I know is:

SELECT * FROM Table1
WHERE (ABS(CAST(
    (BINARY_CHECKSUM
    (keycol1, NEWID())) as int))
    % 100) < 10

Source: MSDN

displayName
  • 13,888
  • 8
  • 60
  • 75
hmfarimani
  • 531
  • 1
  • 8
  • 13
  • I'm not sure but I think using the RAND() rather NEWID() to generate truly random numbers may be better because of disadvantages of using NEWID() in select process. – QMaster Sep 22 '16 at 11:17
  • I try using this method with exact number of records rather percent base, I did it with expand select range and limiting with TOP n, is there any suggestion? – QMaster Sep 22 '16 at 11:47
  • I found another problem with this scenario, If you use group by you will get the same order of randomly selected rows always, so it seems in small tables the @skilvvz approach is most proper. – QMaster Sep 22 '16 at 13:07
0

I was looking to improve on the methods I had tried and came across this post. I realize it's old but this method is not listed. I am creating and applying test data; this shows the method for "address" in a SP called with @st (two char state)

Create Table ##TmpAddress (id Int Identity(1,1), street VarChar(50), city VarChar(50), st VarChar(2), zip VarChar(5))
Insert Into ##TmpAddress(street, city, st, zip)
Select street, city, st, zip 
From tbl_Address (NOLOCK)
Where st = @st


-- unseeded RAND() will return the same number when called in rapid succession so
-- here, I seed it with a guaranteed different number each time. @@ROWCOUNT is the count from the most recent table operation.

Set @csr = Ceiling(RAND(convert(varbinary, newid())) * @@ROWCOUNT)

Select street, city, st, Right(('00000' + ltrim(zip)),5) As zip
From ##tmpAddress (NOLOCK)
Where id = @csr
likeitlikeit
  • 5,563
  • 5
  • 42
  • 56
0

If you really want a random sample of individual rows, modify your query to filter out rows randomly, instead of using TABLESAMPLE. For example, the following query uses the NEWID function to return approximately one percent of the rows of the Sales.SalesOrderDetail table:

SELECT * FROM Sales.SalesOrderDetail
WHERE 0.01 >= CAST(CHECKSUM(NEWID(), SalesOrderID) & 0x7fffffff AS float)
/ CAST (0x7fffffff AS int)

The SalesOrderID column is included in the CHECKSUM expression so that NEWID() evaluates once per row to achieve sampling on a per-row basis. The expression CAST(CHECKSUM(NEWID(), SalesOrderID) & 0x7fffffff AS float / CAST (0x7fffffff AS int) evaluates to a random float value between 0 and 1."

Source: http://technet.microsoft.com/en-us/library/ms189108(v=sql.105).aspx

This is further explained below:

How does this work? Let's split out the WHERE clause and explain it.

The CHECKSUM function is calculating a checksum over the items in the list. It is arguable over whether SalesOrderID is even required, since NEWID() is a function that returns a new random GUID, so multiplying a random figure by a constant should result in a random in any case. Indeed, excluding SalesOrderID seems to make no difference. If you are a keen statistician and can justify the inclusion of this, please use the comments section below and let me know why I'm wrong!

The CHECKSUM function returns a VARBINARY. Performing a bitwise AND operation with 0x7fffffff, which is the equivalent of (111111111...) in binary, yields a decimal value that is effectively a representation of a random string of 0s and 1s. Dividing by the co-efficient 0x7fffffff effectively normalizes this decimal figure to a figure between 0 and 1. Then to decide whether each row merits inclusion in the final result set, a threshold of 1/x is used (in this case, 0.01) where x is the percentage of the data to retrieve as a sample.

Source: https://www.mssqltips.com/sqlservertip/3157/different-ways-to-get-random-data-for-sql-server-data-sampling

XpiritO
  • 2,809
  • 5
  • 28
  • 34