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


Столбец идентичности примерно аналогичен последовательности.
Примерно да, но не совсем. Вам нужно что-то вставить в таблицу, прежде чем вы сможете быть уверены в идентификаторе.
наверное, надо было сказать "очень и очень грубо" :)
И вы также можете получить пробелы в вашей последовательности, если откатите вставку.
@JonasLincoln В ПОСЛЕДОВАТЕЛЬНОСТИ тоже могут быть пробелы. По сути, нет никакого способа гарантировать, что у вас никогда не будет пробелов.
Столбец идентификаторов работает очень хорошо, особенно в сочетании с предложением OUTPUT для возврата созданного значения идентификатора.
Вы можете просто использовать старые простые таблицы и использовать их как последовательности. Это означает, что ваши вставки всегда будут:
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 () выглядит как взлом.
Преодолей это. Находясь в Риме, делайте то же, что и римляне.
вам также нужно будет использовать SET TRANSACTION ISOLATION LEVEL SERIALIZABLE, чтобы гарантировать, что он работает, но, как и @Corey Trager, я также не рекомендую вам реализовывать это.
Что кажется хакерским в последовательностях? Просто любопытно, я сначала научился использовать последовательности. Как вы упомянули, все, что мы узнаем в первую очередь, кажется «правильным».
В SQL Server я просто указываю столбец как IDENTITY, и все готово. В Oracle мне нужно было сделать больше (извините, я забыл подробности. Это было 8 лет назад ...).
Ах, я понимаю, что вы говорите. Для Oracle это правильно, и это больно. В Postgres значение по умолчанию для столбцов может быть следующим значением последовательности, и это очень удобно.
Я полностью согласен и сделал это в прошлом году над проектом.
Я только что создал таблицу с названием последовательности, текущим значением и величиной приращения.
Затем я создал 2 процедуры, чтобы добавить и удалить их. И 2 функции, чтобы перейти к следующему и получить текущее.
Если вы хотите вставить данные с последовательным ключом, но вам не нужно снова запрашивать базу данных, чтобы получить только что вставленный ключ, я думаю, что у вас есть только два варианта:
Если я делаю генерацию ключей на стороне клиента, я использую GUID люблю. Я считаю, что они чертовски красивы.
row["ID"] = Guid.NewGuid();
Эта линия должна быть где-то на капоте спортивного автомобиля.
Я слышу тебя. У всего есть свои плюсы и минусы. На самом деле я большой поклонник естественных ключей, поэтому обычно использую GUID только в том случае, если естественный ключ недоступен. Таким образом, таблица клиентов, вероятно, будет добавлена в PK с помощью SSN или составного PK из имени и адреса и т. д.
Последовательности, реализованные в Oracle, требуют вызова базы данных перед вставкой. идентификаторы, реализованные в SQL Server, требуют вызова базы данных после вставки.
Один не более хакерский, чем другой. Чистый эффект тот же - зависимость от хранилища данных для предоставления уникальных значений искусственного ключа и (в большинстве случаев) два обращения к хранилищу.
Я предполагаю, что ваша реляционная модель основана на искусственных ключах, и в этом контексте я предлагаю следующее наблюдение:
Мы никогда не должны пытаться наделять искусственные ключи смыслом; их единственная цель должна заключаться в связывании связанных записей.
Что вам нужно в отношении данных для заказа? может ли это быть обработано в представлении (презентации) или это истинный атрибут ваших данных, который необходимо сохранить?
Если вы используете SQL Server 2005, у вас есть возможность использовать Row_Number
Создайте рабочую таблицу с идентификатором на ней.
Перед загрузкой промежуточной таблицы обрежьте и повторно установите идентификатор, чтобы он начинался с 1.
Загрузите свой стол. Каждая строка теперь имеет уникальное значение от 1 до N.
Создайте таблицу, содержащую порядковые номера. Это может быть несколько строк, по одной для каждой последовательности.
Найдите порядковый номер в созданной вами таблице последовательностей. Обновите номер последовательности, добавив количество строк в таблице этапов к порядковому номеру.
Обновите идентификатор таблицы этапов, добавив номер последовательности, который вы искали. Это простой одноэтапный процесс. или же Загрузите целевую таблицу, добавьте порядковый номер к идентификатору при загрузке в ETL. Это может использовать преимущества объемного загрузчика и учесть другие преобразования.
Это правильный план, но он не решает проблемы параллелизма. Я только что разместил в ответе код, безопасный для транзакций и обрабатывающий несколько текущих транзакций.
Другая проблема со столбцами идентификаторов заключается в том, что если у вас есть несколько таблиц, в которых порядковые номера должны быть уникальными, столбец идентификаторов не работает. И, как упоминает Кори Трейджер, реализация последовательности типа «ролл-сам» может вызвать некоторые проблемы с блокировкой.
Наиболее прямо эквивалентными решениями, по-видимому, является создание таблицы SQL Server с одним столбцом для идентификатора, который заменяет отдельный тип объекта «последовательность». Например, если в Oracle у вас будет две таблицы из одной последовательности, такой как Dogs <- sequence object -> Cats, тогда в SQL Server вы должны создать три объекта базы данных, все таблицы, такие как Dogs <- Pets с столбцом идентификаторов -> Кошки. Вы должны вставить строку в таблицу Pets, чтобы получить порядковый номер, в котором вы обычно используете NEXTVAL, а затем вставить в таблицу Dogs или Cats, как обычно, когда вы получаете фактический тип домашнего животного от пользователя. Любые дополнительные общие столбцы могут быть перемещены из таблиц Dogs / Cats в таблицу супертипов Pets, с некоторыми последствиями: 1) будет одна строка для каждого порядкового номера, 2) любые столбцы, которые невозможно заполнить при получении порядкового номера, будут должны иметь значения по умолчанию и 3) для получения всех столбцов потребуется соединение.
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/
@eidylon, в предыдущих версиях SQL Server можно было эмулировать объект sequence, используя выделенную таблицу со столбцом identity, как я объяснил в своем ответе. Хотя в сложных случаях, когда вам нужно сгенерировать более одного значения, это становится довольно некрасивым.
Рассмотрим следующий фрагмент.
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;
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)
Для решения этой проблемы я использовал таблицу «Последовательности», в которой хранятся все мои последовательности, и хранимую процедуру «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;
}
Это атомарно? Если два абонента позвонят в nextval в одно и то же время, получат ли они один и тот же номер?
Это решение не решает проблему одновременного запроса нового идентификатора. Если два или более клиентов вызывают процедуру nextval одновременно, тогда неясно, что будет добавлено. Это решение для небольшой системы. Это решение решает только мою заранее известную проблему с идентификатором при вставке данных.
Это решение не оптимально. `nextval 'должен, по крайней мере, установить исключительную блокировку на столе. В противном случае два одновременных вызова вернут один и тот же номер.
@Clavijo - определенно не Atomic, да, два пользователя могут получить одно и то же значение, используя приведенный выше код. Я только что опубликовал ответ на этот вопрос, который (я считаю) допускает атомарные последовательности и решает проблемы параллелизма.
Как sqljunkiesshare состояния, последовательности были добавлены в SQL Server 2012. Вот как это сделать в графическом интерфейсе. Это эквивалент:
CREATE SEQUENCE Schema.SequenceName
AS int
INCREMENT BY 1 ;
Заметки:
Начальное значение по умолчанию, Минимальное значение и Максимальное значение определялись диапазоном типа данных, который в данном случае был int. См. Здесь дополнительные диапазоны типов данных, если вы хотите использовать что-то, кроме int.
Довольно велика вероятность, что вы захотите, чтобы ваша последовательность начиналась с 1, и вы также может потребоваться минимальное значение 1.
С помощью SQL вы можете использовать эту стратегию;
CREATE SEQUENCE [dbo].[SequenceFile]
AS int
START WITH 1
INCREMENT BY 1 ;
и прочитайте уникальное следующее значение этого SQL
SELECT NEXT VALUE FOR [dbo].[SequenceFile]
СДЕЛКА БЕЗОПАСНА! Для версий 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"'
Однако объекты SQL Server SEQUENCE уже безопасны для транзакций. Если вы извлекаете число из последовательности внутри транзакции, а кто-то другой вытаскивает его до того, как ваша транзакция будет завершена, они просто получат следующий номер. Полагаю, как и в случае с Oracle. Вы можете указать причины использования этого метода, а не встроенных функций ...
@MattGibson - ДА! Правильный. Этот код будет полезен только людям, использующим SQLServer версии до 2012 года.
Кажется, это напрямую касается вашего вопроса: sqlteam.com/article/… (если это не так - я, должно быть, не понимаю некоторых тонкостей вашего варианта использования. Прокомментируйте, пожалуйста)