如何为事实表建模
Posted
技术标签:
【中文标题】如何为事实表建模【英文标题】:How to model a fact table 【发布时间】:2012-07-11 10:14:55 【问题描述】:我即将创建一个数据仓库,其中包含星型模式中的事实和维度。
我想回答的商业问题通常是这些:
我们在第一季度卖了多少钱? 我们在第一季度卖给了女性多少钱? 我们在第一季度向 30-35 岁的女性销售了多少钱? 我们在第一季度向居住在纽约的 30-35 岁女性销售了多少钱?我们在第一季度向居住在纽约的 30-35 岁女性卖了多少钱?
去年我们在服装类别中卖了多少钱?
去年我们的产品蓝色牛仔裤卖了多少钱? 去年,我们向居住在澳大利亚的 40 至 42 岁的男性销售蓝色牛仔裤产品的价格是多少?我正在考虑一个小时粒度的日期维度(指定年、月、日、小时、季度、日名称、月名称等) 我也在考虑产品维度和用户维度。
我想知道这些问题是否可以使用单个事实表来回答,或者创建多个事实表是否合适?我正在考虑一个表格,例如:
FactSales
DimDate - 转换为包含日期信息的表格(例如季度、星期几、年、月、日)
DimProduct - 指向包含产品信息的表,例如(产品名称)
DimUser - fk 到包含用户信息的表格,例如(年龄、性别)
TotalSales - 这些特定日期、产品和用户的所有销售额的总和。
另外,如果我想测量展位的总销售额(金钱)和总销售额?创建一个具有相同维度但使用 TotalNumberOfSales 作为事实的新事实表是否合适?
感谢我能得到的所有意见。
【问题讨论】:
也许“DimCustomer”比“DimUser”更合适? 【参考方案1】:我认为你是在正确的轨道上。以上所有问题都应该可以仅使用一个涵盖销售额的事实表来回答。
我认为一开始应该不聚合,然后在需要时聚合。考虑到一个销售可以包含多个产品和多个项目,我将其组织如下......销售中每个产品的一个事实行(通常是发票上的行,所以我称之为“订单行”或“销售线”),也许还有三个柜台属性:
NumItems
- 商品数量,如果客户购买了三件相同的产品,则为 3。
NumLines
- “订单行”的数量 - 应始终为 1。可能在稍后聚合数据时有用(在 SQL 中已经有 sum(NumLines)
而不是 count(*)
),或者在添加更正项时(@987654325 @)。
NumSales
- 一个小数,因此可以将其相加得出销售数量(即,如果销售涉及三种不同的产品并因此包含三个订单行,则为 0.333)。
现在,要获得正确的计数会遇到问题,即“涉及黑色衣服的销售数量”。我们在以前的工作场所遇到过这个问题 - 我确信必须存在一些“最佳实践”,我们最终或多或少地通过在事实表中引入SaleID
(或TransactionID
)并执行@987654329 @。这缺乏优雅,但很有效。
在我们的设置中,我们有几个货币属性 - 最重要的是,一个是收入(在支付与所售商品相关的直接成本后剩下的收入),另一个是营业额(客户为物品)。销售税或增值税可能会增加更多的复杂性。可以只使用一个货币属性,然后在事实表中将销售额拆分为多行,但我认为我更愿意在销售行事实表中推荐多个货币列。事实表中的所有内容都以“基础货币”(在我们的例子中为欧元)计算,然后我们有一个汇率维度来跟踪确切的金额。
我认为包含一天中的小时的日期维度没有意义。在我以前的工作中,我将仓库保存在 postgres 中,实际上我在没有日期维度的情况下管理得很好——尽管日期维度被认为是“最佳业务实践”,但我发现从性能方面来说,就我们所有的目的而言,我们得到了更好的性能通过使用标准的 postgres 日期函数而不是在日期维度中拖动。我玩了很多次,我认为最后我发现最优化的是将日期和时间分成两个不同的属性。 (时区和夏令时让我非常头疼……)
【讨论】:
好的,但是如果我不汇总数据并进行日期/产品/用户的 PK,那么它很可能会出现问题,因为它会重复? 您可能确实在源数据中有某种唯一 ID,即事务 ID?然后一种方法可能是只在仓库中使用它——但是,在数据仓库中使用新的唯一 ID 作为主键被认为是“最佳实践”。当我在之前的工作地点建一个仓库时,我在 ETL 层创建了一个 ID 映射表,以确保仓库中的每条记录都可以归属于源系统中的一条记录,并防止重复。 另外,如果您不聚合数据并保留完整的时间戳(精确到秒或更好),那么时间戳/产品/用户仍然不应该产生重复......至少不会在理想世界。我从经验中了解到,尽管依赖这样的假设是危险的;-)【参考方案2】:我同意 tobixen - 你在正确的轨道上。
我建议您阅读 Ralph Kimball 的书“数据仓库工具包”,尤其是关于零售的一章 - 它深入探讨了销售事实。
日期维度就像有一个日历表 - 您可以根据季度、会计月份和其他特定于日期的业务进行拆分。我通常同时保留日期键和时间戳数据类型,因此我们可以使用会计日历来做事。如果您需要将表格的粒度放在该级别,我实际上会有一个单独的时间维度,一天中的几个小时或分钟等都有桶。我怀疑您是否需要每小时。
我会这样做:
声明事实表的粒度:
每个订单行 1 行
注意grain如何不包含任何不能唯一标识行的东西。
订单行的维度属性:
Date
Time (if needed, and bucketed by hour/minute etc)
Product
Customer
订单行的退化维度(这些是与交易相关的代码):
Order Number
Order Line Number
一些示例措施:
Item Price at time of Sale (optional, may be useful in some situations)
Discount Amount
Sale Dollars
这应该可以回答所有这些问题。
对于总计,过滤维度属性后的简单 COUNT / SUM 应该可以正常工作。
您应该考虑到客户维度是最难建模的维度之一,Kimball 在他的书中用了一整章的篇幅介绍了客户维度。
【讨论】:
我不会打扰时间维度……至少在最初的设计中不会,除非有明确的需要。但是,我认为在事实表中包含完整的时间戳是有意义的——它不会花费太多,并且以后可以进行任何类型的临时查询,例如“一天中的哪个小时我们的营业额最多”,或者“就在午餐时间左右,我们在哪一刻钟的销售量最多?”,如果以后需要,它也可以更容易地创建时间维度。 @tobixen 他在问关于第一季度的问题。在我工作过的几乎每个组织中,Q1 的定义都不同——在时间暗淡/日历表中包含“季度”属性是有意义的。他还询问“年”,可以是“财政年”、“纳税年”等。编辑 - 重读您的评论后,我同意不需要“一天中的时间”类型维度,但我坚持我的建议“日期”维度。 拥有一个日期维度被认为是“最佳商业实践”,我从来没有说过其他的 :-) 但是,我在以前的工作场所的经验是,我可以使用 postgres 的库来管理所有业务需求日期和时间函数,并且我们通过拖入日期维度损失了显着的性能。我不知道美国,但在欧洲,季度和年份通常由公历非常严格地定义,唯一引起小麻烦的是时区和 DST。 @tobixen 是的,在美国,不同的公司有各种各样的日历。零售商往往在销售旺季前后经营,因此第一季度从 2 月开始,第四季度在 1 月结束。教育机构的财政年度通常设定在 7 月至 6 月。会计期间通常采用 4-4-5 或 4-5-4 财政周日历,因此虽然常规日历显示“6 月 1 日”,但会计会为 5 月记录交易。以上是关于如何为事实表建模的主要内容,如果未能解决你的问题,请参考以下文章