Desde que alguien publicó una pregunta similar, he estado reflexionando sobre esto. El primer problema es que las bases de datos no proporcionan secuencias "particionables" (que reiniciarían/recordarían en función de diferentes claves). La segunda es que la SEQUENCE
objetos que son proporcionados están orientados al acceso rápido y no se pueden deshacer (es decir, usted podrá conseguir huecos). Básicamente, esto descarta el uso de una utilidad integrada... lo que significa que tenemos que implementar la nuestra.
Lo primero que vamos a necesitar es una tabla para almacenar nuestros números de secuencia. Esto puede ser bastante simple:
CREATE TABLE Invoice_Sequence (base CHAR(1) PRIMARY KEY CLUSTERED,
invoiceNumber INTEGER);
En realidad, la base
la columna debe ser una referencia de clave externa a cualquier tabla/id que defina la(s) empresa(s)/entidad(es) para las que está emitiendo facturas. En esta tabla, desea que las entradas sean únicas por entidad emitida.
A continuación, desea un proceso almacenado que tomará una clave (base
) y escupe el siguiente número en la secuencia (invoiceNumber
). El conjunto de claves necesario variará (es decir, algunos números de factura deben contener el año o la fecha completa de emisión), pero el formulario base para esta situación es el siguiente:
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 ;;
Tenga en cuenta que:
- Usted debe ejecutar esto en una transacción serializada
- La transacción debe ser el mismo que se está insertando en la tabla de destino (factura).
Así es, aún obtendrá el bloqueo por negocio al emitir números de factura. Tu no puedes evítelo si los números de factura deben ser secuenciales, sin espacios; hasta que la fila se confirme, es posible que se revierta, lo que significa que el número de factura no se habría emitido.
Ahora, dado que no desea tener que recordar llamar al procedimiento para la entrada, envuélvalo en un disparador:
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, tiene más columnas, incluidas otras que deben completarse automáticamente; deberá completarlas)
... que luego puede usar simplemente diciendo:
INSERT INTO Invoice (base) VALUES('A');
Entonces, ¿qué hemos hecho? En su mayoría, todo este trabajo consistía en reducir la cantidad de filas bloqueadas por una transacción. Hasta este INSERT
está comprometido, solo hay dos filas bloqueadas:
- La fila en
Invoice_Sequence
manteniendo el número de secuencia - La fila en
Invoice
para la nueva factura.
Todas las demás filas para una base
particular son gratuitos:se pueden actualizar o consultar a voluntad (borrar información de este tipo de sistema tiende a poner nerviosos a los contadores). Probablemente necesite decidir qué debería suceder cuando las consultas normalmente incluirían la factura pendiente...