一、宽表和窄表的区别
宽表:从字面意义上来说,就是比较"宽"的表,也就是说字段比较多,通常是指业务主题相关的指标、维度、属性关联在一起的一张数据库表。从数据库表设计层面上来说,宽表首先不满足三范式的模型设计规范。所以它主要的弊端是会产生数据的大量冗余,好处呢就是查询性能高、便捷
宽表的设计广泛应用在数据挖掘模型训练前的数据准备,把相关字段都放在同 一张表中,可以大大提高数据挖掘模型训练过程中的迭代计算时的效率!
宽表:空间换时间,便于训练迭代、减少关联表的数量。
窄表:严格按照数据库设计三范式的规范,尽可能的减少数据冗余。但是,缺点就是修改一个数据可能需要去修改多张表!
二、数据库三大范式
为了建立冗余较小、结构合理的数据库,设计数据库时必须遵循一定的规则。在关系型数据库中这种规则就叫做范式。
范式就是符合某一种设计要求的总结,要想设计一个结构合理的关系型数据库,必须满足一定的范式。
在实际开发中最常见的设计范式有三个:
1、第一范式*(确保每列保持原子性)
第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库满足第一范式。
第一范式的合理遵循需要根据系统给的实际需求来确定。比如某些数据库系统中需要用到“地址”这个属性,本来直接将“地址”属性设计成为一个数据库表的字段就行,但是如果系统经常访问“地址”属性中的“城市”部分,那么一定要把“地址”这个属性重新拆分为省份、城市、详细地址等多个部分来进行存储,这样对地址中某一个部分操作的时候将非常方便,这样设计才算满足数据库的第一范式。如下图。
上图所示的用户信息遵循第一范式的要求,这样对用户使用城市进行分类的时候就非常方便,也提高了数据库的性能。
2、第二范式(确保表中的每列都和主键相关)
第二范式在第一范式的基础上更进一层,第二范式需要确保数据库表中每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。也就是说在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中。
比如要设计一个订单信息表,因为订单中可能会有多种商品,所以要将订单编号和商品编号作为数据库表的联合主键,如下图。
这里产生一个问题:这个表中是以订单编号和商品编号作为联合主键,这样在该表中商品名称、单位、商品价格等信息不与该表的主键相关,而仅仅是与商品的编号相关,所以在这里违反了第二范式的设计原则。
而如果把这个订单信息表进行拆分,把商品信息分离到另一个表中,把订单项目表也分离到另一个表中,就非常完美了,如下图。
这里这样设计,在很大程度上减小了数据库的冗余,如果要获取订单的商品信息,使用商品编号到商品信息表中查询即可。
3、第三范式(确保每列都和主键列直接相关,而不是间接相关)
第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。
比如在设计一个订单数据表的时候,可以将客户编号作为一个外键和订单表建立相应的关系,而不可以在订单表中添加关于客户其他信息(比如姓名、所属公司)的字段,如下面这两个表所示的设计就是一个满足第三范式的数据库表。
这样在查询订单信息的时候,就可以使用客户编号来引用客户信息表中的记录,也不必再订单信息表中多次输入客户信息的内容,减小了数据冗余。
三、宽表和窄表选择的实际案例
我们以这样一个案例讲解:
某公司需要设计销售领域的订单事实表,该事实表应该包含哪些维度和度量?事实表和维表该分别如何去设计?
好了,我们把关键信息拿出来,首先我们要有维度包括:销售员、销售员所属部门、下订单的时间;度量:销售量;
那么,订单事实表,其实就是一个商品销售的清单;
依照这个思路,我们建立的第一个模型可能是以下这样的:
单单看上去,貌似是符合我们的问题的需要,而且符合数据库的范式设计:没有冗余字段;但是情况真的就是这样吗?
答案是否定的,确实对于一般的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,那么对于上面这个模型,我们得改动销售员和订单表;而对于上面的其他两个模型都仅仅需要改动一张表就行了,造成查询数据部一致往往也就是这种数据模型所造成的。