Desde que alguém postou uma pergunta semelhante, tenho pensado nisso. O primeiro problema é que os bancos de dados não fornecem sequências "particionáveis" (que reiniciariam/lembrariam com base em chaves diferentes). A segunda é que a
SEQUENCE
objetos que são fornecidos são voltados para o acesso rápido e não podem ser revertidos (ou seja, você vai obter lacunas). Isso essencialmente exclui o uso de um utilitário embutido... o que significa que temos que lançar o nosso próprio. A primeira coisa que vamos precisar é de uma tabela para armazenar nossos números de sequência. Isso pode ser bastante simples:
CREATE TABLE Invoice_Sequence (base CHAR(1) PRIMARY KEY CLUSTERED,
invoiceNumber INTEGER);
Na realidade, a
base
A coluna deve ser uma referência de chave estrangeira para qualquer tabela/id que defina a(s) empresa(s)/entidades para as quais você está emitindo faturas. Nesta tabela, você deseja que as entradas sejam exclusivas por entidade emitida. Em seguida, você deseja um proc armazenado que receberá uma chave (
base
) e cuspir o próximo número na sequência (invoiceNumber
). O conjunto de chaves necessário varia (ou seja, alguns números de fatura devem conter o ano ou a data completa de emissão), mas a forma base para esta situação é a seguinte:CREATE PROCEDURE Next_Invoice_Number @baseKey CHAR(1),
@invoiceNumber INTEGER OUTPUT
AS MERGE INTO Invoice_Sequence Stored
USING (VALUES (@baseKey)) Incoming(base)
ON Incoming.base = Stored.base
WHEN MATCHED THEN UPDATE SET Stored.invoiceNumber = Stored.invoiceNumber + 1
WHEN NOT MATCHED BY TARGET THEN INSERT (base) VALUES(@baseKey)
OUTPUT INSERTED.invoiceNumber ;;
Observe que:
- Você deve execute isso em uma transação serializada
- A transação deve seja o mesmo que está inserindo na tabela de destino (fatura).
Isso mesmo, você ainda terá bloqueio por empresa ao emitir números de fatura. Você não pode evite isso se os números da fatura precisarem ser sequenciais, sem lacunas - até que a linha seja realmente confirmada, ela poderá ser revertida, o que significa que o número da fatura não teria sido emitido.
Agora, como você não quer ter que lembrar de chamar o procedimento para a entrada, envolva-o em um gatilho:
CREATE TRIGGER Populate_Invoice_Number ON Invoice INSTEAD OF INSERT
AS
DECLARE @invoiceNumber INTEGER
BEGIN
EXEC Next_Invoice_Number Inserted.base, @invoiceNumber OUTPUT
INSERT INTO Invoice (base, invoiceNumber)
VALUES (Inserted.base, @invoiceNumber)
END
(obviamente, você tem mais colunas, incluindo outras que devem ser preenchidas automaticamente - você precisará preenchê-las)
... que você pode usar simplesmente dizendo:
INSERT INTO Invoice (base) VALUES('A');
Então, o que fizemos? Principalmente, todo esse trabalho foi reduzir o número de linhas bloqueadas por uma transação. Até este
INSERT
é confirmado, há apenas duas linhas bloqueadas:- A linha em
Invoice_Sequence
mantendo o número de sequência - A linha na
Invoice
para a nova fatura.
Todas as outras linhas de uma
base
específica são gratuitos - podem ser atualizados ou consultados à vontade (a exclusão de informações desse tipo de sistema tende a deixar os contadores nervosos). Você provavelmente precisa decidir o que deve acontecer quando as consultas normalmente incluem a fatura pendente...