简体   繁体   中英

Generate a random number which is not there in a table in sql server

I am looking for generating a random number which the generated number is not there on another table.

For Example: If a table called randomNums having the values 10,20,30,40,50 .

I like to generate a number apart from the above values.

I tried the following query.

Query

;WITH CTE AS
(
    SELECT FLOOR(RAND()*100) AS rn
)
SELECT rn FROM CTE
WHERE rn NOT IN (SELECT num FROM randomNums);

But sometimes this query returns nothing.
Because that time it generates the number which is there in the table randomNums .

How to solve this issue?

Fiddle for reference

Yet another option, I've always liked NEWID() for random ordering, and cross joins create many rows very efficiently:

;with cte AS (SELECT 1 n UNION ALL SELECT 1)
     ,cte2 AS (SELECT TOP 100 ROW_NUMBER() OVER(ORDER BY a.n) n
               FROM cte a,cte b,cte c,cte d, cte e, cte f, cte g)
SELECT TOP 1 n
FROM cte2 a
WHERE NOT EXISTS (SELECT 1
                  FROM randomNums b
                  WHERE a.n = b.num)
ORDER BY NEWID()

Demo: SQL Fiddle

If you don't want to use a WHILE loop then you might look into this solution which employs a recursive CTE :

;WITH CTE AS
(
    SELECT FLOOR(RAND()*100) AS rn  

    UNION ALL

    SELECT s.rn 
    FROM (
       SELECT rn      
       FROM CTE 
       WHERE rn NOT IN (SELECT num FROM randomNums)                         
       ) t
    CROSS JOIN (SELECT FLOOR(RAND()*100) AS rn) AS s
    WHERE t.rn IS NULL

)
SELECT rn
FROM CTE

EDIT:

As stated in comments below the above does not work: If the first generated number (from the CTE anchor member) is a number already present in randomNums , then the CROSS JOIN of the recursive member will return NULL , hence the number from the anchor member will be returned.

Here is a different version, based on the same idea of using a recursive CTE , that works:

DECLARE @maxAttempts INT = 100

;WITH CTE AS
(
   SELECT FLOOR(RAND()*100) AS rn,  
          1 AS i 

   UNION ALL

   SELECT FLOOR(RAND(CHECKSUM(NEWID()))*100) AS rn, i = i + 1
   FROM CTE AS c
   INNER JOIN randomNums AS r ON c.rn = r.num   
   WHERE (i = i) AND (i < @maxAttempts) 
)
SELECT TOP 1 rn
FROM CTE
ORDER BY i DESC

Here, the anchor member of the CTE firstly generates a random number. If this number is already present in randomNums the INNER JOIN of the recursive member will succeed, hence yet another random number will be generated. Otherwise, the INNER JOIN will fail and the recursion will terminate.

A couple of things more to note:

  • i variable is used to record the number of attempts made to generate a 'unique' random number.
  • The value of i is used in the INNER JOIN operation of the recursive member so as to join with the random value of the immediately preceding recursion only .
  • Since repetitive calls of RAND() with the same seed value return the same results, we have to use CHECKSUM(NEWID()) as the seed of RAND() .
  • @maxAttempts can optionally be used to specify the maximum number of attempts made in order to generate a 'unique' random number.

SQL Fiddle Demo here

Query

declare @RandomNums table (Num int);
insert into @RandomNums values (10),(20),(30),(40),(50),(60),(70),(80),(90);

-- Make a table of AvailableNumbers

with N as 
(
    select n from (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) t(n)    
),
AvailableNumbers as 
(      
    select -- top 97 -- limit as you need
        row_number() over(order by (select 1)) as Number
    from 
        N n1, N n2 --, N n3, N n4, N n5, N n6 -- multiply as you need
),

-- Find which of AvailableNumbers is Vacant

VacantNumbers as
(
    select
        OrdinalNumber = row_number() over(order by an.Number) ,
        an.Number
    from
        AvailableNumbers an 
        left join @RandomNums rn on rn.Num = an.number
    where
        rn.Num is null
)

-- select rundom VacantNumber by its OrdinalNumber in VacantNumbers

select
    Number
from
    VacantNumbers
where
    OrdinalNumber = floor(rand()*(select count(*) from VacantNumbers) + 1);

Another option could be to create an unique index on num value for table randomNums. Then in your code catch the possible error if duplicated key is generated, and in that case choose another number and re-try.

Try

declare @n as int

while @n is null and (select COUNT(*) from randomNums) < 100
Begin

;WITH CTE AS
(
    SELECT FLOOR(RAND()*100) AS rn
)
SELECT @n = rn FROM CTE
WHERE rn NOT IN (SELECT num FROM randomNums);

End

select @n

It would only be advisable to use this approach, if the number of exclusions is relatively small.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM