我是一名业务分析师,并为我们正在实施的系统准备了表格/错误。
上下文本质上是一个员工管理系统,员工可以加入公司,更改职位,升职,降职,终止等。所有这些都需要跟踪以进行过滤和报告。因此,我们需要对记录进行历史跟踪。
我的建议和表格的原始设计包括一个名为“生效日期”的字段,因此从日期开始,特定的“行动”基本上是有效的。
比如说,约翰于2017年1月1日作为顾问加入了一个组织,因此行动是他被聘用,因此生效日期是2017年1月1日,他在一段时间内担任顾问,直到他成为2017年9月6日的高级顾问,生效日期为2017年9月6日,并为该记录提升了行动。
顺便说一下,我们还将根据员工的职位和其他参数对员工的工资进行计算,这样就可以从其他表中引用派生的字段和字段等。
现在我的老板和解决方案架构师建议不要使用“生效日期”,我的老板说计算会出现“问题”,但没有详细说明,解决方案架构师说它会更容易使用开始日期和结束日期而不是生效日期。他的理由是,如果行动/事件没有结束日期,但是一旦提供了结束日期,它就处于非活动状态。
我的问题是我们必须维护一个我认为完全不合适的附加列。
大脑对StackOverflow的信任是什么建议?
谢谢:)
答案 0 :(得分:4)
你的直觉很好。不要使用结束日期。这增加了可能的异常数据的复杂性和来源。请按以下顺序输入:
ID <attr> StartDate EndDate
1 ... Jan 1 Jan 20
1 ... Jan 20 Jan 22
1 ... Feb 1 Jul 30
1月1日记录了状态变化,直到1月20日下一次状态变更为止。现在我们遇到了问题。根据该版本的EndDate,1月22日还有另一个州的变化,但下一个版本于2月1日开始。
这在时间流中形成了一个空白,我们没有指出问题所在。 1月22日的EndDate错了吗? 2月1日的StartDate错了吗?或者是否存在连接缺口两端的缺失版本?没有办法说出来。
ID <attr> StartDate EndDate
1 ... Jan 1 Jan 20
1 ... Jan 20 Feb 20
1 ... Feb 1 Jul 30
现在国家重叠。据说第二个州一直持续到2月20日,但是第三个国家说它从2月1日开始。但是一个州的开始在逻辑上意味着前一个州的结束。同样,我们不知道(只是通过查看数据)哪个日期是错误的。
知道一个状态的开始也表示上一个状态的结束,看看当我们简单地删除EndDate列时会发生什么。
ID <attr> EffDate
1 ... Jan 1
1 ... Jan 20
1 ... Feb 1
现在差距和重叠是不可能的。每个州从生效日期开始,到下一个州开始时结束。由于EffDate字段是PK的一部分,因此对于给定的ID值,任何条目都不能具有相同的EffDate值。
此设计不与主实体表一起使用。它是作为第二范式的特殊形式实现的,我可以为普通形式(vnf)版本。
您的员工表格中包含的字段不会随着时间的推移而发生变化。您可能还会更改字段,但您不希望跟踪这些更改。
create table Employees(
ID int auto_generated primary key,
Hired date not null,
FName varchar not null,
LName varchar not null,
Sex enum -- M or F
BDay date,
Position enum not null,
PayRate currency,
DeptID int references Depts( ID )
);
如果我们希望跟踪数据的更改,我们可以添加生效日期字段。但是,请考虑雇用日期和出生日期等数据不会从一个版本更改为另一个版本。因此,它们仅依赖于ID字段。更改的数据(Position,PayRate,DeptID)取决于ID 和生效日期字段。该表不再是2nf。
所以我们正常化:
create table Employees(
ID int auto_generated primary key,
Hired date not null,
FName varchar not null,
Sex enum -- M or F
BDay date
);
create table Employees_V(
ID int not null references Employees( ID ),
EffDate date not null,
LName varchar not null,
Position enum not null,
PayRate currency,
DeptID int references Depts( ID ),
constraint PK_Employees_V primary key( ID, EffDate )
);
姓氏可能会偶尔发生变化,尤其是女性员工。
此方法的一个主要优点是外键无法引用版本。现在所有FK都可以正常引用主实体表。
获取&#34;当前&#34;的查询数据相对简单:
select e.ID, e.Hired, e.FName, v.Lname, e.Sex, e.BDay, v.Position, v.PayRate, v.DeptID
from Employees e
join Employees)V v
on v.ID = e.ID
and v.EffDate =(
select Max( EffDate )
from Employees_V
where ID = v.ID
and EffDate <= GetDate())
where e.ID = 123;
比较查询具有开始/结束日期的表。
select ID, Hired, FName, Lname, Sex, BDay, Position, PayRate, DeptID
from Employees
where ID = 123
and StartDate >= GetDate()
and EndDate < GetDate();
这假定当前版本的EndDate值是一个神奇的值,例如12/31/9999。
第二个查询看起来比第一个查询简单得多。即使数据如上所示进行了规范化,也存在连接但没有子查询。它看起来也会执行得更快。
我已经使用这种技术大约8年了,而且由于性能问题,我从来没有改变它。 vnf查询在最坏情况下运行 比起始/结束版本慢不到10%。因此,一分钟的查询大约需要1分5秒。但是,在某些情况下,vnf查询执行速度会更快。
获取具有许多变化(数千个版本)的实体。开始/结束查询执行索引扫描。它从最早的版本开始,必须按顺序检查每个版本,直到找到EndDate小于目标日期的版本。通常,这是最后一个版本。在vnf查询中,子查询可以执行索引查找。
所以不要拒绝这种设计,因为你觉得它很慢。它并不慢。特别是当您考虑插入新版本时只需要一个INSERT语句。使用开始/结束日期时,插入新版本需要UPDATE,然后是INSERT。在两个现有版本之间插入新版本时,它有两个UPDATE和一个INSERT。要删除开始/结束版本,需要一个或两个UPDATE和一个DELETE语句。要删除vnf版本,只需删除版本。
如果版本之间的开始日期和结束日期不同步,那么您就会有差距或重叠,并找到合适的值。祝你好运。
因此,我会采取较小的性能影响,以确保数据永远不会失去同步并在我身上变得异常。事实证明,这个(vnf)实际上是更简单的设计。
答案 1 :(得分:3)
绝对执行结束日期。写作时要做的工作要多一点,但是你只写了一次,但是你会多次报告它,你会发现当结束日期已经存在时,它会让一切变得更容易(也更快)在记录上。
遍布stackoverflow,您会发现有关编写查询以查找给定记录的结束日期的问题,当在&#39; next&#39;记录而不是当前&#39;记录这些查询丑陋和慢
如果您查看SAP等企业系统的后端,您会发现记录已定义了开始日期和结束日期。
关于你的同事关于不使用生效日期的评论:你没有提供太多信息,所以我猜。我猜测有一个真实的有效日期&#39;当事情发生时,还有另一组开始和结束日期,即变更适用的工资单生效日期。因此,如果某人从1号开始,则工资单生效日期可能实际上是第15天。这也可以用于FTE计算。薪资和工资期确实很大,而且相当复杂,所以你不应该低估那里的复杂性。如果您在此系统中包含薪酬计算,那么至少您需要了解有效的工资核算日期。
您不应该害怕存储四个日期列而不是一个。数据库可以让您轻松搞定。
答案 2 :(得分:0)
使用startDate
和endDate
会使更新变得混乱,但可以更轻松,更快速地获取有效的日期。
异步更新同一条记录可能会导致日期重叠,因为我们需要获取更新范围内的所有记录并分别更新这些记录。
另一方面,使用effectiveDate
只会加快更新过程,并且会终止日期重叠问题。但是用这种方式获取似乎太复杂了。
例如:
ID Data EffDate
1 ... Jan 1 2020
1 ... Jan 30 2020
1 ... Feb 1 2020
在上面的示例中,如果要获取生效日期2月1日的记录,则必须比较前3条记录以匹配最高日期(如果要获取列表,则不可能)。这样一来,与其他有效的带日期表连接起来将很混乱。