"Ідентифікатор" у форматі: YYYYNNNNNN з частиною перезавантаження частини NNNNNN щороку


11

У мене є вимога бізнесу, щоб кожен запис у таблиці рахунків-фактур мав ідентифікатор, який виглядає як YYYYNNNNNN.

Частина NNNNNN потребує перезавантаження на початку кожного року. Отже, перший рядок, введений у 2016 році, виглядатиме як 2016000001, а другий - 2016000002 тощо. Скажімо, останній запис за 2016 рік був 2016123456, Наступний рядок (2017 року) повинен виглядати як 2017000001

Мені не потрібно, щоб цей ідентифікатор був основним ключем, і я також зберігаю дату створення. Ідея полягає в тому, що цей "ідентифікатор відображення" є унікальним (так що я можу запитувати його) та здатним до групи людей за роками.

Навряд чи будь-які записи будуть видалені; проте я б схильний кодувати захистно проти чогось подібного.

Чи є спосіб, щоб я міг створити цей ідентифікатор, не маючи запиту про максимальний ідентифікатор цього року кожен раз, коли вставляти новий рядок?

Ідеї:

  • A CreateNewInvoiceSP, який отримує MAXзначення за цей рік (yucky)
  • Якась магічна вбудована функція для саме цього (я можу мріяти правильно)
  • Можливість вказати деякі UDF або щось у IDENTITYабо DEFAULTдекларації (??)
  • Вид, який використовує PARTITION OVER + ROW()(видалення буде проблематичним)
  • Тригер увімкнено INSERT(все одно потрібно буде виконати деякий MAXзапит :()
  • Щорічне фонове завдання, оновлено таблицю з MAX для кожного року, що вставляється, я потім ... Щось ?!

Все це трохи не ідеально. Будь-які ідеї чи варіації вітаються, хоча!


У вас є кілька хороших відповідей, але якщо у вас є рік, ідентифікатор як ПК, то вибирати макс досить швидко.
папараццо

використання вибору max max запиту є звичайною практикою. використовувати це.
Uğur Gümüşhan

Відповіді:


17

До вашого поля є 2 елементи

  • Рік
  • Номер автоматичного збільшення

Їх не потрібно зберігати як одне поле

Приклад:

  • Стовпець року, де встановлено за замовчуванням YEAR(GETDATE())
  • Стовпчик чисел на основі послідовності.

Потім створіть обчислений стовпчик, об'єднуючи їх (з відповідним форматуванням). Послідовність може бути скинута при зміні року.

Приклад коду в SQLfiddle : * (SQLfiddle не завжди працює)

-- Create a sequence
CREATE SEQUENCE CountBy1
    START WITH 1
    INCREMENT BY 1 ;

-- Create a table
CREATE TABLE Orders
    (Yearly int NOT NULL DEFAULT (YEAR(GETDATE())),
    OrderID int NOT NULL DEFAULT (NEXT VALUE FOR CountBy1),
    Name varchar(20) NOT NULL,
    Qty int NOT NULL,
    -- computed column
    BusinessOrderID AS RIGHT('000' + CAST(Yearly AS VARCHAR(4)), 4)
                     + RIGHT('00000' + CAST(OrderID AS VARCHAR(6)), 6),
    PRIMARY KEY (Yearly, OrderID)
    ) ;


-- Insert two records for 2015
INSERT INTO Orders (Yearly, Name, Qty)
    VALUES
     (2015, 'Tire', 7),
     (2015, 'Seat', 8) ;


-- Restart the sequence (Add this also to an annual recurring 'Server Agent' Job)
ALTER SEQUENCE CountBy1
    RESTART WITH 1 ;

-- Insert three records, this year.
INSERT INTO Orders (Name, Qty)
    VALUES
     ('Tire', 2),
     ('Seat', 1),
     ('Brake', 1) ;

1
Можливо, чистіше мати одну послідовність на рік. Таким чином, немає необхідності виконувати DDL як частину регулярних операцій.
usr

@gbn Тож мені знадобиться фонове завдання, щоб перезапустити його SEQUENCE на початку кожного року?
DarcyThomas

@usr На жаль, ви не можете використовувати NEXT VALUE FORу CASEзаяві (я спробував)
DarcyThomas

8

Чи планували ви створити поле ідентичності з насінням = 2016000000?

 create table Table1 (
   id bigint identity(2016000000,1),
   field1 varchar(20)...
)

Це насіння потрібно автоматично збільшувати щороку, наприклад, у ніч на 2017/1/1 потрібно запланувати

DBCC CHECKIDENT (Table1, RESEED, 2017000000)

Але я вже бачу проблеми з дизайном, наприклад: що робити, якщо у вас мільйон записів?


2
Інша проблема полягає в тому, якщо записи не відображаються хронологічно. Ідентичність - це, мабуть, не шлях, якщо це так.
Даніель Хатмахер

@LiyaTansky У моєму випадку мені сказали, що повинно бути лише 50 000 записів на рік. Але я розумію, що ти маєш на увазі те, що він крихкий з 1kk рядками
DarcyThomas

1

У цьому сценарії я помножив рік на 10 ^ 6 і додав до цього значення послідовності. Це має перевагу в тому, що не потрібно розраховувати обчислене поле з його (малим) поточним накладним покриттям, і поле може використовуватися як a PRIMARY KEY.

Можливі два прийоми:

  • переконайтеся, що ваш множник достатньо великий, щоб ніколи не виснажуватися, і

  • Вам не гарантується послідовність без прогалин через кешування послідовності.

Я не є експертом у галузі SQL Server, але ви, ймовірно, можете встановити подію, яку слід викликати в 201x 00:00:00, щоб скинути послідовність на нуль. Це теж я робив на Firebird (чи це Interbase?).


1

Редагувати: Це рішення не працює під навантаженням

Я не шанувальник спускових механізмів, але це здається найкращим, що я міг опрацювати.

Плюси:

  • Без фонових завдань
  • Може робити швидкі запити на DisplayId
  • Тригеру не потрібно сканувати попередню частину NNNNNN
  • Буде перезавантажувати частину NNNNN щороку
  • Працюватиме, якщо буде більше 100000 рядків на рік
  • Не потребує оновлення схеми (наприклад, скидання послідовностей), щоб продовжувати працювати в майбутньому

Редагувати: Мінуси:

  • Вийде з ладу під навантаженням (назад до креслення дошки)

(Подяка @gbn, коли я взяв натхнення від їхньої відповіді) (Будь-яка зворотній зв'язок та вказівка ​​на очевидні помилки вітаються :)

Додайте нові COLUMNs та anINDEX

ALTER TABLE dbo.Invoices
ADD     [NNNNNNId]      INT  NULL 

ALTER TABLE dbo.Invoices
ADD [Year]              int NOT NULL DEFAULT (YEAR(GETDATE()))

ALTER TABLE dbo.Invoices
ADD [DisplayId]     AS  'INV' +
                        CAST([Year] AS VARCHAR(4))+
                        RIGHT('00000' + CAST([NNNNNNId] AS VARCHAR(4)),  IIF (5  >= LEN([NNNNNNId]), 5, LEN([NNNNNNId])) )                  

EXEC('CREATE NONCLUSTERED INDEX IX_Invoices_DisplayId
ON dbo.Invoices (DisplayId)')

Додайте нове TRIGGER

CREATE TRIGGER Invoices_DisplayId
ON dbo.Invoices
  AFTER  INSERT
AS 
BEGIN

SET NOCOUNT ON;    

UPDATE dbo.Invoices
SET NNNNNNId = CalcDisplayId
FROM (SELECT I.ID, IIF (Previous.Year = I.Year , (ISNULL(Previous.NNNNNNId,0) + 1), 1) AS CalcDisplayId  FROM
        (SELECT 
            ID  
           ,NNNNNNId 
           ,[year]
        FROM  dbo.Invoices
        ) AS Previous
    JOIN inserted AS I 
    ON Previous.Id = (I.Id -1) 
    ) X
WHERE 
   X.Id = dbo.Invoices.ID       
END
GO

Я настійно не рекомендую цього робити. Це, ймовірно, може зайти в глухий кут і призвести до несправностей вставок, коли ви потрапите під невеликий навантаження Чи помістили ви копію в базу даних і забили її декількома десятками ниток одночасно, роблячи вставки (і, можливо, також вибирає / оновлює / видаляє), щоб побачити, що відбувається?
Коді Коніор

@CodyKonior це принципово помилково чи його можна було б відновити за допомогою трохи розумного блокування? Як ні, як би ви підійшли до проблеми?
DarcyThomas

Хммм. Ран з 10 нитками. Не впевнений, чи це мертві замки, але я отримую деякі перегони. Якщо один тригер завершений, перед запуском попереднього рядка рядків Це призводить до введення купівлі NULLзначень. Назад до дошки для малювання ...
DarcyThomas

Тоді катастрофа запобігла :-) Мій секрет у тому, що я розпізнав зразок чогось, що я робив близько п'яти років тому. Я знаю лише, що те, як ви скануєте таблицю всередині тригера, шукаючи наступну послідовність, підтягує речі під навантаженням. Я не пам'ятаю, як я це вирішив, але можу перевірити пізніше.
Коді Коніор

@CodyKonior Я не думаю, що це робить сканування ( ON Previous.Id = (I.Id -1) слід просто шукати), але так, все одно не працює. Якби я міг заблокувати таблицю (?) Під час вставки та тригера, я думаю, що це спрацює. Але це також звучить як кодовий запах.
DarcyThomas
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.