Если вы просто хотите отслеживать, когда новые сотрудники нанимаются или увольняются, вам следует начать с добавления соответствующих полей в саму таблицу сотрудников: HireDate date NOT NULL
и TerminationDate date NULL
.
Тогда очень легко определить численность персонала (и детали) в любой конкретный день:
SELECT EmployeeID, EmployeeName, ...
FROM Employees
WHERE HireDate <= @EndDate
AND (TerminationDate IS NULL OR TerminationDate > @BeginDate)
Если вам нужно отслеживать изменений (т.е. изменение заголовка), то подход, который даст вам максимальную гибкость, заключается в ведении таблицы истории в реальном времени с триггерами (или встроенной в вашу базу данных). изменить отслеживание, если доступно). Я бы не советовал делать полные снимки, поскольку они будут занимать огромное количество места на протяжении всего жизненного цикла приложения.
Ваша таблица истории должна содержать все поля в вашей базовой таблице, плюс еще два - измененная дата и тип транзакции. Возможно, также 3-е поле автономера / последовательности / идентификатора. Версия T-SQL:
CREATE TABLE EmployeeHistory
(
TransactionID int NOT NULL IDENTITY(1, 1)
CONSTRAINT PK_EmployeeHistory PRIMARY KEY CLUSTERED,
TransactionDate datetime NOT NULL,
TransactionType tinyint NOT NULL, -- 1 = Add, 2 = Change, 3 = Delete
EmployeeID int NOT NULL,
EmployeeName varchar(100) NOT NULL,
...
)
Затем сохраните его с помощью триггера:
CREATE TRIGGER tr_Employees_History
ON Employees
FOR INSERT, UPDATE
AS BEGIN
INSERT EmployeeHistory (TransactionDate, TransactionType, EmployeeID, ...)
SELECT
GETDATE(),
CASE
WHEN d.EmployeeID IS NULL THEN 1
WHEN (i.TerminationDate IS NOT NULL) AND
(d.TerminationDate IS NULL) THEN 3
ELSE 2
END,
i.EmployeeID, i.EmployeeName, ...
FROM inserted i
LEFT JOIN deleted d
ON d.EmployeeID = i.EmployeeID
END
Я собираюсь предположить, что вы не удаляете записи о сотрудниках и просто устанавливаете TerminationDate
; если вы удалите вместо этого (пожалуйста, не делайте этого), вам нужно будет написать аналогичный триггер DELETE
вместо второй строки CASE WHEN i.TerminationDate ...
.
Теперь заполняем таблицу истории:
INSERT EmployeeHistory (TransactionDate, TransactionType, EmployeeID, ...)
SELECT HireDate, 1, EmployeeID, ...
FROM Employees
Примечание - если у вас нет HireDate
, просто замените его на GETDATE()
- ваша история будет действительна только с того момента, как вы ее заполнили.
Теперь, если вы хотите получить исторический «снимок», вы можете сделать это:
CREATE FUNCTION dbo.GetEmployeeSnapshot(@ReportDate datetime)
RETURNS TABLE
AS RETURN
WITH History_CTE AS
(
SELECT
TransactionType, EmployeeID, EmployeeName, ...,
ROW_NUMBER() OVER (ORDER BY TransactionDate DESC) AS RowNum
FROM EmployeeHistory
WHERE TransactionDate <= @ReportDate
)
SELECT *
FROM History_CTE
WHERE RowNum = 1
AND TransactionType IN (1, 2) -- Filter out terminated employees
И если этот запрос выполняется медленно, если вам нужно ускорить определенные агрегаты, такие как численность персонала, , то и только тогда вы должны начать думать о таблицах моментальных снимков:
CREATE TABLE HeadcountHistory
(
ReportDate datetime NOT NULL
CONSTRAINT PK_HeadcountHistory PRIMARY KEY CLUSTERED,
HeadCount int NOT NULL
)
и обновление proc:
CREATE PROCEDURE dbo.UpdateHeadcountHistory
AS
DECLARE @ReportDate datetime
SET @ReportDate = GETDATE()
INSERT HeadcountHistory (HeadCount)
SELECT @ReportDate, COUNT(*)
FROM dbo.GetEmployeeSnapshot(@ReportDate)
Запустите этот последний sproc как часть запланированного задания, и тогда у вас будет денормализованная таблица отчетов для нужных вам агрегатов.
Что-нибудь более сложное, чем это, и я думаю, что вы захотите вместо этого начать изучать хранилище данных.