目录
今天我们来聊聊在数仓模型中举足轻重的维度建模。
简单而言,数据仓库的核心目标是为展现层提供优质服务。其中包含ETL过程、数仓规范、数仓分层等建设流程,最终提供更清晰易用的展现层。
维度建模的领域主要适用于数据集市层,它的最大的作用其实是为了解决数据仓库建模中的性能问题。
在谈论维度模型前,我们先来聊聊数仓架构。
数仓模型不只是考虑如何设计和实现功能,设计原则应该从访问性能、数据成本、使用成本、数据质量、扩展性来考虑。
1)业务数据驱动
保持底层业务的数据驱动为导向,同时结合业务需求驱动的基本原则。
2)便于数据分析
屏蔽底层复杂业务,简单、完整、集成的将数据暴露给分析层。保持底层业务变动与上层需求变动对模型冲击最小化。
业务系统变化影响削弱在基础数据层,结合自上而下的建设方法,削弱需求变动对模型的影响,保持数据水平层次清晰化。
3)高内聚低耦合
主题之内或各个完整意义的系统内数据的高内聚
主题之间或各个完整意义的系统间数据的低耦合
4)构建仓库基础数据层
底层业务数据整合工作与上层应用开发工作相隔离,为仓库大规模开发奠定基础。仓库层次更加清晰,对外暴露数据更加统一。
好的数据仓库需要满足如下的特点: 稳定、可信、丰富、透明。

数仓设计需要满足功能架构、数据架构和技术架构的整体统一。

当前主流建模方法为:ER模型、维度模型。
1)ER模型
常用于oltp数据库建模,应用到构建数仓时更偏重数据整合, 站在企业整体考虑,将各个系统的数据按相似性一致性、合并处理,为数据分析、决策服务,但并不便于直接用来支持分析。
主要缺陷:需要全面梳理企业所有的业务和数据流,周期长、人员要求高。
2)维度建模
面向olap分析场景而生,针对分析场景构建数仓模型。重点关注快速、灵活的解决分析需求,同时能够提供大规模数据的快速响应性能。
不需要完整的梳理企业业务流程和数据,实施周期根据主题边界而定,容易快速实现demo,而且相对来说便于理解、提高查询性能、对称并易扩展。
作为大数据板块,数据来源更加广泛,针对的业务域也更加宽广,所以维度建模相对来说更加灵活并适用。
在讨论维度建模之前,关注数仓和BI的基本目标是非常有意义的,在做日常的数据需求的时候,经常会遇到如下几个痛点:
收集了海量数据,不知道如何去做ETL
不同来源的数据该如何去聚合
如何方便业务人员快速方便的获取数据
如何定义重要的数据指标
如何确保数据准确性
数据如何支持决策
基于上面的痛点,需要搭建一套DW/BI系统。现在市面上有很多类似的产品,例如:QuickBI、GrowingIO、神策、猛犸等等。
但是对于公司而言,适合自己的才是最好的,大部分公司选择自己搭建或者利用开源的软件(如MateBase),该系统必须满足下面的特性。
1)信息存储便捷
即能跟现在主流数据库打通,系统展现的内容必须是容易理解的,对于业务人员必须直观而且易操作。
数据结构和标示必须符合业务思维过程和词汇,用户能够以各种形式切割和分析数据,同时能够快速的将查询结果反馈。
2)指标的唯一性
系统必须以一致性的形式展现信息。也就是说数据必须是可信的,同一指标定义在不同的数据源中,所含的意义必须相同,既 同名同意性。
3)模块的低耦合
系统能够适应变化。当用户需求、业务维度需要调整的调整的时候,设计的DW模型必须能够兼容这些变化。
已经存在数据和指标不应该被破坏或修改,就算一些指标的调整,也要以适当的方式描述变化,并对用户完全透明。
4)数据安全保障
能展示的数据必须是统计的结果数据,一些详单展现和下载必须和平台的权限系统挂钩,避免数据泄漏。
5)监控系统
必须配套一个展现模块的监控系统,能够让产品方知道各个模块的使用情况,对一些访问量比较少的模块可以适当的调整和优化。

1)源事务
业务库或者日志等各个方面的数据源,一般不维护历史信息。
2)ETL
目的是构建和加载数据到展现区的目标维度模型中,划分维度和事实。
3)模型
围绕业务过程度量事件进行构建,为满足用户无法预估的需求,必须包含详细的原子数据。
为避免数据的冗余存储造成的浪费和低效,并方便多业务部门查询方便以及同一指标的数据准确性和业务的扩展性,一般采取以下的架构模式。

用于度量的事实表。事实表一般会有两个或者多个外键与维度表的主键进行关联。事实表的主键一般是组合健,表达多对多的关系。
用于描述环境的维度表。单一主键,维度表的属性是所有查询约束和报表标识的来源。维度提供数据的入口点,提供所有分析的最终标识和分组。
所以维度建模表示每个业务过程包含的事实表,事实表里面存储事件的数值化度量,围绕事实表的是多个维度表,维度表包含事件发生的实际存在的文本环境。

从图表中能看出来,维度模型(星型模型)比较简单,而且适于变化,各个维度的地位相同。可根据业务情况进行新增或者修改(只要维度的单一值已经存在事实表中)。

维度建模主要分为4个步骤:
1)选择业务过程
业务过程是通常表示的是业务执行的活动,与之相关的维度描述和每个业务过程事件关联的描述性环境。
通常由某个操作型系统支持,例如:订单系统。
业务过程建立或获取关键性能度量。
一系列过程产生一系列事实表。
2)声明粒度
粒度传递的是与事实表度量有关的细节级别。
精确定义某个事实表的每一行表示什么。
对事实表的粒度要达成共识。
3)确认维度
健壮的维度集合来粉饰事实表。
维度表示承担每个度量环境中所有可能的单值描述符。
4)确认事实
不同粒度的事实必须放在不同的事实表中。
事实表的设计完全依赖物理活动,不受最终报表的影响。
事实表通过外健关联与之相关的维度。
查询操作主要是基于事实表开展计算和聚合。
其中粒度是非常重要的,粒度用于确定事实表的行表示什么,建议从关注原子级别的粒度数据开始设计。
原子粒度能够承受无法预估的用户查询,且原子数据可以以各种可能的方式进行上卷。
事实是整个维度建模的核心,其中雪花模型、星型模型都是基于一张事实表通过外键关联维表进行扩展。
最终生成一份能够支撑可预知查询需求的模型宽表,而且最后的查询也是落在事实表中进行。
目前常见的维度模型:
星型模型
每一个维表都与都与事实表相关联。数据冗余量较大
雪花模型
有些维表可能不与事实表直接关联,而是通过其他维表关联到事实表。数据冗余量较小
星座模型
由多个事实表相组合,维表是公共的。企业中一般都是星座模型
需要注意:
1)维度表的唯一主键应该是代理键(自然键)。自然键通常具有一定的业务含义,但这些信息是有可能发生变化的,而代理健可以提高关联效率且保持业务的解耦。
2)维度表和事实表关联的每个连接应该基于无含义的整数代理键。
3)固定深度层次在维度表中应该扁平化,规范化的雪花模型不利于多属性浏览,而且大量的表和连接操作会影响性能。
4)非完全独立的维度应该合并为一个维度。例如:日维度、周维度、月维度等可以合并为一个周期维度。
维度建模是一个迭代设计过程,设计工作从总线矩阵中抽取实体级别的初始图形化模型开始,详细建模过程要深入定义、资源、关系、数据质量问题以及每张表的数据转换。
主要目标是建立满足用户需求的模型,校验可加载到模型中的数据,为ETL提供明确的方向。
我们来看看如下以客户创建为事实的售前流程的雪花模型。
事实表
客户创建信息表
维度表
销售信息表、店铺信息表、跟进表/约见表/风控通过表/订单表的维度上卷。

以上面的维度模型可以聚合出创建、跟进、风控等各个维度的上层展现的数据。
维度建模很难提供一个完整地描述真实业务实体之间的复杂关系的抽象方法。实际生产中仍然存在一些实际问题:
建模之前需要进行大量的数据预处理,导致大量的数据处理工作(ETL)。
当业务发生变化,需要重新进行维度定义。重复进行数据预处理,产生大量的数据冗余。
单纯的维度建模,不能保证数据来源的一致性和准确性;不同层级,维度建模并不完全适用。
常见公司的数仓模型架构:

首先对ETL得到的数据进行E-R(关系)建模,得到一个规范化的公司层面的数据中心库。基于中心数据库为公司各部门建立基于维度建模的数据集市。
维度建模都集中在DM层里面,针对具体业务线或者主题域,紧紧围绕着业务模型,直观反映出业务问题。
5.2 分层的误区
数仓层内部的划分不是为了分层而分层,分层是为了解决 ETL 任务及工作流的组织、数据的流向、读写权限的控制、不同需求的满足等各类问题。
业界较为通行的做法将整个数仓层又划分成了 dwd、dwb、dws、dim、mid 等等很多层。然而我们却无法说清层级间清晰的界限是什么,复杂的业务场景令我们无法真正落地执行。
所以数据分层一般来说三层是最基础的:

至于DW层如何进行切分,是根据具体的业务需求和公司场景自己去定义,一般来说需要:
分层是解决数据流向和快速支撑业务的目的
必须按照主题域和业务域进行贯穿
层级之间不可逆向依赖
确定分层规范后,后续最好都遵循这个架构,约定成俗即可
血缘关系、数据依赖、数据字典、数据命名规范等配套先行
DW分层没有最正确的,只有最适合的。
5.3 宽表的误区
所谓宽表,迄今为止没有一个明确的定义。
通常做法是把很多的维度、事实上卷或者下钻之后关联到某一个事实表中,形成一张既包含了大量维度又包含了相关事实的表。
宽表的使用,有其一定的便利性。使用方不需要再去考虑跟维度表的关联,也不需要了解维度表和事实表是什么东西。
但是宽表的使用还是需要注意:
随着业务的增长,我们始终无法预见性地设计和定义宽表究竟该冗余多少维度
同时无法清晰地定义出宽表冗余维度的底线在哪里
甚至为了满足使用的需求,要不断地将维表中已经存在的列增加到宽表中。这直接导致了宽表的表结构频繁发生变动。
通常采用的做法是:
根据主题域和业务域,将某个业务的所有节点梳理清楚
将关键节点的数据作为事实表依据,然后横向扩充其他事实表的,同时纵向的添加该节点上一些主键对应的维度
宽表的建设不依赖具体的业务需求,而是根据整体业务线相匹配;
尽量用维度建模代替宽表
为什么用维度建模代替宽表会更好呢
维度建模是以某个既定事实为依据。既然是事实表,那么这块的业务如果不变动,事实表的粒度基本不会变。
事实表和维度表解耦。维度表的变更不会影响到事实表,结果表也只需要回刷一下数据流程即可。
新增维度完全可以按照星型模型、雪花模型动态添加。
维度模型可以作为宽表的基础。一旦数据流程确定,可以通过维度模型再生成对应宽表进行快速的业务支撑。
6、扩展:实时数仓
目前不少公司都在尝试以Flink、Kudu为基础的实时数仓架构,里面的数仓分层模型和离线的数仓架构基本相同。

1)ODS原始层
存放原始数据,主要是埋点数据(日志数据)和业务操作数据(binlong),数据源主要是Mysql、HDFS、Kafka等。
2) DW数仓层
存放ETL和主题汇总之后的中间层数据,这块又分为:
DWD:数据仓库明细层,以业务过程作为建模驱动,基于每个具体的业务过程特点,构建最细粒度的明细层事实表。
DWS:数据仓库轻度汇总层,按照各个业务域进行轻度汇总成分析某一个主题域的服务数据,一般是宽表。
DIM:维度表,公共维度层,基于维度建模理念思想,建立整个业务过程的一致性维度,主要使用 MySQL、Hbase、Redis 三种存储引擎。
3)DM数据集市层
以数据域+业务域的理念建设公共汇总层,对于DM层比较复杂,需要综合考虑对于数据落地的要求以及具体的查询引擎来选择不同的存储方式,分为轻度汇总层和高度汇总层。
轻度汇总层以宽表的形式存在,主要是针对业务域进行快速方便的查询;
高度汇总层由明细数据层或轻度汇总层通过聚合计算后,产出部分实时数据指标需求,灵活性比较差,主要做大屏展现。
4)理论上上面还一APP层,应用层,主要是通过这几层之后,生成轻度或者高度汇总的数据,然后根据业务域进行接口封装提供给上层使用。
但是实时数仓面临以下几个实施关键点:
端到端数据延迟、数据流量的监控;
故障的快速恢复能力;
数据的回溯处理,系统支持消费指定时间段内的数据;
实时数据从实时数仓中查询,T+1数据借助离线通道修正;
业务数据质量的实时监控;
实时数仓架构和数据中台一样,虽然都是属于当前比较热门的概念,但是对于实时数仓的狂热追求大可不必。
首先,在技术上几乎没有难点,基于强大的开源中间件(Flink、kudu等)。技术通用。
其次,实时数仓的建设一定是伴随着业务的发展,武断的认为实时数仓架构最符合当前公司的需求是不对的,要考虑实际情况。
如何顺畅的将传统的离线数仓+实时链路处理流程升级到实时数仓架构是个很大的问题。
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我希望将Favorite模型添加到我的User和Link模型。业务逻辑用户可以有多个链接(即可以添加多个链接)用户可以收藏多个链接(他们自己的或其他用户的)一个链接可以被多个用户收藏,但只有一个所有者我对如何为这种关联建模以及在模型就位后如何创建用户收藏夹感到困惑?classUser 最佳答案 下面的数据模型怎么样:classUser:destroyhas_many:favorite_links,:through=>:favorites,:source=>:linkendclassLink:destroyhas_many:favor
我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何
我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah
我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss
我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢
我有一些非常大的模型,我必须将它们迁移到最新版本的Rails。这些模型有相当多的验证(User有大约50个验证)。是否可以将所有这些验证移动到另一个文件中?说app/models/validations/user_validations.rb。如果可以,有人可以提供示例吗? 最佳答案 您可以为此使用关注点:#app/models/validations/user_validations.rbrequire'active_support/concern'moduleUserValidationsextendActiveSupport:
对于Rails模型,是否可以/建议让一个类的成员不持久保存到数据库中?我想将用户最后选择的类型存储在session变量中。由于我无法从我的模型中设置session变量,我想将值存储在一个“虚拟”类成员中,该成员只是将值传递回Controller。你能有这样的类(class)成员吗? 最佳答案 将非持久属性添加到Rails模型就像任何其他Ruby类一样:classUser扩展解释:在Ruby中,所有实例变量都是私有(private)的,不需要在赋值前定义。attr_accessor创建一个setter和getter方法:classUs
我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案
ruby如何管理内存。例如:如果我们在执行过程中采用C程序,则以下是内存模型。类似于这个ruby如何处理内存。C:__________________|||stack|||------------------||||------------------|||||Heap|||||__________________|||data|__________________|text|__________________Ruby:? 最佳答案 Ruby中没有“内存”这样的东西。Class#allocate分配一个对象并返回该对象。这就是程序