Дублирование номера счета

Здравствуйте!

Сегодня столкнулся с проблемой, что создавая счета, в базу были внесены для двух разных счетов одинаковые номера.
Менеджеры работают через Web-сервисы. Когда посмотрел на дату создания счетов с одинаковым номером, увидел разницу в 1 секунду!

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

Нравится

17 комментариев

Самым быстрым вариантом, думаю, будет сделать триггер.

Что бы не было дублирования номера должны генерится в одном месте. Логично в данном случае этим местом выбрать СУБД. Т.е. либо тригер, как предложил Александр Кравчук, но тогда не будет видно номера при редактировании. Либо слать запрос на получение номера до поста, но тогда в нумераци будут дырки.
Пока писал придумал еще простейший в реализации вариант - повесить уникальный ключ на поле :)

Сейчас задействована стандартная нумерация из системных настроек!

Решено перенести в тригер СУБД

--
Cogito, ergo sum

"Underscore a.k.a. _" написал:повесить уникальный ключ на поле :)

+1
Добавил :)

--
Cogito, ergo sum

Варианты

"Александр Кравчук" написал:
сделать триггер

или
"Underscore a.k.a. _" написал:
повесить уникальный ключ на поле

неудобны: или уникальный индекс будет выдавать ошибку или Вы не будет видеть какой номер у документа, счета и т.п.
Думаю самым приемлемым способом, будет светить какой-то номер во время добавления - пусть это будет через системные настройки, а перед добавлением пересчитывать на корректный: запускать ХП с выходным параметром, которая в свою очередь вычитывает текущее значение системной настройки, увеличивает на единицу, сохраняет и возвращает результат для сохранения в номере. Можно для надежности в транзакции ее запускать. Можно эту логику и в триггер вынести. Но тогда немного рассредоточится логика по формированию номера: и в конфигурации, и в триггере. Неудобно будет сопровождать. А ХП можно сделать универсальной - допустим только для числовых последовательностей.

ЗЫ

"Underscore a.k.a. _" написал:
в нумераци будут дырки.

Для этого есть или ну очень надуманные способы или запретить удаление :)

Соглашусь, что для

"Виталий Ковалишин aka samael" написал:
Как избежать в будущем дублирование нумерации счетов, которые создаются за короткий промежуток времени?

уникальный индекс самое то - на уровне сервера будет проверка целостности данных.

Такой вот вариант надумал:

CREATE TRIGGER tr_tbl_Invoice_IIN 
   ON  [tbl_Invoice]
   AFTER INSERT
AS 
BEGIN
	declare @ID uniqueidentifier, @NewNumber int,
		@Number nvarchar(250), @NumberMask varchar(10)
 
	SET NOCOUNT ON;
 
	select @ID = [ID], @Number = [InvoiceNumber] from inserted
 
    if (@Number is null)
    begin
	set @NumberMask = ( select [StringValue] from [tbl_SystemSetting] where [Code] = 'InvoiceMask' )
	set @NewNumber = ( select [IntegerValue] +1 from [tbl_SystemSetting] where [Code] = 'InvoiceNumber' )
	set @Number = REPLACE(@NumberMask, '%1', @NewNumber) 
	update [tbl_SystemSetting] set [IntegerValue] = @NewNumber where [Code] = 'InvoiceNumber'
	update [tbl_Invoice] set [InvoiceNumber] = @Number where [ID] = @ID
    end
 
END

--
Cogito, ergo sum

"Виталий Ковалишин aka samael" написал:
SELECT @ID = [ID], @Number = [InvoiceNumber] FROM inserted

Учтите, что inserted может содержать несколько записей.

"Осауленко Александр" написал:Думаю самым приемлемым способом, будет светить какой-то номер

А чем показывать в общем случае неправильный номер лучше, чем нек показывать его вообще.

"Осауленко Александр" написал:Учтите, что inserted может содержать несколько записей.

учтено:

ALTER TRIGGER tr_tbl_Invoice_IIN 
   ON  [tbl_Invoice]
   AFTER INSERT
AS 
BEGIN
	DECLARE @ID uniqueidentifier, @NewNumber int,
		@Number nvarchar(250), @NumberMask varchar(10)
 
SET NOCOUNT ON;
 
DECLARE  c_Invoices CURSOR FOR
  select [ID], [InvoiceNumber] from inserted
 
OPEN  c_Invoices
  WHILE 1 = 1
  BEGIN 
    FETCH c_Invoices
     INTO @ID, @Number
 
     IF @@fetch_status = -1 BREAK 
     IF @@fetch_status = -2 CONTINUE 
 
    if (@Number is null)
    begin
	set @NumberMask = ( select [StringValue] from [tbl_SystemSetting] where [Code] = 'InvoiceMask' )
	set @NewNumber = ( select [IntegerValue] +1 from [tbl_SystemSetting] where [Code] = 'InvoiceNumber' )
	set @Number = REPLACE(@NumberMask, '%1', @NewNumber) 
	update [tbl_SystemSetting] set [IntegerValue] = @NewNumber where [Code] = 'InvoiceNumber'
	update [tbl_Invoice] set [InvoiceNumber] = @Number where [ID] = @ID
    end
   END  
CLOSE c_Invoices
DEALLOCATE c_Invoices
 
END
GO

"Осауленко Александр" написал:Можно эту логику и в триггер вынести. Но тогда немного рассредоточится логика по формированию номера: и в конфигурации, и в триггере. Неудобно будет сопровождать.

Не нарушена, все из системных настроек :)

--
Cogito, ergo sum

"Underscore a.k.a. _" написал:
А чем показывать в общем случае неправильный номер лучше, чем нек показывать его вообще.

Я почти на 100% уверен, что такая ситуация будет очень редко, да еще и пользователь на это обратит внимание в 1 случае из 1000. Т.е. почти не реально :)

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

Самое главное - чтобы транзакция по получению номера и его увеличению была очень-очень короткая (намного короче, чем транзакция по сохранению всего счета). Возможна еще блокировки записи на время транзакции. Тогда одинаковых номеров не будет.

А "дырки" в такой нумерации - это практически неизбежное зло

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

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

Все что нужно было, так это перенести логику со скриптов на уровень СУБД... Так и сделал!

--
Cogito, ergo sum

"Виталий Ковалишин aka samael" написал:

ALTER TRIGGER tr_tbl_Invoice_IIN
   ON  [tbl_Invoice]
   AFTER INSERT
AS
BEGIN
        DECLARE @ID uniqueidentifier, @NewNumber int,
...


Я бы еще добавил условие для репликации, даже если у Вас пока ее и нет, что бы потом не "шерстить" БД:

ALTER TRIGGER tr_tbl_Invoice_IIN
   ON  [tbl_Invoice]
   AFTER INSERT
AS
BEGIN
        IF (SYSTEM_USER = 'TS_REPLICATION') RETURN 
        DECLARE @ID uniqueidentifier, @NewNumber int,
...

Спасибо, Александр!

Тогда данное условие нужно во все новые триггеры добавить

IF (SYSTEM_USER = 'TS_REPLICATION') RETURN 

--
Cogito, ergo sum

Здравствуйте! В очередной раз поражаюсь возможностям T-SQL:

"Виталий Ковалишин aka samael" написал:

...
SET @NumberMask = ( SELECT [StringValue] FROM [tbl_SystemSetting] WHERE [Code] = 'InvoiceMask' )
        SET @NewNumber = ( SELECT [IntegerValue] +1 FROM [tbl_SystemSetting] WHERE [Code] = 'InvoiceNumber' )
        SET @Number = REPLACE(@NumberMask, '%1', @NewNumber)
        UPDATE [tbl_SystemSetting] SET [IntegerValue] = @NewNumber WHERE [Code] = 'InvoiceNumber'
UPDATE [tbl_Invoice] SET [InvoiceNumber] = @Number WHERE [ID] = @ID
...

...


Можно заменить на:

...
  UPDATE [tbl_SystemSetting]
  SET
     @Number = (SELECT REPLACE([StringValue], '%1', [IntegerValue] + 1)
                FROM [tbl_SystemSetting] WHERE [Code] = 'InvoiceMask'), 
     [IntegerValue] =  [IntegerValue] + 1
  WHERE [Code] = 'InvoiceNumber'
 
  UPDATE [tbl_Invoice]
  SET
     [InvoiceNumber] = @Number
  WHERE [ID] = @ID
...

Красота!:)

Абсолютно с Вами согласен! :)

--
Cogito, ergo sum

Показать все комментарии