SQL:如何在UDF中获取没有冲突的唯一随机数

时间:2017-11-10 13:51:30

标签: sql sql-server

我正在尝试找到一种在SQL中计算随机和唯一键的明确方法(使用SQL 2012)。

我有一个像这样定义的表:

CREATE TABLE Variables
(
   ...
   KeyId bigint NOT NULL DEFAULT(dbo.GetKeyId())
)

UDF定义如下:

CREATE FUNCTION [dbo].[GetKeyId]()
RETURNS bigint
AS
BEGIN
    DECLARE @maxAttempts INT = 100
    DECLARE @MaxID bigint
    DECLARE @NewKeyId bigint

    SET @MaxID = POWER(36.0, 12)

    ;WITH CTE AS
    (
        SELECT FLOOR((SELECT rndvalue FROM rnd)*@MaxID) AS rn, 1 AS i

        UNION ALL

        SELECT FLOOR((SELECT rndvalue FROM rnd)*@MaxID) AS rn, i = i + 1
        FROM CTE AS c
            INNER JOIN Variables AS r ON c.rn = r.KeyId
        WHERE (i < @maxAttempts)
           AND NOT EXISTS(SELECT * FROM Variables WHERE KeyId=c.rn)
    )
    SELECT TOP 1 @NewKeyId = rn
    FROM CTE
    ORDER BY i DESC

    RETURN @NewKeyId
END

当然还有以下观点

CREATE VIEW [dbo].[rnd]
AS
    SELECT RAND() AS rndvalue

在小规模测试此解决方案后,我发现它不起作用。有时它会返回已存在的键值。使用UDF给我带来了许多限制的麻烦。 任何人都可以推荐一个有效的版本吗?

3 个答案:

答案 0 :(得分:1)

经典方法之一:

  • 填写包含大量随机值的表/数组
  • 使用identity / index
  • 对其中的行进行编号
  • 使用独立的具有增量值的行来引用它的行

e.g。

CREATE TABLE BulkOfRealRandoms (id int identity(1,1), rand_value BINARY(8)

填写像md5的db这样的东西。我的意思是这样做是一次性的工作。在OLTP场景中,在内部事务中不要做这样的事情。正如许多其他人所说 - 你的udf是性能杀手。

CREATE TABLE RandomReceiver (self_id int, rand_value_id int)

您可以使用IDENTITY或SEQUENCE生成rand_value_id。这就是你如何快速,自信地获得随机值而没有共谋

如果你试图通过分配随机公共id而不是顺序公钥来隐藏数据库中某些表的增长,那么你应该发明一个algorythm来生成这样的id,这可能是字符串数据类型。定性随机化不是一项微不足道的任务。如果这是您的情况(生成公共订单ID或类似信息),那么随机数字将变得非常舒适和有用。

答案 1 :(得分:0)

我会重新使用IDENTITYUNIQUEIDENTIFIER,但如果您坚持这样做,请尝试以下操作:

1-创建你的功能:

CREATE FUNCTION GetKeyId ()
RETURNS bigint
AS
BEGIN
    DECLARE @Value BIGINT;
    ExixtsValue:
    SET @Value = (CONVERT(BigInt, CRYPT_GEN_RANDOM(3)) % 1000000);
    IF EXISTS (SELECT 1 FROM MyVariables WHERE KeyId = @Value)
        GOTO ExixtsValue;
RETURN @Value
END

2-创建测试表:

CREATE TABLE MyVariables (
    KeyId bigint NOT NULL DEFAULT(dbo.GetKeyId())
);

3-根据需要测试它:

INSERT INTO MyVariables VALUES (dbo.GetKeyId() );

答案 2 :(得分:0)

如果可以在UDF中使用CRYPT_GEN_RANDOM,那么Sami的答案也不会太糟糕。

我的最终解决方案是不使用UDF作为KeyId的默认值。然后,在表中预先生成密钥,并在插入变量表时(在执行插入的SP中)提取KeyId值。

因此Variables表定义如下:

package cit260.harrypotter.view;
import java.util.Scanner;
public abstract class View implements ViewInterface {

    public View() {


        @Override
         public void display(){
            boolean endView = false;
            do {
                String[] inputs = this.getInputs(); 
                endView = doAction(inputs);
            } while (!endView);
        }

    @Override
        public String getInput(String promptMessage) {
            String input;
            boolean valid = false;
            while(!valid) {
                System.out.println(promptMessage);
                Scanner keyboard = new Scanner(System.in);
                input = keyboard.nextLine(); input.trim();
                if (input.length != 0){
                    System.out.println("Please enter a valid input");
                    valid = true;
                }
            }
            return input;
        }
    }
}

和PoolKeys表:

CREATE TABLE Variables
(
   ...
   KeyId bigint NOT NULL
)

我定义了这个视图来获得一个大的随机数(OK伪随机):

CREATE TABLE [dbo].[PoolKeys](
    [KeyId] [bigint] NOT NULL,
    [Rank] [int] NOT NULL
)

然后这个SP生成新密钥:

CREATE VIEW [dbo].[rndBig]
AS
    SELECT ABS(CAST(CRYPT_GEN_RANDOM(10) AS bigint)) AS rndvalue

和SP提取一个新的KeyId:

CREATE PROCEDURE [dbo].[GenerateNewKeys]
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @min int = 1
    DECLARE @max bigint = POWER(36.0, 12)-1     -- needs to be an odd value
    DECLARE @n INT = 1000

        ;WITH CTE AS (
        SELECT TOP(@n) CONVERT(bigint, FLOOR((SELECT rndvalue FROM dbo.rndBig) * CONVERT(float, (@max - @min + 1) / @max))) % @max + @min AS KeyId,
            ROW_NUMBER() OVER (ORDER BY s1.[object_id]) AS rnk
        FROM sys.all_objects as s1 
            CROSS JOIN sys.all_objects as s2
    )
    INSERT INTO PoolKeys(KeyId, [Rank])
    SELECT DISTINCT TOP (@n) s.KeyId, s.rnk
    FROM CTE AS s
    WHERE NOT EXISTS (SELECT * FROM Variables V WHERE V.KeyId = s.KeyId)
END

有了这一切,在执行INSERT INTO变量的SP中,我调用GetNewKey来获取我插入变量的下一个KeyId。 因此,每1000次调用一次,将花费几毫秒来生成另外1000个密钥。这在我的案例中是可以接受的。