'Id' con il formato: AAAAMMGNNNN con la parte NNNNNN che si riavvia ogni anno


11

Ho un requisito aziendale che ogni record nella tabella Fattura abbia un ID che assomiglia a AAAAMMGNNNN.

La parte NNNNNN deve essere riavviata all'inizio di ogni anno. Quindi la prima riga immessa nel 2016 sarebbe simile a 2016000001 e la seconda come 2016000002 ecc. Supponiamo che l'ultimo record per il 2016 sia stato 2016123456, La riga successiva (del 2017) dovrebbe apparire come 2017000001

Non ho bisogno che questo ID sia la chiave primaria e memorizzo anche la data di creazione. L'idea è che questo "ID di visualizzazione" sia unico (quindi posso interrogarlo da esso) e capace di essere utilizzato da gruppi umani, per anno.

È improbabile che qualsiasi record venga cancellato; tuttavia, sarei propenso a codificare sulla difensiva qualcosa del genere.

Esiste un modo per creare questo ID senza dover richiedere l'id massimo quest'anno ogni volta che inserisco una nuova riga?

idee:

  • A CreateNewInvoiceSP, che ottiene il MAXvalore per quell'anno (schifo)
  • Alcune funzionalità magiche integrate per fare esattamente questo (posso sognare bene)
  • Essere in grado di specificare un UDF o qualcosa nella dichiarazione IDENTITYo DEFAULT(??)
  • Una vista che utilizza PARTITION OVER + ROW()(eliminata sarebbe problematica)
  • Un trigger attivato INSERT(sarebbe comunque necessario eseguire alcune MAXquery :()
  • Un lavoro in background annuale, aggiornato una tabella con il MAX per ogni anno inserito che poi ... Qualcosa ?!

Tutto ciò non è l'ideale. Eventuali idee o variazioni sono benvenute!


Hai delle buone risposte ma se hai un anno, id come PK, selezionare max è piuttosto veloce.
paparazzo,

l'utilizzo di una query select max id è una pratica comune. usalo.
Uğur Gümüşhan,

Risposte:


17

Ci sono 2 elementi nel tuo campo

  • Anno
  • Un numero con incremento automatico

Non è necessario che siano memorizzati come un campo

Esempio:

  • Una colonna dell'anno con un valore predefinito di YEAR(GETDATE())
  • Una colonna numerica basata su una sequenza.

Quindi creare una colonna calcolata concatenandoli (con la formattazione appropriata). La sequenza può essere ripristinata al cambio di anno.

Codice di esempio in SQLfiddle : * (SQLfiddle non funziona sempre)

-- 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
Forse è più pulito avere una sequenza all'anno. In questo modo non è necessario eseguire DDL come parte delle normali operazioni.
usr

@gbn Quindi avrei bisogno di un lavoro in background per riavviare SEQUENCE all'inizio di ogni anno?
Darcy Thomas

@usr Purtroppo non puoi usarlo NEXT VALUE FORin una CASEdichiarazione (ho provato)
DarcyThomas

8

Hai considerato di creare un campo identità con seed = 2016000000?

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

Questo seme dovrebbe essere autoincrementato ogni anno, ad esempio nella notte del 2017/1/1 che devi programmare

DBCC CHECKIDENT (Table1, RESEED, 2017000000)

Ma vedo già problemi con il design, ad esempio: cosa succede se hai milioni di record?


2
Un altro problema è se i record non compaiono in ordine cronologico. L'identità probabilmente non è la strada da percorrere se questo è il caso.
Daniel Hutmacher,

@LiyaTansky Nel mio caso mi è stato detto che dovrebbero essere solo 50k record all'anno. Ma capisco cosa intendi sul fatto che sia fragile con 1kk file
DarcyThomas

1

Quello che ho fatto in questo scenario è stato moltiplicare l'anno per 10 ^ 6 e aggiungere il valore della sequenza a quello. Ciò ha il vantaggio di non richiedere un campo calcolato con il suo (piccolo) overhead in corso e il campo può essere utilizzato come PRIMARY KEY.

Esistono due possibili gotcha:

  • assicurati che il tuo moltiplicatore sia sufficientemente grande da non essere mai esaurito, e

  • non è garantita una sequenza senza lacune a causa della memorizzazione nella cache della sequenza.

Non sono un esperto di SQL Server, ma probabilmente puoi impostare un evento da attivare a 201x 00:00:00 per ripristinare la sequenza su zero. Questo è anche quello che ho fatto su Firebird (o era Interbase?).


1

Modifica: questa soluzione non funziona sotto carico

Non sono un fan dei fattori scatenanti, ma questo mi sembra meglio che potrei capire.

Professionisti:

  • Nessun lavoro in background
  • Può eseguire query rapide su DisplayId
  • Il trigger non deve eseguire la scansione per la parte NNNNNN precedente
  • Riavvia la parte NNNNN ogni anno
  • Funzionerà se ci sono più di 100000 righe all'anno
  • Non richiede aggiornamenti dello schema (ad es. Ripristini di sequenza) per continuare a funzionare in futuro

Modifica: Contro:

  • Fallirà sotto carico (torna al tavolo da disegno)

(Ringraziamo @gbn perché ho preso ispirazione dalla loro risposta) (Qualsiasi feed back e sottolineando gli ovvi errori benvenuti :)

Aggiungi alcuni nuovi se COLUMNunINDEX

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)')

Aggiungi il nuovo 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

Consiglio vivamente di non farlo. È probabile che si blocchi e causi errori di inserimento una volta sotto carico. Hai messo una copia in un database fittizio e l'hai martellato con poche dozzine di thread contemporaneamente facendo inserimenti (e forse anche selezionando / aggiorna / elimina) per vedere cosa succede?
Cody Konior,

@CodyKonior è fondamentalmente difettoso o potrebbe essere resuscitato con un po 'di blocco giudizioso? In caso contrario, come affronteresti il ​​problema?
Darcy Thomas,

Hmmm. Ha funzionato con 10 discussioni. Non sono sicuro se si tratta di blocchi morti, ma ho alcune condizioni di gara. Quando viene completato un trigger, prima che il trigger delle righe precedenti sia terminato. Questo porta all'inserimento di un sacco di NULLvalori. Di nuovo al tavolo da disegno ...
DarcyThomas

Il disastro evitato allora :-) Il mio segreto è che ho riconosciuto lo schema di qualcosa che ho fatto circa cinque anni fa. So solo che il modo in cui scansionate la tabella all'interno del trigger cercando la sequenza successiva fa scattare le cose sotto carico. Non ricordo come l'ho risolto, ma posso controllare più tardi.
Cody Konior,

@CodyKonior Non penso che stia eseguendo una scansione ( ON Previous.Id = (I.Id -1) dovrebbe solo cercare), ma sì, non funziona ancora. Se potessi bloccare la tabella (?) Durante l'inserimento e il trigger, penso che funzionerebbe. Ma sembra anche un odore di codice.
Darcy Thomas Thomas,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.