SQL Server — автоматическое увеличение, позволяющее выполнять операторы UPDATE.

При добавлении элемента в мою базу данных мне нужно, чтобы он автоматически определял значение для поля DisplayOrder. Идентификация (автоинкремент) была бы идеальным решением, но мне нужно иметь возможность программно изменять (ОБНОВЛЯТЬ) значения столбца DisplayOrder, а Identity, похоже, не позволяет этого. На данный момент я использую этот код:

CREATE PROCEDURE [dbo].[AddItem]

AS

DECLARE @DisplayOrder INT

SET @DisplayOrder = (SELECT MAX(DisplayOrder) FROM [dbo].[MyTable]) + 1

INSERT INTO [dbo].[MyTable] ( DisplayOrder ) VALUES ( @DisplayOrder )

Это хороший способ сделать это или есть лучший/более простой способ?


person Community    schedule 10.08.2010    source источник
comment
Я только что столкнулся с проблемой, связанной с этим методом: MAX возвращает NULL, если в таблице нет строк. Следовательно, эта процедура не может добавить первую строку, так как выдает ошибку!   -  person    schedule 11.08.2010
comment
Здесь найдено исправление: stackoverflow.com/questions/1688715/   -  person    schedule 12.08.2010


Ответы (5)


Решение этой проблемы из статьи «Внутри Microsoft SQL Server 2008: запросы T-SQL».

CREATE TABLE dbo.Sequence(
 val int IDENTITY (10000, 1) /*Seed this at whatever your current max value is*/
 )

GO

CREATE PROC dbo.GetSequence
@val AS int OUTPUT
AS
BEGIN TRAN
    SAVE TRAN S1
    INSERT INTO dbo.Sequence DEFAULT VALUES
    SET @val=SCOPE_IDENTITY()
    ROLLBACK TRAN S1 /*Rolls back just as far as the save point to prevent the 
                       sequence table filling up. The id allocated won't be reused*/
COMMIT TRAN

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

CREATE TABLE dbo.Sequence2(
 val int 
 )

GO

INSERT INTO dbo.Sequence2 VALUES(10000);

GO

CREATE PROC dbo.GetSequence2
@val AS int OUTPUT,
@n as int =1
AS
UPDATE dbo.Sequence2 
SET @val = val = val + @n;

SET @val = @val - @n + 1; 
person Martin Smith    schedule 11.08.2010

Вы можете настроить свой увеличивающийся столбец на использование свойства identity. Затем в процессах, которым необходимо вставить значения в столбец, вы можете использовать команду SET IDENITY_INSERT в своем пакете.

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

INSERT INTO [dbo].[MyTable] ( MyData ) VALUES ( @MyData )

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

SET IDENTITY_INSERT MyTable ON

INSERT INTO [dbo].[MyTable] ( DisplayOrder, MyData )
VALUES ( @DisplayOrder, @MyData )

SET IDENTITY_INSERT MyTable OFF

Вы должны иметь возможность ОБНОВИТЬ столбец без каких-либо других шагов.

Вы также можете изучить команду DBCC CHECKIDENT. Эта команда установит ваше следующее значение идентификатора. Если вы вставляете строки, где следующее значение идентификатора может оказаться неподходящим, вы можете использовать команду для установки нового значения.

DECLARE @DisplayOrder INT

SET @DisplayOrder = (SELECT MAX(DisplayOrder) FROM [dbo].[MyTable]) + 1

DBCC CHECKIDENT (MyTable, RESEED, @DisplayOrder)
person bobs    schedule 10.08.2010
comment
(Кстати, в SET IDENITY_INSERT есть опечатка) - person ; 11.08.2010
comment
Приятно помочь. Я исправлю опечатку для будущих читателей. Удачи. - person bobs; 11.08.2010
comment
Я только что заметил, что IDENTITY_INSERT, похоже, не разрешает операторы UPDATE для столбцов идентификаторов (он разрешает только операторы INSERT). Мне понадобится другое решение. - person ; 11.08.2010

Вот решение, которое я сохранил:

CREATE PROCEDURE [dbo].[AddItem]

AS

DECLARE @DisplayOrder INT

BEGIN TRANSACTION

SET @DisplayOrder = (SELECT ISNULL(MAX(DisplayOrder), 0) FROM [dbo].[MyTable]) + 1

INSERT INTO [dbo].[MyTable] ( DisplayOrder ) VALUES ( @DisplayOrder )

COMMIT TRANSACTION
person Community    schedule 11.08.2010
comment
Это может привести к вводу дубликатов DisplayOrders, если у вас есть одновременные выполнения AddItem. - person Martin Smith; 12.08.2010
comment
Но операторы SELECT и INSERT заключены в транзакцию. Разве он не должен исправлять дубликаты? - person ; 12.08.2010
comment
Я думаю, что это была идея, но она не будет работать без дополнительных подсказок блокировки на любом уровне изоляции. Поскольку два одновременных выбора не будут блокировать друг друга и будут читать одно и то же значение. Затем вставки будут успешными, за исключением сериализуемого уровня, когда, я думаю, вы получите тупик. - person Martin Smith; 12.08.2010

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

Этого достаточно легко добиться: добавьте

begin transaction

в начале процедуры и

commit transaction

в конце.

person David Knell    schedule 10.08.2010
comment
Этот ответ, похоже, не отвечает на вопрос. - person bobs; 11.08.2010
comment
Это отвечает на часть вопроса о том, можно ли улучшить предложенный способ. - person David Knell; 11.08.2010
comment
Объединение SELECT и INSERT в транзакцию не блокирует 2 одновременных выбора чтения максимального значения - person Martin Smith; 12.08.2010

Ваш способ отлично работает (с небольшой модификацией) и прост. Я бы обернул это транзакцией, как сказал @David Knell. Это приведет к коду вроде:

CREATE PROCEDURE [dbo].[AddItem]

AS

DECLARE @DisplayOrder INT

BEGIN TRANSACTION

SET @DisplayOrder = (SELECT MAX(DisplayOrder) FROM [dbo].[MyTable]) + 1

INSERT INTO [dbo].[MyTable] ( DisplayOrder ) VALUES ( @DisplayOrder )

COMMIT TRANSACTION

Включение SELECT и INSERT в транзакцию гарантирует, что ваши значения DisplayOrder не будут дублироваться AddItem. Если вы выполняете много одновременных добавлений (много раз в секунду), может возникнуть конфликт в MyTable, но для случайных вставок это не будет проблемой.

person Aaron D    schedule 10.08.2010
comment
Включение SELECT и INSERT в транзакцию вовсе не гарантирует этого. - person Martin Smith; 12.08.2010
comment
Вы правы, я думаю, что этот ответ на самом деле имеет гораздо более чистое решение: stackoverflow.com/questions/193257/ - person Aaron D; 12.08.2010
comment
Ага. Версия этого была в книге, упомянутой в моем ответе. Он обрабатывает распределение фрагментов чисел намного проще, чем решение, которое я выбрал. Дело в том, что это решение заключается в том, что если порядковый номер выделяется внутри одной и той же транзакции, он будет блокировать любые одновременные вставки до тех пор, пока не завершится первая транзакция, что может быть или не быть желательным. Решение для таблицы идентификаторов не блокирует. Редактировать: посмотрев на это еще раз, это совсем не то же самое. Я обновлю свой ответ альтернативным. - person Martin Smith; 12.08.2010