Existe una técnica llamada control de versiones que existe desde hace muchos años, pero que en gran medida no funciona por varias razones. Sin embargo, existe una técnica similar a la que llamo Version Normal Form que he encontrado muy útil. Aquí hay un ejemplo usando una tabla de Empleados.
Primero, se crea la tabla estática. Esta es la tabla de entidad principal y contiene datos estáticos sobre la entidad. Los datos estáticos son datos que no se espera que cambien durante la vida de la entidad, como la fecha de nacimiento.
create table Employees(
ID int auto_generated primary key,
FirstName varchar( 32 ),
Hiredate date not null,
TermDate date, -- last date worked
Birthdate date,
... -- other static data
);
Es importante darse cuenta de que hay una entrada para cada empleado, al igual que con cualquier tabla de este tipo.
Luego, la tabla de versiones asociada. Esto establece una relación de 1 m con la tabla estática, ya que podría haber varias versiones para un empleado.
create table Employee_versions(
ID int not null,
EffDate date not null,
char( 1 ) IsWorking not null default true,
LastName varchar( 32 ), -- because employees can change last name
PayRate currency not null,
WorkDept int references Depts( ID ),
..., -- other changable data
constraint PK_EmployeeV primary key( ID, EffDate )
);
En la nota de la tabla de versiones hay una fecha de vigencia, pero no un campo coincidente que ya no sea efectivo. Esto se debe a que una vez que una versión entra en vigencia, permanece vigente hasta que se reemplaza por la versión posterior. La combinación de ID y EffDate debe ser única, por lo que no puede haber dos versiones para el mismo empleado que estén activas al mismo tiempo, ni puede haber una brecha entre el momento en que finaliza una versión y el momento en que comienza la siguiente.
La mayoría de las consultas querrán saber la versión actual de los datos de los empleados. Esto se proporciona al unir la fila estática del empleado con la versión que está vigente ahora. Esto se puede encontrar con la siguiente consulta:
select ...
from Employees e
join Employee_versions v1
on v1.ID = e.ID
and v1.EffDate =(
select Max( v2.EffDate )
from EmployeeVersions v2
where v2.ID = v1.ID
and v2.EffDate <= NOW()
)
where e.ID = :EmpID;
Esto devuelve la única versión que comenzó en el pasado más reciente. Usando la desigualdad <=en la verificación de fecha (v2.EffDate <= NOW()
) permite fechas efectivas en el futuro. Suponga que sabe que un nuevo empleado comenzará el primer día del próximo mes o un aumento de sueldo está programado para el día 13 del próximo mes, estos datos se pueden insertar con anticipación. Tales entradas "precargadas" serán ignoradas.
No dejes que la subconsulta te afecte. Todos los campos de búsqueda están indexados por lo que el resultado es bastante rápido.
Hay mucha flexibilidad con este diseño. La consulta anterior devuelve los datos más recientes de todos los empleados, presentes y pasados. Puede verificar el TermDate
campo para obtener sólo los empleados presentes. De hecho, dado que muchos lugares de sus aplicaciones solo estarán interesados en la información actual de los empleados actuales, esa consulta sería una buena vista (omita el where
final cláusula). No es necesario que las aplicaciones sepan que existen tales versiones.
Si tiene una fecha en particular y desea ver los datos que estaban vigentes en ese momento, cambie v2.EffDate <= NOW()
en la subconsulta a v2.EffDate <= :DateOfInterest
.
Se pueden encontrar más detalles en una presentación de diapositivas aquí y en un documento no completo aquí.
Para mostrar un poco de la extensibilidad del diseño, observe que hay un IsWorking
indicador en la tabla de versiones, así como una fecha de terminación en la tabla estática. Cuando un empleado deja la empresa, se inserta la última fecha en la tabla estática y una copia de la última versión con IsWorking
establecido en false
se inserta en la tabla de versiones.
Es bastante común que los empleados dejen una empresa por un tiempo y luego sean contratados nuevamente. Con solo la fecha en la tabla estática, la entrada se puede activar nuevamente simplemente configurando esa fecha nuevamente en NULL. Pero una consulta de "mirar hacia atrás" para cualquier momento en que la persona ya no era un empleado arrojaría un resultado. No habría ninguna indicación de que habían dejado la empresa. Pero una versión con IsWorking
=falso al dejar la empresa y IsWorking
=verdadero al regresar a la empresa permitirá verificar ese valor en el momento de interés e ignorar a los empleados cuando ya no eran empleados, incluso si regresaron más tarde.