问题
I need a stored procedure to generate @n
records, each with a unique random 8 digit number. This number must not be incremental and must not exist already in the table.
CREATE TABLE Codes
(
ID UNIQUEIDENTIFIER PRIMARY KEY,
Code INT,
CONSTRAINT UQ_Code UNIQUE(Code)
);
I can generate random numbers:
DECLARE @min int = 0,
@max int = 99999999,
@n INT = 100;
SELECT TOP (@n) FLOOR(CAST(CRYPT_GEN_RANDOM(4) AS BIGINT) / 4294967296 * ((@max - @min) + 1)) + @min
FROM sys.all_objects s1
CROSS JOIN sys.all_objects s2;
But what I'm struggling to figure out is how to atomically generate and insert @n
numbers into the [Codes]
table whilst making provision to avoid collisions. Can this be done without a loop?
Update
By "must not be incremental"
I simply meant that for each call to the SP
, I don't want it to return "1, 2, 3, 4"
or any other common pattern. I need to be able to consume all values so ultimately incremental values will exist but will be generated at different points in time rather than sequentially.
回答1:
You can use cte with calculated codes, distinct
and check if the Code already exists in your table:
;with cte_stream as (
select
floor(cast(crypt_gen_random(4) as bigint) / 4294967296 * ((@max - @min) + 1)) + @min as Code
from sys.all_objects as s1
cross join sys.all_objects as s2;
)
insert into [Codes]
select distinct top (@n) s.Code
from cte_stream as s
where not exists (select * from [Codes] as c where c.Code = s.Code)
So distinct
helps you to avoid collision between new codes and exists
help you to avoid collisions with already existing codes in the [Codes]
table, and order by newid()
helps you to get random values from new codes
回答2:
You can try this :
DECLARE @min int = 0,
@max int = 99999999,
@n INT = 100;
Insert Into Codes(ID, Code)
SELECT G, RandomNumber
From (
Select TOP (@n) NEWID() As G, FLOOR(CAST(CRYPT_GEN_RANDOM(4) AS BIGINT) / 4294967296 * ((@max - @min) + 1)) + @min As RandomNumber
FROM sys.all_objects s1
CROSS JOIN sys.all_objects s2) AS Ran
Where RandomNumber Not IN (Select Code From Codes);
Edit: I added the where condition to avoid existing codes.
回答3:
You can try the following:
DECLARE @c INT = 10
;WITH cteDigits AS (SELECT d FROM (VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(d)),
cteCombinations AS (SELECT @c + 100 AS rn
UNION ALL
SELECT rn + 1 FROM cteCombinations WHERE rn < @c + 99 + @c
)
SELECT 99999999 - ca1.d*10000000
- ca2.d*1000000
- ca3.d*100000
- ca4.d*10000
- ca5.d*1000
- ca6.d*100
- ca7.d*10
- ca8.d AS Code
FROM cteCombinations c
CROSS APPLY(SELECT TOP 1 d FROM cteDigits d WHERE d < 9 AND d.d <> c.rn ORDER BY NEWID()) ca1
CROSS APPLY(SELECT TOP 1 d FROM cteDigits d WHERE d.d <> c.rn ORDER BY NEWID()) ca2
CROSS APPLY(SELECT TOP 1 d FROM cteDigits d WHERE d.d <> c.rn ORDER BY NEWID()) ca3
CROSS APPLY(SELECT TOP 1 d FROM cteDigits d WHERE d.d <> c.rn ORDER BY NEWID()) ca4
CROSS APPLY(SELECT TOP 1 d FROM cteDigits d WHERE d.d <> c.rn ORDER BY NEWID()) ca5
CROSS APPLY(SELECT TOP 1 d FROM cteDigits d WHERE d.d <> c.rn ORDER BY NEWID()) ca6
CROSS APPLY(SELECT TOP 1 d FROM cteDigits d WHERE d.d <> c.rn ORDER BY NEWID()) ca7
CROSS APPLY(SELECT TOP 1 d FROM cteDigits d WHERE d.d <> c.rn ORDER BY NEWID()) ca8
OPTION(MAXRECURSION 0)
Output:
Code
82520154
41164702
16701568
23744767
34570681
18158118
17548441
57261417
18272038
16576412
The idea is to generate random digits from 1 to 9
eight times
1, 4, 6, 2, 8, 9, 4, 3
5, 8, 1, 1, 5, 7, 5, 1
....
then just subtract formula from 99999999
.
EDIT:
To avoid collisions you can do a left join:
Insert Into Codes
Select Top(@n) t.Code
From(
SELECT FLOOR(CAST(CRYPT_GEN_RANDOM(4) AS BIGINT) / 4294967296 * ((@max - @min) + 1)) + @min AS Code
FROM sys.all_objects s1
CROSS JOIN sys.all_objects s2) t
Left Join Codes c on t.Code = c.Code
Where c.Code Is NULL
EDIT2:
I have created table where those codes are already randomly inserted:
CREATE TABLE Codes(ID BIGINT NOT NULL IDENTITY(1, 1) PRIMARY KEY, Code BIGINT NOT NULL, IsUsed BIT NOT NULL)
GO
;WITH t AS(
SELECT 10000000 + ROW_NUMBER() OVER(ORDER BY (SELECT 1)) rn
FROM
(VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS t1(n)
CROSS JOIN (VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS t2(n)
CROSS JOIN (VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS t3(n)
CROSS JOIN (VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS t4(n)
CROSS JOIN (VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS t5(n)
CROSS JOIN (VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS t6(n)
CROSS JOIN (VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS t7(n)
CROSS JOIN (VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS t8(n)
)
INSERT INTO Codes
SELECT rn, 0 FROM t WHERE rn <= 99999999
ORDER BY NEWID()
CREATE CLUSTERED INDEX someindex ON codes(code)
GO
CREATE INDEX someindex2 ON codes(IsUsed)
GO
This step takes about 10 minutes. Then just use update statement with output:
;WITH cte
AS ( SELECT TOP 1000
*
FROM dbo.Codes
WHERE IsUsed = 0
ORDER BY id
)
UPDATE cte
SET IsUsed = 1
OUTPUT Inserted.Code
It updates bit and returns updated codes. It uses index seek and thus is super fast and updates and returns 100.000 rows within 1 sec.
来源:https://stackoverflow.com/questions/29803927/generate-list-of-new-unique-random-numbers-in-t-sql