Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Chave composta exclusiva do SQL Server de dois campos com incremento automático do segundo campo


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:
  1. Você deve execute isso em uma transação serializada
  2. 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...