在T-SQL中生成新的唯一随机数列表

时间:2015-04-22 16:43:41

标签: sql sql-server tsql stored-procedures sql-server-2012

我需要一个存储过程来生成@n条记录,每条记录都有一个唯一的随机8位数字。此编号不得为增量编号,并且不得存在于表格中。

CREATE TABLE Codes
(
    ID UNIQUEIDENTIFIER PRIMARY KEY,
    Code INT,
    CONSTRAINT UQ_Code UNIQUE(Code) 
);

我可以生成随机数:

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;

但我正在努力弄清楚的是如何在@n表中原子地生成并插入[Codes]数字,同时提供避免冲突的规定。这可以在没有循环的情况下完成吗?

更新
"must not be incremental"我只是意味着每次拨打SP时,我都不希望它返回"1, 2, 3, 4"或任何其他常见模式。我需要能够使用所有值,因此最终将存在增量值,但将在不同的时间点而不是按顺序生成。

3 个答案:

答案 0 :(得分:1)

您可以将cte与计算代码distinct一起使用,并检查代码中是否已存在代码:

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

因此distinct可帮助您避免新代码之间发生冲突,exists可帮助您避免与[Codes]表中现有代码发生冲突,order by newid()可帮助您从新代码中获取随机值

答案 1 :(得分:0)

你可以试试这个:

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

编辑: 我添加了where条件以避免现有代码。

答案 2 :(得分:0)

您可以尝试以下操作:

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)

输出:

Code
82520154
41164702
16701568
23744767
34570681
18158118
17548441
57261417
18272038
16576412

这个想法是从1 to 9八次生成随机数字

1, 4, 6, 2, 8, 9, 4, 3
5, 8, 1, 1, 5, 7, 5, 1
....

然后从99999999中减去公式。

修改

为避免碰撞,您可以进行左连接:

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:

我已创建表格,其中已经随机插入了这些代码:

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

此步骤大约需要10分钟。然后只需使用带语句的更新语句:

;WITH    cte
          AS ( SELECT TOP 1000
                        *
               FROM     dbo.Codes
               WHERE    IsUsed = 0
               ORDER BY id
             )
    UPDATE  cte
    SET     IsUsed = 1
    OUTPUT  Inserted.Code

它更新位并返回更新的代码。它使用索引查找,因此超快,并在1秒内更新并返回100.000行。