如何在Microsoft SQL Server中实现序列?

如何在Microsoft SQL Server中实现序列?,sql,sql-server,database,sequences,Sql,Sql Server,Database,Sequences,有没有人有一种在SQLServer中实现序列之类的东西的好方法 有时候你只是不想使用GUID,除了它们很难看之外。也许你想要的序列不是数字?此外,插入一行,然后询问数据库的数字是多少,这似乎太老套了。大致类似于序列。您可以使用普通的旧表,并将它们用作序列。这意味着您的插入始终是: BEGIN TRANSACTION SELECT number from plain old table.. UPDATE plain old table, set the number to be the n

有没有人有一种在SQLServer中实现序列之类的东西的好方法


有时候你只是不想使用GUID,除了它们很难看之外。也许你想要的序列不是数字?此外,插入一行,然后询问数据库的数字是多少,这似乎太老套了。

大致类似于序列。

您可以使用普通的旧表,并将它们用作序列。这意味着您的插入始终是:

BEGIN TRANSACTION  
SELECT number from plain old table..  
UPDATE plain old table, set the number to be the next number  
INSERT your row  
COMMIT  
但不要这样做。锁会坏的

我从SQL Server开始,对我来说,Oracle的“序列”方案看起来像是一个黑客。我猜你是从另一个方向来的,对你来说,scope_identity()看起来像一个黑客


忘掉它。入乡随俗

我完全同意,并在去年的一个项目中这样做了

我刚刚创建了一个表,其中包含序列名称、当前值和增量金额


然后我创建了两个程序来添加和删除它们。和两个函数来获取下一个键和当前键。

如果您想用顺序键插入数据,但不想再次查询数据库来获取刚刚插入的键,我认为您只有两个选择:

  • 通过返回新插入的键值的存储过程执行插入
  • 在客户端实现序列(以便在插入之前知道新密钥)
  • 如果我在做客户端密钥生成,我喜欢guid。我觉得它们非常漂亮

    row["ID"] = Guid.NewGuid();
    

    这条线应该放在跑车的引擎盖上。

    Oracle实现的序列需要在插入之前调用数据库。 SQL Server实现的标识需要在插入后调用数据库

    一个并不比另一个更老练。净效果是一样的——依赖/依赖数据存储来提供唯一的人工键值和(在大多数情况下)对存储的两个调用

    我假设您的关系模型基于人工键,在本文中,我将提供以下观察结果:

    我们决不应该试图赋予人工钥匙以意义;它们的唯一目的应该是链接相关记录


    与订购数据相关的需求是什么?它可以在视图(表示)中处理,还是必须持久化的数据的真实属性

    如果您使用的是SQL Server 2005,您可以选择使用行号

    创建一个带有标识符的阶段表

    在加载stage表之前,截断并重新设置标识符的种子,使其从1开始

    装上你的桌子。现在,每行都有一个从1到N的唯一值

    创建一个包含序列号的表。这可以是几行,每个序列一行

    从创建的序列表中查找序列号。 通过将阶段表中的行数添加到序列号来更新序列号

    通过添加查找到的序列号来更新阶段表标识符。这是一个简单的一步过程。 或
    加载目标表,在ETL中加载时将序列号添加到标识符中。这可以利用批量加载程序并允许进行其他转换。

    标识列的另一个问题是,如果有多个表的序列号需要唯一,则标识列不起作用。正如Corey Trager提到的,您自己类型的序列实现可能会出现一些锁定问题


    最直截了当的等效解决方案似乎是创建一个SQL Server表,其中包含一列标识,它取代了单独类型的“序列”对象。例如,如果在Oracle中,一个序列中有两个表,例如Dogs-Cats,那么在SQL Server中,您将创建三个数据库对象,所有表都像Dogs-Cats。您可以在Pets表中插入一行,以获取通常使用NEXTVAL的序列号,然后像从用户处获取实际类型的宠物一样,插入Dogs或Cats表。任何额外的公共列都可以从Dogs/Cats表移动到Pets超类型表中,其结果是1)每个序列号都有一行,2)获取序列号时无法填充的任何列都需要具有默认值,3)需要联接才能获取所有列。

    Sql Server 2012已引入,允许您生成与任何表都不关联的顺序数值

    创建它们很容易:

    CREATE SEQUENCE Schema.SequenceName
    AS int
    INCREMENT BY 1 ;
    
    插入前使用它们的示例:

    DECLARE @NextID int ;
    SET @NextID = NEXT VALUE FOR Schema.SequenceName;
    -- Some work happens
    INSERT Schema.Orders (OrderID, Name, Qty)
      VALUES (@NextID, 'Rim', 2) ;
    
    请参阅我的博客,深入了解如何使用序列:


    考虑以下代码片段

    CREATE TABLE [SEQUENCE](
        [NAME] [varchar](100) NOT NULL,
        [NEXT_AVAILABLE_ID] [int] NOT NULL,
     CONSTRAINT [PK_SEQUENCES] PRIMARY KEY CLUSTERED 
    (
        [NAME] ASC
    )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
    ) ON [PRIMARY]
    GO
    
    CREATE PROCEDURE CLAIM_IDS (@sequenceName varchar(100), @howMany int)
    AS
    BEGIN
        DECLARE @result int
        update SEQUENCE
            set
                @result = NEXT_AVAILABLE_ID,
                NEXT_AVAILABLE_ID = NEXT_AVAILABLE_ID + @howMany
            where Name = @sequenceName
        Select @result as AVAILABLE_ID
    END
    GO
    

    在SQL Server 2012中,您只需使用

    CREATE SEQUENCE
    
    在2005年和2008年,您可以使用公共表表达式获得任意序列号列表

    下面是一个示例(请注意,MAXRECURSION选项很重要):

    DECLARE@MinValue INT=1;
    声明@MaxValue INT=1000;
    使用IndexMaker(IndexNumber)作为
    (
    挑选
    @作为索引编号的最小值
    联合所有选择
    指数编号+1
    从…起
    索引生成器
    其中IndexNumber<@MaxValue
    )
    挑选
    指数
    从…起
    索引生成器
    订购人
    指数
    选项
    (最大递归0)
    
    我用来解决这个问题的方法是一个表'Sequences',它存储了我所有的序列和一个'nextval'存储过程

    Sql表:

    CREATE TABLE Sequences (  
        name VARCHAR(30) NOT NULL,  
        value BIGINT DEFAULT 0 NOT NULL,  
        CONSTRAINT PK_Sequences PRIMARY KEY (name)  
    );
    
    使用PK_序列只是为了确保永远不会有同名的序列

    Sql存储过程:

    IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'nextVal') AND type in (N'P', N'PC')) DROP PROCEDURE nextVal;  
    GO  
    CREATE PROCEDURE nextval  
        @name VARCHAR(30)  
    AS  
        BEGIN  
            DECLARE @value BIGINT  
            BEGIN TRANSACTION  
                UPDATE Sequences  
                SET @value=value=value + 1  
                WHERE name = @name;  
                -- SELECT @value=value FROM Sequences WHERE name=@name  
            COMMIT TRANSACTION  
            SELECT @value AS nextval  
        END;  
    
    插入一些序列:

    INSERT INTO Sequences(name, value) VALUES ('SEQ_Workshop', 0);
    INSERT INTO Sequences(name, value) VALUES ('SEQ_Participant', 0);
    INSERT INTO Sequences(name, value) VALUES ('SEQ_Invoice', 0);  
    
    最后得到序列的下一个值

    execute nextval 'SEQ_Participant';
    
    一些c#代码从序列表中获取下一个值

    public long getNextVal()
    {
        long nextval = -1;
        SqlConnection connection = new SqlConnection("your connection string");
        try
        {
            //Connect and execute the select sql command.
            connection.Open();
    
            SqlCommand command = new SqlCommand("nextval", connection);
            command.CommandType = CommandType.StoredProcedure;
            command.Parameters.Add("@name", SqlDbType.NVarChar).Value = "SEQ_Participant";
            nextval = Int64.Parse(command.ExecuteScalar().ToString());
    
            command.Dispose();
        }
        catch (Exception) { }
        finally
        {
            connection.Dispose();
        }
        return nextval;
    }
    
    正如正确所说,从SQLServer2012开始,有一个内置功能

    原来的问题并不重要
    CREATE TABLE [dbo].[SequenceContractNumber]
    (
        [ContractNumber] [int] IDENTITY(1,1) NOT NULL,
    
        CONSTRAINT [PK_SequenceContractNumber] PRIMARY KEY CLUSTERED ([ContractNumber] ASC)
    )
    
    CREATE PROCEDURE [dbo].[GetNewContractNumber]
    AS
    BEGIN
        -- SET NOCOUNT ON added to prevent extra result sets from
        -- interfering with SELECT statements.
        SET NOCOUNT ON;
        SET XACT_ABORT ON;
    
        DECLARE @Result int = 0;
    
        IF @@TRANCOUNT > 0
        BEGIN
            -- Procedure is called when there is an active transaction.
            -- Create a named savepoint
            -- to be able to roll back only the work done in the procedure.
            SAVE TRANSACTION ProcedureGetNewContractNumber;
        END ELSE BEGIN
            -- Procedure must start its own transaction.
            BEGIN TRANSACTION ProcedureGetNewContractNumber;
        END;
    
        INSERT INTO dbo.SequenceContractNumber DEFAULT VALUES;
    
        SET @Result = SCOPE_IDENTITY();
    
        -- Rollback to a named savepoint or named transaction
        ROLLBACK TRANSACTION ProcedureGetNewContractNumber;
    
        RETURN @Result;
    END
    
    DECLARE @VarContractNumber int;
    EXEC @VarContractNumber = dbo.GetNewContractNumber;
    
    CREATE TABLE [dbo].[SequenceS2TransactionNumber]
    (
        [S2TransactionNumber] [int] IDENTITY(1,1) NOT NULL,
        [Filler] [int] NULL,
        CONSTRAINT [PK_SequenceS2TransactionNumber] 
        PRIMARY KEY CLUSTERED ([S2TransactionNumber] ASC)
    )
    
    -- Description: Returns a list of new unique S2 Transaction numbers of the given size
    -- The caller should create a temp table #NewS2TransactionNumbers,
    -- which would hold the result
    CREATE PROCEDURE [dbo].[GetNewS2TransactionNumbers]
        @ParamCount int -- not NULL
    AS
    BEGIN
        -- SET NOCOUNT ON added to prevent extra result sets from
        -- interfering with SELECT statements.
        SET NOCOUNT ON;
        SET XACT_ABORT ON;
    
        IF @@TRANCOUNT > 0
        BEGIN
            -- Procedure is called when there is an active transaction.
            -- Create a named savepoint
            -- to be able to roll back only the work done in the procedure.
            SAVE TRANSACTION ProcedureGetNewS2TransactionNos;
        END ELSE BEGIN
            -- Procedure must start its own transaction.
            BEGIN TRANSACTION ProcedureGetNewS2TransactionNos;
        END;
    
        DECLARE @VarNumberCount int;
        SET @VarNumberCount = 
        (
            SELECT TOP(1) dbo.Numbers.Number
            FROM dbo.Numbers
            ORDER BY dbo.Numbers.Number DESC
        );
    
        -- table variable is not affected by the ROLLBACK, so use it for temporary storage
        DECLARE @TableTransactionNumbers table
        (
            ID int NOT NULL
        );
    
        IF @VarNumberCount >= @ParamCount
        BEGIN
            -- the Numbers table is large enough to provide the given number of rows
            INSERT INTO dbo.SequenceS2TransactionNumber
            (Filler)
            OUTPUT inserted.S2TransactionNumber AS ID INTO @TableTransactionNumbers(ID)
            -- save generated unique numbers into a table variable first
            SELECT TOP(@ParamCount) dbo.Numbers.Number
            FROM dbo.Numbers
            OPTION (MAXDOP 1);
    
        END ELSE BEGIN
            -- the Numbers table is not large enough to provide the given number of rows
            -- expand the Numbers table by cross joining it with itself
            INSERT INTO dbo.SequenceS2TransactionNumber
            (Filler)
            OUTPUT inserted.S2TransactionNumber AS ID INTO @TableTransactionNumbers(ID)
            -- save generated unique numbers into a table variable first
            SELECT TOP(@ParamCount) n1.Number
            FROM dbo.Numbers AS n1 CROSS JOIN dbo.Numbers AS n2
            OPTION (MAXDOP 1);
    
        END;
    
        /*
        -- this method can be used if the SequenceS2TransactionNumber
        -- had only one identity column
        MERGE INTO dbo.SequenceS2TransactionNumber
        USING
        (
            SELECT *
            FROM dbo.Numbers
            WHERE dbo.Numbers.Number <= @ParamCount
        ) AS T
        ON 1 = 0
        WHEN NOT MATCHED THEN
        INSERT DEFAULT VALUES
        OUTPUT inserted.S2TransactionNumber
        -- return generated unique numbers directly to the caller
        ;
        */
    
        -- Rollback to a named savepoint or named transaction
        ROLLBACK TRANSACTION ProcedureGetNewS2TransactionNos;
    
        IF object_id('tempdb..#NewS2TransactionNumbers') IS NOT NULL
        BEGIN
            INSERT INTO #NewS2TransactionNumbers (ID)
            SELECT TT.ID FROM @TableTransactionNumbers AS TT;
        END
    
    END
    
    -- Generate a batch of new unique transaction numbers
    -- and store them in #NewS2TransactionNumbers
    DECLARE @VarTransactionCount int;
    SET @VarTransactionCount = ...
    
    CREATE TABLE #NewS2TransactionNumbers(ID int NOT NULL);
    
    EXEC dbo.GetNewS2TransactionNumbers @ParamCount = @VarTransactionCount;
    
    -- use the generated numbers...
    SELECT ID FROM #NewS2TransactionNumbers AS TT;
    
    INSERT INTO @TableTransactions (ID)
    EXEC dbo.GetNewS2TransactionNumbers @ParamCount = @VarTransactionCount;
    
    CREATE SEQUENCE Schema.SequenceName
    AS int
    INCREMENT BY 1 ;
    
    CREATE SEQUENCE [dbo].[SequenceFile]
    AS int
    START WITH 1
    INCREMENT BY 1 ;
    
    SELECT NEXT VALUE FOR [dbo].[SequenceFile]
    
    --it is used like this:
    -- use the sequence in either insert or select:
    Insert into MyTable Values (NextVal('MySequence'), 'Foo');
    
    SELECT NextVal('MySequence');
    
    --you can make as many sequences as you want, by name:
    SELECT NextVal('Mikes Other Sequence');
    
    --or a blank sequence identifier
    SELECT NextVal('');
    
      CREATE TABLE SequenceHolder(SeqName varchar(40), LastVal int);
    
    GO
    CREATE function NextVAL(@SEQname varchar(40))
    returns int
    as
    begin
        declare @lastval int
        declare @barcode int;
    
        set @lastval = (SELECT max(LastVal) 
                          FROM SequenceHolder
                         WHERE SeqName = @SEQname);
    
        if @lastval is null set @lastval = 0
    
        set @barcode = @lastval + 1;
    
        --=========== USE xp_cmdshell TO INSERT AND COMMINT NOW, IN A SEPERATE TRANSACTION =============================
        DECLARE @sql varchar(4000)
        DECLARE @cmd varchar(4000)
        DECLARE @recorded int;
    
        SET @sql = 'INSERT INTO SequenceHolder(SeqName, LastVal) VALUES (''' + @SEQname + ''', ' + CAST(@barcode AS nvarchar(50)) + ') '
        SET @cmd = 'SQLCMD -S ' + @@servername +
                  ' -d ' + db_name() + ' -Q "' + @sql + '"'
        EXEC master..xp_cmdshell @cmd, 'no_output'
    
        --===============================================================================================================
    
        -- once submitted, make sure our value actually stuck in the table
        set @recorded = (SELECT COUNT(*) 
                           FROM SequenceHolder
                          WHERE SeqName = @SEQname
                            AND LastVal = @barcode);
    
        --TRIGGER AN ERROR 
        IF (@recorded != 1)
            return cast('Barcode was not recorded in SequenceHolder, xp_cmdshell FAILED!! [' + @cmd +']' as int);
    
        return (@barcode)
    
    end
    
    GO
    
    COMMIT;
    
    --- LOOSEN SECURITY SO THAT xp_cmdshell will run 
    ---- To allow advanced options to be changed.
    EXEC sp_configure 'show advanced options', 1
    GO
    ---- To update the currently configured value for advanced options.
    RECONFIGURE
    GO
    ---- To enable the feature.
    EXEC sp_configure 'xp_cmdshell', 1
    GO
    ---- To update the currently configured value for this feature.
    RECONFIGURE
    GO
    
    —-Run SQLServer Management Studio as Administrator,
    —- Login as domain user, not sqlserver user.
    
    --MAKE A DATABASE USER THAT HAS LOCAL or domain LOGIN! (not SQL server login)
    --insure the account HAS PERMISSION TO ACCESS THE DATABASE IN QUESTION.  (UserMapping tab in User Properties in SQLServer)
    
    —grant the following
    GRANT EXECUTE on xp_cmdshell TO [domain\user] 
    
    —- run the following:
    EXEC sp_xp_cmdshell_proxy_account 'domain\user', 'pwd'
    
    --alternative to the exec cmd above: 
    create credential ##xp_cmdshell_proxy_account## with identity = 'domain\user', secret = 'pwd'
    
    
    -—IF YOU NEED TO REMOVE THE CREDENTIAL USE THIS
    EXEC sp_xp_cmdshell_proxy_account NULL;
    
    
    -—ways to figure out which user is actually running the xp_cmdshell command.
    exec xp_cmdshell 'whoami.exe'  
    EXEC xp_cmdshell 'osql -E -Q"select suser_sname()"'
    EXEC xp_cmdshell 'osql -E -Q"select * from sys.login_token"'