zoukankan      html  css  js  c++  java
  • 数据仓库中数据模型之拉链表

    1.数据仓库及数仓中的数据模型及操作描述

    在大数据设计与开发过程中,数据仓库是必不可少的一部分。但很多开发者将数仓理解为很多业务库和业务表的汇总集合,这是不全面的。数仓存在很多设计,架构,业务建模等多个维度的问题。关于数仓的大体介绍可以参照之前的文章(https://www.cnblogs.com/jiashengmei/p/12092691.html), 本篇只分享数据仓库中的数据模型及操作。落地到实际中就是在某些业务场景下,数仓的表怎样去设计。

    有过大数据开发经验的开发者都明白,在大数据环节,数据的操作不能像RDMS数据库那样。比如最常见的,在RDMS库中,假设是mysql数据库,对数据的单条或根据一定条件修改更新,单条或根据一定条件删除等等一些操作是再正常不过的。可能会因为数据库表记录多存在一定的性能问题。而在大数据环节,很多开发者都知道,大数据的很多数据服务组件其实不支持单条删除或者更新操作的,比如hive,clickhouse等等数据组件。虽然某些大数据组件在高版本也做到对数据进行特殊的删除修改,但是也是不建议使用的。为什么会有这样的问题或者设计?,很多大数据开发人员都清楚,在大数据开发中,往往是面对TB、PB甚至更多的数据,也会面对光一个表就可能是10亿、100亿甚至更多的条数的数据。在这种情况下,如果不按分而治之的思想去对数据进行操作,而是采用传统的RDMS数据组件的方式去操作,有一半的操作是没法进行的,同时会对数据质量,数据的一致性、完整性、时效性造成破坏,同时会对数据服务带来问题。而分而治之的设计其实就是对数据进行分区,最常见的有按天分区(T-1)模式,按小时分区(H-1)模式。在这些前提下,数仓的数据模型,表结构,及表对数据的存储和操作都需要区别于传统的RDMS单独设计。当然,如何设计一定是站在业务的基础上,只是某些设计方案和方法具备共用性。下面就举例介绍在以hive为数仓基础上的几个常用的数据模型表设计。

    2.数仓之拉链表

    以电商中的订单为例,经常会遇到这样的需求

    a.表中的部分字段会被update,最常见的如订单的状态
    b.需要查看某一个时间点或者时间段的历史快照信息,比如查看某一个订单在历史某一个时间点的状态,比如查看某一个用户在过去某一段时间内,更新过几次。
    c.变化的比例和频率不是很大,比如,总共有1000万的会员,每天新增和发生变化的有10万左右。

    举个简单例子,比如有一张订单表,6月20号有3条记录:

    订单创建日期 订单编号 订单状态
    2020-06-20 001 创建订单
    2020-06-20 002 创建订单
    2020-06-20 003 支付完成

    到6月21日,表中有5条记录:

    订单创建日期 订单编号 订单状态
    2020-06-20 001 支付完成(从创建到支付)
    2020-06-20 002 创建订单
    2020-06-20 003 支付完成
    2020-06-21 004 创建订单
    2020-06-21 005 创建订单

    可以看出从2020-06-20到2020-06-21,订单编号为001的订单状态有修改操作,订单编号为004、005是新增操作

    到6月22日,表中有6条记录:

    订单创建日期 订单编号 订单状态
    2020-06-20 001 支付完成(从创建到支付)
    2020-06-20 002 创建订单
    2020-06-20 003 已发货(从支付到发货)
    2020-06-21 004 创建订单
    2020-06-21 005 支付完成(从创建到支付)
    2020-06-22 006 创建订单

    如果按照上述方式数据存储下来,会遇到以下几个问题
    a.只保留一份全量,如当前时间为6月22号,数据如6月22日的记录一样,如果需要查看6月21日订单003的状态,则无法满足。
    b.每天都保留一份全量,即将每天的状态作全量的保留,则数据仓库中的该表共有14条记录,有记录是重复的,没有任务变化,如订单002,004,数据量大了,会造成很大的存储浪费

    基于以上问题,采用拉链表来设计订单表
    从6月20号到6月22号拉链表的记录如下::

    订单创建日期 订单编号 订单状态 dw_begin_date dw_end_date
    2012-06-20 001 创建订单 2012-06-20 2012-06-20
    2012-06-20 001 支付完成 2012-06-21 9999-12-31
    2012-06-20 002 创建订单 2012-06-20 9999-12-31
    2012-06-20 003 支付完成 2012-06-20 2012-06-21
    2012-06-20 003 已发货 2012-06-22 9999-12-31
    2012-06-21 004 创建订单 2012-06-21 9999-12-31
    2012-06-21 005 创建订单 2012-06-21 2012-06-21
    2012-06-21 005 支付完成 2012-06-22 9999-12-31
    2012-06-22 006 创建订单 2012-06-22 9999-12-31

    拉链表字段描述
    a. dw_begin_date表示该条记录的生命周期开始时间,dw_end_date表示该条记录的生命周期结束时间。
    b. dw_end_date='9999-12-31'表示该条记录目前处于有效状态。

    下面来几个查询:

    a.查询当前订单的最新有效状态
    select * from order_his where dw_end_date='9999-12-31';
    

    查询结果

    b.如果查询2020-06-21的历史快照
    select * from order_his where dw_begin_date<='2020-06-21' and dw_end_date>='2020-06-21';
    

    查询结果

    从以上结果可以看出,这样的拉链表,既能满足对历史数据的需求,又能很大程度的节省存储资源。

    3.拉链表的更新操作

    现在有如下需求:
    a.数据仓库中订单历史表的刷新频率为一天,当天更新前一天的增量数据。
    b.如果一个订单在一天内有多次状态变化,则只会记录最后一个状态的历史。
    c.订单状态包括三个:创建、支付、完成。
    d.创建时间和修改时间只取到天,如果源订单表中没有状态修改时间,那么抽取增量就比较麻烦,需要有个机制来确保能抽取到每天的增量数据。
    基于以上需求,需要创建三张表
    源系统中订单表

    CREATE TABLE orders(
    order_id string,
    create_time string,
    modified_time string,
    status string)
    ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';
    

    数仓的ODS层,有一张订单的增量数据表,按天分区,存放每天的增量数据

    CREATE TABLE t_ods_orders_inc(
    order_id string,
    create_time string,
    modified_time string,
    status string)
    PARTITIONED BY (day string)
    ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';
    

    数仓的DW层,有一张订单的历史数据拉链表,存放订单的历史状态数据

    CREATE TABLE t_dw_orders_his (
    order_id string,
    create_time string,
    modified_time string,
    status string,
    dw_start_date string,
    dw_end_date string) 
    ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';
    

    orders表在2020-08-21的全量数据

    在2020-08-22这天抽取orders全量数据到t_ods_orders_inc

    INSERT overwrite TABLE t_ods_orders_inc PARTITION (day = '2020-08-21')
    SELECT order_id,create_time,modified_time,status
    FROM orders WHERE create_time<='2020-08-21';
    

    在2020-08-22这天抽取t_ods_orders_inc全量数据到t_dw_orders_his

    INSERT overwrite TABLE t_dw_orders_his
    SELECT order_id,create_time,modified_time,status,
    create_time AS dw_start_date,
    '9999-12-31' AS dw_end_date
    FROM t_ods_orders_inc WHERE day = '2020-08-21';
    

    t_dw_orders_his数据明细如下

    因为这是第一次通过拉链表同步数据,可能看不出什么特殊的情况,现在来演示增量更新
    orders表在2020-08-22的全量数据

    在2020-08-23号只做增量同步抽取2020-08-22的数据到t_ods_orders_inc

    INSERT overwrite TABLE t_ods_orders_inc PARTITION (day = '2020-08-22')
    SELECT order_id,create_time,modified_time,status
    FROM orders
    WHERE create_time = '2020-08-22' OR modified_time = '2020-08-22';
    

    查询结果如下

    通过DW历史数据(数据日期为2015-08-21),和ODS增量数据(2020-08-22),刷新历史表
    先把数据放到一张临时表中:

    DROP TABLE IF EXISTS t_dw_orders_his_tmp;
    CREATE TABLE t_dw_orders_his_tmp AS 
    SELECT order_id,
    create_time,
    modified_time,
    status,
    dw_start_date,
    dw_end_date 
    FROM (
        SELECT a.order_id,
        a.create_time,
        a.modified_time,
        a.status,
        a.dw_start_date,
        CASE WHEN b.order_id IS NOT NULL AND a.dw_end_date > '2020-08-22' THEN '2020-08-21' ELSE a.dw_end_date END AS dw_end_date 
        FROM t_dw_orders_his a 
        left outer join (SELECT * FROM t_ods_orders_inc WHERE day = '2020-08-22') b 
        ON (a.order_id = b.order_id) 
        UNION ALL 
        SELECT order_id,
        create_time,
        modified_time,
        status,
        modified_time AS dw_start_date,
        '9999-12-31' AS dw_end_date 
        FROM t_ods_orders_inc 
        WHERE day = '2020-08-22' 
    ) x 
    ORDER BY order_id,dw_start_date;
    

    最后把临时表中数据插入历史表

    INSERT overwrite TABLE t_dw_orders_his SELECT * FROM t_dw_orders_his_tmp;
    

    最后t_dw_orders_his全表的数据如下:

    现在来几个查询,看拉链表是否能够满足基本的业务需求
    查询订单编号为001的整个状态流程

    查询订单编号为002的目前状态

    查询2020-08-21的历史快照

    说明

    a.在实际实践中,只要数据到了DW层的拉链表,其他临时相关的表就可以删除了,以便减少数据的存储
    b.实际开发设计时,上述过程是完全自动化的

    4.数仓模型中其他几种类型的表

    全量表:每天的所有的最新状态的数据
    增量表:每天的新增数据
    流水表:对于表中的每一个修改都会记录,可以用于反映实际记录的变更

  • 相关阅读:
    五大P2P平台费用一览
    警惕P2B模式
    仿照jquery封装一个自己的js库
    怎样给div增加resize事件
    美化浏览器滚动条效果
    手机端触屏手指滑动方向及拖动层
    JS中原型链中的prototype与_proto_的个人理解与详细总结(**************************************************************)
    JavaScript this用法总结(**************************************)
    js中call、apply、bind那些事2
    深刻理解JavaScript---闭包
  • 原文地址:https://www.cnblogs.com/jiashengmei/p/14139383.html
Copyright © 2011-2022 走看看