宽表和窄表的建设该如何选择?

本文探讨了数据仓库中销售领域订单事实表的设计方法,通过对比不同的表结构,分析其对统计查询的影响,旨在寻找更适合OLAP场景的数据模型。


这个问题相信纠结了很多从是数据库开发、数据仓库开发和后台开发人员;单单考虑这个问题,难给出一个绝对的答案;本人从事数据仓库开发工作到现在已经有一年半时间了,对于这个问题,我也曾经纠结过,但是是否有绝对的答案呢?事实上任何东西都没有绝对的说法。

考虑这样的一个问题,一个公司有这样的一个需求:

设计销售领域的订单事实表,该事实表应该包含哪些维度和度量?事实表和维表该分别如何去设计?

好了,我们把关键信息拿出来,首先我们要有维度包括:销售员、销售员所属部门、下订单的时间;度量:销售量;

那么,订单事实表,其实就是一个商品销售的清单;

依照这个思路,我们建立的第一个模型可能是以下这样的:

单单看上去,貌似是符合我们的问题的需要,而且符合数据库的范式设计:没有冗余字段;但是情况真的就是这样吗?

答案是否定的,确实对于一般的OLTP系统而言这样的表设计确实减少了冗余和,增删改查等操作也很方便,但是往往对于我们的统计系统、OLAP、数据挖掘而言,情况却并非如此,举个例子:我们要统计每个部门各自的销售量为多少?那么对于上表,sql是这样的:

select a.*,b.sid into #dep_saleser from department a,saleser_dim b on a.dep_id = b.dep_id;

select count(1),a.dep_name from #dep_saleser a,order_fact b on a.sid=b.sid group by a.dep_name;

对于这么一个简单的需求已经要写两了sql去实现了,其实数据库表模型的的设计是灵活的,我们完全可以根据我们的业务去设计我们的数据表;考虑到部门和销售员可以是同属于销售者这个维度,只是他们是有上下级别关系的那么依照这个思路,我们的模型可以建立为下面这样:

那么统计每个部门各自的销售量,可以用如下sql去实现:

select count(1),a.dep_name from saleser_dim a,order_fact b

on a.sid=b.sid group by a.dep_name;

确实对于这个模型而言,有些情况下会出现冗余(填写用户,没有填写部门;填写部门没填写用户);但是对于提取数统计的逻辑又相对来说要简单了好多;

考虑到要实现取数简单,我们还可以想出另外一种方法:

  看上去好像不错哦~~,取数据也就一句sql就搞掂了,但是却是最最槽糕的情况,有可能一个销售员,前几天登记的部门是a,但是其实他的所属于的部门为b,那么对于上面这个模型,我们得改动销售员和订单表;而对于上面的其他两个模型都仅仅需要改动一张表就行了,造成查询数据部一致往往也就是这种数据模型所造成的。

    所谓的宽表就是字段比较多的表,包含的维度层次比较多,造成冗余也比较多,毁范式设计,但是利于取数统计,而窄表往往对于OLTP比较合适,符合范式设计原则;

 

### 解决方案设计 #### 已知信息整理 用户希望查询商店1中铅笔的售价,涉及多联查操作。假设当前数据库中有如下两张核心: - **Products**: 记录商品基本信息及其在各个商店的价格。 - 字段:`product_id`, `product_name`, `store1_price`, `store2_price`, `store3_price` - **Stores**: 记录商店详情(可选)。 - 字段:`store_id`, `store_name` 目标是从 `Products` 中提取满足条件的数据,并将其转换为标准化形式以便后续扩展。 --- #### 查询逻辑分解 为了实现该需求,可以通过以下步骤构建 SQL 查询语句: 1. **过滤指定商店价格不为空的商品记录** 需要选取 `store1_price` 不为空的商品作为候选集[^1]。 2. **限定商品名称为“铅笔”** 结合第一步的结果进一步缩小范围至仅包含商品名为“铅笔”的条目。 3. **重构输出格式** 将原始结构转化为形式,便于展示每种商品在一个特定商店内的定价情况。最终结果应至少包括三列:`product_id`, `store`, `price`. --- #### SQL 实现代码 以下是具体的 SQL 查询语句示例: ```sql SELECT product_id, 'store1' AS store, store1_price AS price FROM Products WHERE product_name = '铅笔' AND store1_price IS NOT NULL; ``` 上述命令实现了针对单个商店 (`store1`) 的精确查找任务。如果还需要同时获取其他店铺的相关报价,则可通过 UNION ALL 来组合多个子查询成果: ```sql SELECT product_id, 'store1' AS store, store1_price AS price FROM Products WHERE product_name = '铅笔' AND store1_price IS NOT NULL UNION ALL SELECT product_id, 'store2' AS store, store2_price AS price FROM Products WHERE product_name = '铅笔' AND store2_price IS NOT NULL; /* 可继续追加更多商店 */ ``` 以上方法不仅适用于单一商店场景,也支持跨多家门店统一检索业务诉求[^1]。 --- #### 示例解释说明 假如我们有一份初始数据如下所示: | product_id | product_name | store1_price | store2_price | store3_price | |------------|--------------|---------------|---------------|---------------| | 1 | 铅笔 | 5 | | 7 | | 2 | 橡皮擦 | | 3 | | 运行上面提到的第一个查询后得到的结果将是这样的: | product_id | store | price | |------------|-------|-------| | 1 | store1| 5 | 而第二个综合版本则会返回更全面的信息列覆盖所有符合条件的有效记录项。 --- ### 注意事项提醒 尽管所提供的解决方案已经尽力兼顾通用性灵活性,但在实际应用过程中仍需关注一些潜在陷阱或者边界状况处理机制建设工作: 1. 如果未来计划增加新的销售网点字段时记得及时调整现有视图定义以免遗漏新增维度带来的影响变化; 2. 考虑性能优化策略尤其是在大规模数据集合上的现评估指标监控体系建立完善程度如何直接影响用户体验质量高低优劣之分界线所在位置究竟在哪里值得深入探讨研究一番才行呢! --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值