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

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

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


person Nathan Lee    schedule 12.11.2008    source источник
comment
Кажется, это напрямую отвечает на ваш вопрос: sqlteam.com/ article / (если нет - я, должно быть, не понимаю некоторых тонкостей вашего варианта использования. Прокомментируйте, пожалуйста)   -  person SquareCog    schedule 12.11.2008


Ответы (16)


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/

person sqljunkieshare    schedule 11.12.2011
comment
@eidylon, в предыдущих версиях SQL Server можно было эмулировать объект sequence, используя выделенную таблицу со столбцом identity, как я объяснил в своем ответе. Однако в сложных случаях, когда вам нужно сгенерировать более одного значения, это становится довольно некрасивым. - person Vladimir Baranov; 02.09.2015

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

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

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

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

«Кроме того, вставка строки и последующий запрос у БД, какое число кажется таким хакерским».

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

Чтобы ответить на вопрос в заголовке «Как бы вы реализовали последовательности?» - мы используем 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 без имени точки сохранения или имени транзакции выполняет откат к началу транзакции. При вложении транзакций этот же оператор откатывает все внутренние транзакции до самого внешнего оператора BEGIN TRANSACTION.

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

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

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

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


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

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

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

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

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

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 путем перекрестного соединения с самим собой.

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

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

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

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

Я получал ошибку

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

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

После всего этого, как хорошо было бы переключиться на последнюю версию SQL-сервера, у которой есть подходящий объект SEQUENCE.

person Vladimir Baranov    schedule 10.12.2014
comment
Здесь - еще одна похожая статья, надеюсь, кому-то поможет. - person Shaiju T; 23.08.2016
comment
Вы создали кластерный первичный ключ для более быстрого запроса и что такое ASC и PK_SequenceContractNumber в CONSTRAINT [PK_SequenceContractNumber] PRIMARY KEY CLUSTERED ([ContractNumber] ASC)? - person Shaiju T; 23.08.2016
comment
@stom таблица SequenceContractNumber имеет кластерный первичный ключ. PK_SequenceContractNumber - это имя ограничения первичного ключа и соответствующий индекс, который создается для реализации ограничения. ASC означает ContractNumber в возрастающем порядке сортировки в индексе. Суть метода, который я здесь описал, заключается в том, что вспомогательная таблица со столбцом IDENTITY всегда остается пустой. Вставляется строка, сгенерированный ID записывается, и транзакция откатывается. - person Vladimir Baranov; 24.08.2016

Столбец Identity примерно аналогичен последовательности.

person matt b    schedule 12.11.2008
comment
Примерно да, но не совсем. Вам нужно что-то вставить в таблицу, прежде чем вы сможете быть уверены в идентификаторе. - person Nathan Lee; 12.11.2008
comment
наверное, надо было сказать очень и очень грубо :) - person matt b; 12.11.2008
comment
И вы также можете получить пропуски в вашей последовательности, если откатите вставку. - person Jonas Lincoln; 17.11.2008
comment
@JonasLincoln В ПОСЛЕДОВАТЕЛЬНОСТИ тоже могут быть пробелы. По сути, нет никакого способа гарантировать, что у вас никогда не будет пробелов. - person Bacon Bits; 25.01.2018
comment
Столбец идентификаторов работает очень хорошо, особенно в сочетании с предложением OUTPUT для возврата созданного значения идентификатора. - person Bacon Bits; 25.01.2018

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

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 () выглядит как взлом.

Преодолей это. Находясь в Риме, делайте то же, что и римляне.

person Corey Trager    schedule 12.11.2008
comment
вам также нужно будет использовать SET TRANSACTION ISOLATION LEVEL SERIALIZABLE, чтобы гарантировать, что он работает, но, как и @Corey Trager, я также не рекомендую вам реализовывать это. - person Mitch Wheat; 12.11.2008
comment
Что кажется хакерским в последовательностях? Просто любопытно, я сначала научился использовать последовательности. Как вы упомянули, все, что мы узнаем в первую очередь, кажется правильным. - person Steve K; 12.11.2008
comment
В SQL Server я просто указываю столбец как IDENTITY, и все готово. В Oracle мне нужно было сделать больше (извините, я забыл подробности. Это было 8 лет назад ...). - person Corey Trager; 12.11.2008
comment
Ах, я понимаю, что вы говорите. Для Oracle это правильно, и это больно. В Postgres вы можете установить значение столбца по умолчанию в качестве следующего значения последовательности, и это очень удобно. - person Steve K; 12.11.2008

Для решения этой проблемы я использовал таблицу «Последовательности», в которой хранятся все мои последовательности, и хранимую процедуру «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 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;
}
person George Siggouroglou    schedule 28.08.2013
comment
Это атомарно? Если два абонента позвонят в nextval в одно и то же время, получат ли они один и тот же номер? - person Clavijo; 04.10.2013
comment
Это решение не решает проблему одновременного запроса нового идентификатора. Если два или более клиентов вызывают процедуру nextval одновременно в одно и то же время, тогда неясно, что будет добавлено. Это решение для небольшой системы. Это решение решает только мою заранее известную проблему с идентификатором при вставке данных. - person George Siggouroglou; 10.10.2013
comment
Это решение не оптимально. `nextval 'должен, по крайней мере, установить исключительную блокировку на столе. В противном случае два одновременных вызова вернут один и тот же номер. - person SQL Police; 14.10.2016
comment
@Clavijo - определенно не Atomic, да, два пользователя могут получить одно и то же значение, используя приведенный выше код. Я только что опубликовал ответ на этот вопрос, который (я считаю) допускает атомарные последовательности и решает проблемы параллелизма. - person mike; 25.01.2018

В 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)
person James Cane    schedule 23.01.2013

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

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

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

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

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

person user36804    schedule 12.11.2008

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

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

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

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

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

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

person Paul Klotka    schedule 13.01.2011
comment
Это правильный план, но он не решает проблемы параллелизма. Я только что разместил в ответе код, безопасный для транзакций и обрабатывающий несколько текущих транзакций. - person mike; 25.01.2018

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

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
person Trident D'Gao    schedule 08.10.2012

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

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

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

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

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

Примечания:

person Tony L.    schedule 28.09.2016

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

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

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

person John MacIntyre    schedule 12.11.2008

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

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

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

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

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

person MusiGenesis    schedule 12.11.2008
comment
Я слышу тебя. У всего есть свои плюсы и минусы. На самом деле я большой поклонник естественных ключей, поэтому обычно использую GUID только в том случае, если естественный ключ недоступен. Таким образом, таблица клиентов, вероятно, будет добавлена ​​в PK с помощью SSN или составного PK из имени и адреса и т. Д. - person MusiGenesis; 12.11.2008

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

person Community    schedule 15.12.2008

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

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

person Rob at TVSeries.com    schedule 17.10.2011

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

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

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

SELECT NEXT VALUE FOR [dbo].[SequenceFile]
person daniele3004    schedule 21.10.2017

БЕЗОПАСНОСТЬ ТРАНЗАКЦИЙ! Для версий SQLServer до 2012 года ... (спасибо Matt G.) В этом обсуждении не хватает одной вещи - безопасности транзакций. Если вы получаете число из последовательности, этот номер должен быть уникальным, и никакое другое приложение или код не должны иметь возможность получить этот номер. В моем случае мы часто извлекаем уникальные числа из последовательностей, но фактическая транзакция может занимать значительный промежуток времени, поэтому мы не хотим, чтобы кто-либо другой получил то же число до того, как мы зафиксируем транзакцию. Нам нужно было имитировать поведение последовательностей оракулов, где номер был зарезервирован при извлечении. Мое решение - использовать 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"'
person mike    schedule 24.01.2018
comment
Однако объекты SQL Server SEQUENCE уже безопасны для транзакций. Если вы извлекаете число из последовательности внутри транзакции, а кто-то другой вытаскивает его до того, как ваша транзакция будет завершена, они просто получат следующий номер. Полагаю, как и в случае с Oracle. Вы можете указать причины использования этого метода, а не встроенных функций ... - person Matt Gibson; 25.01.2018
comment
@MattGibson - ДА! Правильный. Этот код будет полезен только людям, использующим SQLServer версии до 2012 года. - person mike; 25.01.2018