Подтвердить что ты не робот

Как реализовать последовательности в Microsoft SQL Server?

У кого-нибудь есть хороший способ реализовать что-то вроде последовательности на SQL-сервере?

Иногда вы просто не хотите использовать GUID, кроме того, что они уродливы, как черт. Может быть, последовательность, которую вы хотите, не является числовой? Кроме того, вставляя строку, а затем спрашивая БД, что число просто кажется таким хакерским.

4b9b3361

Ответ 1

Sql Server 2012 представил SEQUENCE объекты, которые позволяют генерировать последовательные числовые значения, не связанные с какой-либо таблицей.

Создание их легко:

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

См. мой блог для углубленного изучения того, как использовать последовательности:

http://sqljunkieshare.com/2011/12/11/sequences-in-sql-server-2012-implementingmanaging-performance/

Ответ 2

Как sqljunkieshare правильно сказано, начиная с SQL Server 2012 существует встроенный SEQUENCE.

Оригинальный вопрос не уточняет, но я предполагаю, что требования к последовательности следующие:

  • Он должен предоставить набор уникальных растущих чисел
  • Если несколько пользователей запрашивают следующее значение последовательности одновременно, все они должны получать разные значения. Другими словами, уникальность сгенерированных значений гарантируется независимо от того, что.
  • Из-за возможности откат некоторых транзакций возможно, что конечный результат сгенерированных номеров будет иметь пробелы.

Я хотел бы прокомментировать выражение в исходном вопросе:

"Кроме того, вставляя строку, а затем спрашивая БД, какой номер просто кажется настолько хаки".

Ну, здесь мы мало что можем сделать. БД является поставщиком последовательных номеров, а БД обрабатывает все эти проблемы concurrency, с которыми вы не справитесь. Я не вижу альтернативы просить БД для следующего значения последовательности. Должна быть атомная операция "дать мне следующее значение последовательности", и только такая БД может обеспечить такую ​​атомную операцию. Клиентский код не может гарантировать, что он единственный, кто работает с этой последовательностью.

Чтобы ответить на вопрос в заголовке "Как бы вы реализовали последовательности" - мы используем 2008, который не имеет функции SEQUENCE, поэтому после некоторого чтения в этом разделе я получил следующее.

Для каждой необходимой последовательности я создаю отдельную вспомогательную таблицу только с одним столбцом IDENTITY (таким же образом, как и в 2012 году, вы создадите отдельный объект Sequence).

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

Несколько заметок о процедуре.

Во-первых, было не очевидно, как вставить строку в таблицу, которая имеет только один столбец идентификатора. Ответ: DEFAULT VALUES.

Затем я хотел, чтобы процедура работала правильно, если она была вызвана внутри другой транзакции. Простой ROLLBACK откатывает все, если есть вложенные транзакции. В моем случае мне нужно откат только INSERT в вспомогательную таблицу, поэтому я использовал SAVE TRANSACTION.

ROLLBACK TRANSACTION без имени savepoint или имени транзакции возвращается к началу транзакции. Когда гнездование транзакций, этот же оператор возвращает все внутренние транзакции в самый внешний оператор BEGIN TRANSACTION.

Вот как я использую эту процедуру (внутри некоторой другой большой процедуры, которая, например, создает новый контракт):

DECLARE @VarContractNumber int;
EXEC @VarContractNumber = dbo.GetNewContractNumber;

Все работает отлично, если вам нужно генерировать значения последовательности по одному. В случае контрактов каждый контракт создается индивидуально, поэтому этот подход работает отлично. Я могу быть уверен, что все контракты всегда имеют уникальные номера контрактов.

NB: просто чтобы не допустить возможных вопросов. Эти номера контрактов в дополнение к суррогатной идентификационной информации, которую имеет таблица моих контрактов. Суррогатный ключ - это внутренний ключ, который используется для ссылочной целостности. Срочный номер контракта - это номер для человека, который указан в контракте. Кроме того, в той же таблице контрактов содержатся как окончательные контракты, так и предложения, которые могут стать контрактами или могут оставаться в качестве предложений навсегда. Оба предложения и контракты содержат очень похожие данные, поэтому они хранятся в одной таблице. Предложение может стать контрактом, просто изменив флаг в одной строке. Предложения нумеруются с использованием отдельной последовательности чисел, для которой у меня есть вторая таблица SequenceProposalNumber и вторая процедура GetNewProposalNumber.


В последнее время я столкнулся с проблемой. Мне нужно было генерировать значения последовательности в пакете, а не один за другим.

Мне нужна процедура, которая обрабатывала бы все платежи, которые были получены в течение данного квартала за один раз. Результатом такой обработки может быть ~ 20 000 транзакций, которые я хочу записать в таблице Transactions. У меня такой же дизайн здесь. В таблице Transactions есть внутренний столбец IDENTITY, который пользователь никогда не видит и имеет удобный для транзакции номер транзакции, который будет напечатан в инструкции. Итак, мне нужен способ генерации заданного количества уникальных значений в пакете.

По сути, я использовал один и тот же подход, но есть несколько особенностей.

Во-первых, нет прямого способа вставить несколько строк в таблицу только с одним столбцом IDENTITY. Хотя существует способ обхода (ab) с помощью MERGE, я не использовал его в конце. Я решил, что добавить фиктивный столбец Filler было проще. Таблица My Sequence будет всегда пустой, поэтому дополнительный столбец не имеет значения.

Таблица помощников выглядит так:

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;

Здесь есть несколько вещей, требующих объяснения.

Мне нужно вставить заданное количество строк в таблицу SequenceS2TransactionNumber. Для этого я использую таблицу-помощник Numbers. Эта таблица просто содержит целые числа от 1 до 100 000. Он также используется в других местах в системе. Я проверяю, достаточно ли строк в таблице Numbers и расширяет его до 100 000 * 100 000 путем перекрестного соединения с самим собой, если это необходимо.

Мне нужно сохранить результат массовой вставки и передать его вызывающему. Один из способов передачи таблицы за пределами хранимой процедуры - использовать временную таблицу. Я не могу использовать параметр table-valued здесь, потому что он доступен только для чтения. Кроме того, я не могу напрямую вставить созданные значения последовательности во временную таблицу #NewS2TransactionNumbers. Я не могу использовать #NewS2TransactionNumbers в предложении OUTPUT, потому что ROLLBACK очистит его. К счастью, переменные таблицы не влияют на ROLLBACK.

Итак, я использую переменную таблицы @TableTransactionNumbers как пункт назначения OUTPUT. Затем я ROLLBACK транзакция для очистки таблицы Sequence. Затем скопируйте сгенерированные значения последовательности из переменной таблицы @TableTransactionNumbers во временную таблицу #NewS2TransactionNumbers, так как для вызывающей стороны хранимой процедуры может быть видна только временная таблица #NewS2TransactionNumbers. Переменная таблицы @TableTransactionNumbers не отображается вызывающей стороне хранимой процедуры.

Кроме того, можно использовать предложение OUTPUT для отправки сгенерированной последовательности непосредственно вызывающему абоненту (как вы можете видеть в комментарии, который использует MERGE). Он отлично работает сам по себе, но мне нужны сгенерированные значения в некоторой таблице для дальнейшей обработки в вызывающей хранимой процедуре. Когда я попробовал что-то вроде этого:

INSERT INTO @TableTransactions (ID)
EXEC dbo.GetNewS2TransactionNumbers @ParamCount = @VarTransactionCount;

Я получаю сообщение об ошибке

Невозможно использовать оператор ROLLBACK в инструкции INSERT-EXEC.

Но мне нужно ROLLBACK внутри EXEC, поэтому у меня получилось так много временных таблиц.

В конце концов, как бы неплохо было перейти на последнюю версию SQL-сервера, у которой есть правильный SEQUENCE объект.

Ответ 4

Вы можете просто использовать простые старые таблицы и использовать их как последовательности. Это означает, что ваши вставки всегда будут:

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 "sequence" выглядела как взлома. Я предполагаю, что вы исходите из другого направления и к вам, а scope_identity() выглядит как хак.

Поверните. Когда в Риме делайте, как делают римляне.

Ответ 5

То, что я использовал для решения этой проблемы, - это таблица "Последовательности", в которой хранятся все мои последовательности и хранимая процедура "nextval".

Таблица Sql:

CREATE TABLE Sequences (  
    name VARCHAR(30) NOT NULL,  
    value BIGINT DEFAULT 0 NOT NULL,  
    CONSTRAINT PK_Sequences PRIMARY KEY (name)  
);

PK_Sequences используется, чтобы быть уверенным, что никогда не будет последовательностей с тем же именем.

Сохраненная процедура 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 [email protected]  
        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';

Некоторый код С#, чтобы получить следующее значение из таблицы Sequence,

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

Ответ 6

Последовательности, реализованные Oracle, требуют вызова базы данных перед вставкой. идентификаторы, реализованные SQL Server, требуют вызова базы данных после вставки.

Один не более хакерский, чем другой. Сетевой эффект один и тот же - зависимость/зависимость от хранилища данных для предоставления уникальных значений искусственных ключей и (в большинстве случаев) двух вызовов в хранилище.

Я предполагаю, что ваша реляционная модель основана на искусственных ключах, и в этом контексте я предложу следующее наблюдение:

Мы никогда не должны пытаться наполнить искусственные ключи смыслом; их единственная цель - связать связанные записи.

Каковы ваши потребности в заказе данных? может ли он обрабатываться в представлении (представлении) или это истинный атрибут ваших данных, который должен сохраняться?

Ответ 7

В SQL Server 2012 вы можете просто использовать

CREATE SEQUENCE

В 2005 и 2008 годах вы можете получить произвольный список последовательных чисел, используя общее табличное выражение.

Вот пример (обратите внимание, что параметр MAXRECURSION важен):

DECLARE @MinValue INT = 1;
DECLARE @MaxValue INT = 1000;

WITH IndexMaker (IndexNumber) AS
(
    SELECT 
        @MinValue AS IndexNumber
    UNION ALL SELECT 
        IndexNumber + 1
    FROM
        IndexMaker
    WHERE IndexNumber < @MaxValue
)
SELECT
    IndexNumber
FROM
    IndexMaker
ORDER BY
    IndexNumber
OPTION 
    (MAXRECURSION 0)

Ответ 8

Создайте таблицу сцен с идентификатором на ней.

Перед загрузкой таблицы сцен обрезайте и повторно введите идентификатор, начинающийся с 1.

Загрузите таблицу. Каждая строка теперь имеет уникальное значение от 1 до N.

Создайте таблицу, содержащую порядковые номера. Это может быть несколько строк, по одному для каждой последовательности.

Найдите номер последовательности из созданной последовательности. Обновите номер последовательности, добавив количество строк в таблице этапа к порядковому номеру.

Обновите идентификатор таблицы сцен, добавив номер последовательности, который вы просмотрели. Это простой одноэтапный процесс. или Загрузите целевую таблицу, добавьте порядковый номер в идентификатор при загрузке в ETL. Это может использовать преимущества загрузчика и допускать другие преобразования.

Ответ 9

Рассмотрим следующий фрагмент.

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

Ответ 10

Как sqljunkiesshare states, последовательности были добавлены в SQL Server 2012. Вот как это сделать в графическом интерфейсе. Это равнозначно:

CREATE SEQUENCE Schema.SequenceName
AS int
INCREMENT BY 1 ;
  • В Проводнике объектов разверните папку Programmability
  • В папке Programmability щелкните правой кнопкой мыши по последовательностям как показано ниже:

введите описание изображения здесь

  1. Подчеркнуты значения, которые вы обновили, чтобы получить эквивалент вышеприведенного оператора SQL, однако я бы рассмотрел изменяя их в зависимости от ваших потребностей (см. примечания ниже).

введите описание изображения здесь

Примечания:

Ответ 11

Я полностью согласен и сделал это в прошлом году по проекту.

Я только что создал таблицу с именем последовательности, текущего значения и суммы приращения.

Затем я создал 2 procs для их добавления и удаления. И 2 функции для получения следующего и получения тока.

Ответ 12

Если вы хотите вставить данные с помощью последовательного ключа, но вы не хотите снова запрашивать базу данных, чтобы получить только что вставленный ключ, я думаю, что ваши два варианта:

  • Выполните вставку с помощью хранимой процедуры, которая возвращает вновь вставленное значение ключа
  • Внедрить последовательность на стороне клиента (чтобы вы знали новый ключ перед вставкой)

Если я выполняю генерацию ключей на стороне клиента, я люблю GUID. Я думаю, что они красивы, как черт.

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

Эта линия должна укладываться на капот спортивного автомобиля где-то.

Ответ 13

Если вы используете SQL Server 2005, у вас есть возможность использовать Row_Number

Ответ 14

Другая проблема с столбцами идентификаторов заключается в том, что если у вас более одной таблицы, где порядковые номера должны быть уникальными, столбец идентификаторов не работает. И, как упоминает Кори Трейгер, реализация последовательности ваших действий в рулонах может вызвать некоторые проблемы с блокировкой.

Наиболее справедливо эквивалентные решения, похоже, состоят в том, чтобы создать таблицу SQL Server с одним столбцом для идентификатора, который заменяет отдельный тип объекта "sequence". Например, если в Oracle у вас будет две таблицы из одной последовательности, такие как Dogs < - sequence object → Cats, тогда в SQL Server вы создадите три объекта базы данных, все таблицы, такие как Dogs < - Pets с идентификационным столбцом, → Кошки. Вы вставляете строку в таблицу "Домашние животные", чтобы получить порядковый номер, где вы обычно используете NEXTVAL, а затем вставляете в таблицу "Собаки или кошки", как обычно, как только вы получите фактический тип домашнего животного от пользователя. Любые дополнительные общие столбцы можно перенести из таблиц Dogs/Cats в таблицу супертипов Pets, с некоторыми последствиями: 1) для каждой последовательности будет одна строка, 2) любые столбцы, которые не могут быть заполнены при получении номера последовательности, будут должны иметь значения по умолчанию и 3) для соединения всех столбцов потребуется объединение.

Ответ 15

По SQL вы можете использовать эту стратегию;

CREATE SEQUENCE [dbo].[SequenceFile]
AS int
START WITH 1
INCREMENT BY 1 ;

и прочитайте уникальное следующее значение с этим SQL

SELECT NEXT VALUE FOR [dbo].[SequenceFile]

Ответ 16

СДЕЛОК БЕЗОПАСНОСТИ! Для версий SQLServer до 2012 года... (спасибо Мэтту Г.). Одна из недостатков в этом обсуждении - безопасность транзакций. Если вы получаете номер из последовательности, этот номер должен быть уникальным, и никакое другое приложение или код не смогут получить этот номер. В моем случае мы часто извлекаем уникальные числа из последовательностей, но фактическая транзакция может занять значительное количество времени, поэтому мы не хотим, чтобы кто-то еще получал одинаковое число, прежде чем мы совершили транзакцию. Нам нужно было подражать поведению последовательностей оракулов, где число было зарезервировано, когда оно было вытащено. Мое решение - использовать xp_cmdshell для получения отдельного сеанса/транзакции в базе данных, чтобы мы могли немедленно обновить последовательность для всей базы данных даже до завершения транзакции.

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

Теперь, чтобы заставить эту процедуру работать, вам нужно будет включить xp_cmdshell, есть много хороших описаний, как это сделать, вот мои личные заметки, которые я сделал, когда пытались заставить работать. Основная идея заключается в том, что вам нужна xp_cmdshell, включенная в SQLServer Surface. Конфигурация, и вам нужно установить учетную запись пользователя в качестве учетной записи, которую будет выполнять команда xp_cmdshell, которая будет обращаться к базе данных, чтобы вставить порядковый номер и зафиксировать его.

--- 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"'