我有以下要求
Sales Officer: Bob
Week1 Week2 Week3 ................. Week52
Prod1 10 15 12 ................. 14
Prod2 20 14 10 ................. 17
. .
. .
. .
销售主管将每周为每位销售人员设定目标。
销售人员可以通过与设定目标相似的网格(例如,每天)为每个产品输入实际销售额
的修改
在上述情况下,主管已为第1周设定了10个单位的目标。现在,销售人员将每天输入销售额1,2,0,1,3,2 = 9(第1周的实际销售额),因此针对目标他在第一周卖出了9个单位
我已经创建了Employee和Product表。任何人都可以指导如何在数据库中存储天和周的最佳做法,以便记录目标和实际销售。
我在考虑将数据存储在下表中
EmpSales (EmployeeID,ProductID,SaleTarget,Actual Sale,Date,WeekNo,Month)
提前致谢
答案 0 :(得分:3)
这个纯粹的关系建模术语非常简单。我没有看到任何形式的“非规范化”的必要性。
如果您不熟悉关系数据库建模标准,IDEF1X Notation可能会有所帮助。
纯5NF;完整的声明参照完整性;没有空,没有更新异常;没有GROUP BYs
;纯日期算术。
通过投影将SaleTarget
与SaleActual
进行比较,并且可能位于相同的结果集中。
如果您有月度和年度销售会计,则所需的扩展名是一个具有一点控制或结构的公共日历表;例如。类似于Week
,包括每个月和每年的行。请告诉我,我会更新模型。
我说5NF,因为这是我提供的最小消除更新异常,大多数建模者都熟悉它。但是,如果它没有吓到你,那么两个销售表实际上是第六范式。
这允许完全透视(跨越顶部的数周或数月;产品或员工;反之亦然;任何组合),无需临时表或复杂的SQL。 (请问。)
我认为它甚至可能是不言自明的,但我会提供详细说明业务规则的动词短语,因为每个都有三个家长参与:
享受。
StartDate
和 EndDate
,您已经破坏了3NF(引入的功能依赖性),并引入了更新异常。任何一行中的EndDate
是下一行中的StartDate
(减去1秒计算为欺骗,是一种设计);更新时,现在必须更改两行而不是一行。更重要的是,这种结构非常简单(不是时间序列或“时间”要求),EndDate
不是必需的。数据模型已更新,包括Month
和Year
要求。您现在需要在SaleTarget上使用Check Constraint来确保DateType
为W
一周。加载Date表很简单,您不需要在SQLTeam上发布的无意义代码(手动重复剪切和粘贴);他们以愚蠢和不合标准着称。
SaleActual
表现在包含每日,每周,每月和每年的值。当然,您可以在每周,每月,每一天的第一天以编程方式进行总结。首先将新行添加到Date
。
5NF是目前标准合规所需的最低要求,因此您需要习惯它。基本上,在3NF和5NF之间的NFs中,学术界(加上像wiki发布完整错误条目的cesspits)之间存在很多争论。 5NF的简短定义是它是3NF的目标,零数据重复,零更新异常(没有重复的列要交易更新)。
暂时忘记6NF。任何6NF的表都是5NF(4NF和BCNF和3NF)。只需将两个Sales表视为5NF即可。当你必须编写一个轮回报告时,比如从现在起一年后,就会知道这个结构的价值。捐赠给我最喜爱的慈善机构或喂养无家可归的人。
答案 1 :(得分:2)
我个人会将目标和实际值存储在不同的行中,并且很可能在单独的表中:
目标: EmployeeId,PeriodId,ProductId,TargetValue
销售: EmployeeId,PeriodId,ProductId,SalesValue
事实上,在一个集成系统中,第二个表通常是不必要的(假设您有一个完整的销售记录系统,这应该是实际记录销售的预测/视图 - 适当分配员工,期间和产品基于该子系统的模型)。
为了满足您的日历要求,我几乎肯定会有一个日期表,这样您就可以确保所有各种业务规则的数周和月的定义没有复杂的日期逻辑。然后通过连接到日历表来促进确定期间和聚合。
因此,ActualSales看起来像这样(只有一个通用的Period表,它本身可能是一个句号和日期表):
SELECT sp.EmployeeId
, p.ProductId
, pd.PeriodType
, pd.PeriodId
, SUM(id.Quantity * id.UnitProce) AS TotalSales
FROM Invoice AS i
INNER JOIN InvoiceDetail AS id
ON id.InvoiceId = i.InvoiceId
INNER JOIN Employee AS sp
ON sp.EmployeeId = i.SalesPersonId
INNER JOIN Product AS p
ON id.ProductId = p.ProductId
INNER JOIN Period AS pd
ON pd.StartDate <= i.InvoiceDate
AND pd.EndDate > i.InvoiceDate
GROUP BY sp.EmployeeId, p.ProductId, pd.PeriodType, pd.PeriodId
在这种情况下,如果您有重叠的句点(如每日,每周,每月),数据将会重复,因此您只需聚合一种类型的句点 - 这就是为什么我在此示例视图中特别包含它的原因尽管这里多余。
我希望通用的Period表看起来像:
PeriodId
PeriodType
StartDate
EndDate
这将预先填写您要报告的各个时段:
'Q', 1/1/2010, 4/1/2010
'M', 1/1/2010, 2/1/2010
'M', 2/1/2010, 3/1/2010
'M', 3/1/2010, 4/1/2010
'W', 1/3/2010, 1/10/2010
'W', 1/10/2010, 1/17/2010
etc.
'D', 1/1/2010, 1/2/2010
'D', 1/2/2010, 1/3/2010
etc.
担心假期是没有意义的,除非您可能不会分配目标,如果他们不工作,这主要是关于管理分配,以便他们可能是现实的。您可以拥有带有各种标志的日历表
Calendar
DateId
Date
IsHoliday
然后,您可以在加入时计算一段时间内的假期/周末数等。
这通常是会计/业务的事情,但您可能希望考虑标准化您的日历。例如,在媒体购买电视广告时,他们使每个“季度”相等,并使每个“月”标准化 - 4周,4周,5周。显然,他们为假日和特殊电视活动制作了例外,但这有助于平滑会计并更容易地比较期间。
答案 2 :(得分:1)
就个人而言,我会选择一个更通用的“期间”表。
期间(periodId,的startDate,结束日期,weekNo,月,年)
然后添加
empSales(EMPLOYEEID,产品编号,SaleTarget,ActualSale,periodId)。
这有点灵活(您可以轻松引入不同的时间跨度,并使相对周字段为空,或者定义一些在“标准”周内映射时间段的规则),冗余较少(注意如何月份和周已经从empSales表中移开了)它允许你进行报告和计算(顺便说一下,你没有包含Year字段,有原因吗?)。
计算内容应该更容易,因为假设您按天分类销售,除非您想在整个数据库中复制“周”字段,否则更容易将这些间隔相加。
另请注意,您可以轻松地在不同的重叠时段设置目标。
例如,您可以设置11月22日至28日的每周目标(我使用周一开始一周的欧洲会议)并在黑色星期五设置一个特殊的一天期限
所以:
Period:
periodId|startDate |endDate |weekNo|Month | Year|
0030020|22-NOV-2010|28-NOV-2010| 43 |November | 2010 |
0030026|26-NOV-2010|26-NOV-2010| null |November | 2010 |
empSales:
EmployeeId|ProductId|SaleTarget|ActualSale|periodId|
567689| 788585| 58 | 42 | 0030020|
567689| 788585| 28 | 32 | 0030026|
请注意员工567689如何错过他的每周目标,但设法超越了他的黑色星期五目标。
顺便说一下,在处理这个例子时,我认为你最好删除“empSales”表,将其重命名为“empTargets”:
empTargets(EMPLOYEEID,产品编号,SaleTarget,periodId)。
因为实际销售很容易通过UDF计算或放在视图中 - 毕竟,它只是一个
select sum(items_sold)
from sales
where sales.employeeId = empTargets.employeeId and
sales.ProductId= empTargets.ProductId and
sales.saleDate between empTargets.startDate and
empTargets.endDate)
因此无需将其直接存储在表格中(事实上,如果退回商品或其他未来更正,它可能会成为负担。)