hive-拉链表

目录

  • 拉链表概述
    • 缓慢变化维
    • 拉链表定义
  • 拉链表的实现
    • 常规拉链表
      • 历史数据
      • 每日新增数据
      • 历史数据与新增数据的合并
    • 分区拉链表


拉链表概述

缓慢变化维

通常我们用一张维度表来维护维度信息,比如用户手机号码信息。然而随着时间的变化,某些用户信息会发生改变,这就是所谓的缓慢变化维。需要注意的是,这里的缓慢变化是相对事实表而言的,事实表的变化速度要快得多。

针对缓慢变化维问题,通常有以下几种处理方式:

1)仅保留每个用户最新的一条维度信息

​ 这种方法比较简单粗暴,维度只考虑最新就行,保证了维度值的唯一性。但缺点是无法查看历史信息,在需要回溯查看数据的场景就不适用了,可能需要去原始数据查询,及其不方便。

2)仅保留每个用户最初的一条维度信息

​ 这种就相当于一次填写,终身不允许修改,那么在实际关联数据时,很可能获取的是无效的维度信息。比如某个用户的手机号以及变了,但是维度表中仍然保留最初的手机号,这就导致数据关联结果是错误的。而且对于用户来说,一旦手残录入错误就无法再更改,用户的体验也是不好的。

3)用新增行的方式在维度表中同时保留所有变化的维度信息

​ 这种方式其实跟拉链表很接近了,就是用户每改一次信息,就在维度表中新增一行,只不过这里的历史数据和新增数据如何区分,以及他们的有效时间范围如何区分,就是需要着重考虑的问题了。

4)用新增列的方式在维度表中同时保留所有变化的维度信息

​ 这个方式的优势就是维度表的行数可以不变,只需要新增列,但是缺点也很明显,新增列意味着表结构会一直变化,而且也没有办法确定到底要新增几列。

拉链表定义

拉链表就是记录一个事物从开始到当前状态的变化过程的数据表,主要是用于维度发生变化的场景,也即我们常说的缓慢变化维。

比如说我们用一张维度表记录用户的手机号码,但是随着时间推进,用户可能某一天会换手机号,这时我们的维度表就需要相应的更改,这时我们就可以用拉链表来进行记录,这就实现了保留历史数据的同时,还能查询最新维度信息。可以说拉链表其实是解决缓慢变化维的最佳方案了。

一个简单的拉链表示例如下:

useridtelstart_dtend_dt
011112024010120240601
012222024060299991231
023332024010199991231

每行记录都表示一个用户的属性值以及对应的日期有效范围,如果是最新的数据,则结束日期是99991231。用户01的联系方式发生过变化,因此会有两条数据记录。

拉链表的实现

常规拉链表

历史数据

现在有一批数据如下所示,表示用户的属性值以及传回来的日期和时间戳(单位s):

with data1 as (select '01' as userid, 'ab' as addr, '20220101' as dt, 1641039513 as ts union allselect '01' as userid, 'ab' as addr, '20220103' as dt, 1641211200 as ts union allselect '01' as userid, 'cd' as addr, '20220108' as dt, 1641607200 as ts union allselect '02' as userid, 'ab' as addr, '20220101' as dt, 1641039480 as ts union allselect '02' as userid, 'bc' as addr, '20220104' as dt, 1641261600 as ts union allselect '02' as userid, 'cd' as addr, '20220109' as dt, 1641639600 as ts union allselect '03' as userid, 'ab' as addr, '20220101' as dt, 1641038400 as ts union allselect '03' as userid, 'cd' as addr, '20220101' as dt, 1641002400 as ts union allselect '03' as userid, 'ab' as addr, '20220107' as dt, 1641520800 as ts
)

历史数据的处理规则:

1)同一天仅保留最新一条数据

select userid, addr, dt, ts
from (select userid, addr, dt, tsrow_number() over (partition by userid, dt order by ts desc) rnfrom data1
) ta
where rn = 1;

2)获取每个用户每个属性最早的一条数据

with data2 as (select userid, addr, dt, tsfrom (select userid, addr, dt, ts,row_number() over (partition by userid, dt order by ts desc) rnfrom data1) tawhere rn = 1
)
select userid, addr, dt, ts
from (selectuserid, addr, dt, ts,row_number() over (partition by userid, addr order by dt) rnfrom data2
) tb
where rn = 1;

这样处理以后数据如下所示:
在这里插入图片描述

3)获取当前行的下一行日期数据并处理截止日期

这一步我们需要得到每个用户每个属性的下一行,用来获取当前属性的截止日期。截止日期的处理条件:如果为空则用99991231填充,否则就用next_dt减一天来填充。

上一步的处理结果我们放到data3中,部分代码会做省略处理:

with data3 as (select userid, addr, dt, tsfrom (selectuserid, addr, dt, ts,row_number() over (partition by userid, addr order by dt) rnfrom data2) tbwhere rn = 1
)
selectuserid, addr, dt start_dt,if(next_dt is null, '99991231', date_format(date_add(from_unixtime(unix_timestamp(next_dt, 'yyyyMMdd'), 'yyyy-MM-dd'), -1), 'yyyyMMdd')) end_dt
from (selectuserid, addr, dt, ts,lead(dt) over (partition by userid order by dt) next_dtfrom data3
) tc

得到的结果如下:
在这里插入图片描述

完整的代码如下:

with data1 as (select '01' as userid, 'ab' as addr, '20220101' as dt, 1641039513 as ts union allselect '01' as userid, 'ab' as addr, '20220103' as dt, 1641211200 as ts union allselect '01' as userid, 'cd' as addr, '20220108' as dt, 1641607200 as ts union allselect '02' as userid, 'ab' as addr, '20220101' as dt, 1641039480 as ts union allselect '02' as userid, 'bc' as addr, '20220104' as dt, 1641261600 as ts union allselect '02' as userid, 'cd' as addr, '20220109' as dt, 1641639600 as ts union allselect '03' as userid, 'ab' as addr, '20220101' as dt, 1641038400 as ts union allselect '03' as userid, 'cd' as addr, '20220101' as dt, 1641002400 as ts union allselect '03' as userid, 'ab' as addr, '20220107' as dt, 1641520800 as ts
)
, data2 as (select userid, addr, dt, tsfrom (select userid, addr, dt, ts,row_number() over (partition by userid, dt order by ts desc) rnfrom data1) tawhere rn = 1
)
, data3 as (select userid, addr, dt, tsfrom (selectuserid, addr, dt, ts,row_number() over (partition by userid, addr order by dt) rnfrom data2) tbwhere rn = 1
)
selectuserid, addr, dt start_dt,if(next_dt is null, '99991231', date_format(date_add(from_unixtime(unix_timestamp(next_dt, 'yyyyMMdd'), 'yyyy-MM-dd'), -1), 'yyyyMMdd')) end_dt
from (selectuserid, addr, dt, ts,lead(dt) over (partition by userid order by dt) next_dtfrom data3
) tc

每日新增数据

新增数据如下:

with new_data1 as (select '01' as userid, 'ab' as addr, '20220121' as dt, 1642723200 as ts union allselect '02' as userid, 'cd' as addr, '20220121' as dt, 1642723200 as ts union allselect '04' as userid, 'ef' as addr, '20220121' as dt, 1642723200 as ts union allselect '04' as userid, 'xg' as addr, '20220121' as dt, 1642723300 as ts union allselect '05' as userid, 'xy' as addr, '20220127' as dt, 1642723200 as ts
)

新增数据的处理:

1)保留最新一条数据

新增数据的处理很简单,因为一般是增量读取某一天的数据,因此我们只要保证每个用户只保留最新一条数据即可。

select userid, addr, dt, ts
from (select userid, addr, dt, ts,row_number() over (partition by userid, dt order by ts desc) rnfrom new_data1
) ta
where rn = 1

处理之后结果如下所示,可以看到每个用户只剩下了最新的一条数据:
在这里插入图片描述

2)结束日期均设置为99991231

with new_data2 as (select userid, addr, dt, tsfrom (select userid, addr, dt, ts,row_number() over (partition by userid, dt order by ts desc) rnfrom new_data1) tawhere rn = 1
)
select userid, addr, dt start_dt, '99991231' end_dt
from new_data2;

历史数据与新增数据的合并

1)历史数据与新增数据的全连接

取历史数据的开链数据(结束日期为99991231)与新增数据进行全连接:

select t1.userid old_userid, t1.addr old_addr, t1.start_dt old_start_dt, t1.end_dt old_end_dt,t2.userid new_userid, t2.addr new_addr, t2.start_dt new_start_dt, t2.end_dt new_end_dt
from (select userid, addr, start_dt, end_dtfrom history_datawhere end_dt = '99991231'
) t1
full join new_data t2
on t1.userid = t2.userid
;

全连接的结果如下:
在这里插入图片描述

2)全连接以后的条件处理

a)新旧属性相同或新旧属性不同且旧属性开始日期较大,则仅保留old数据

主要针对两种情况:

一是当新旧属性相同时,仅保留旧属性,这是因为大多数情况下旧属性的日期比较早。不过如果出现重刷数据时,可能新属性的日期早于旧属性,这时应当只保留旧属性。

二是当新旧属性不同,且旧属性的开始日期大于新属性的开始日期时,这也是发生了回刷数据的情况,此时仅保留旧属性。

selectold_userid userid, old_addr addr, old_start_dt start_dt, old_end_dt end_dt
from data_join
where old_addr = new_addr or (old_addr != new_addr and old_start_dt >= new_start_dt);

需要处理的数据是这一条:
在这里插入图片描述

b)新旧属性不同,new不为空时保留new,否则保留old

此时针对的是三种情况:

一是只有old数据则保留old数据;二是只有new数据则保留new数据;三是old与new都不为空且不相同时,仅保留new数据。

selectcoalesce(new_userid, old_userid) userid,coalesce(new_addr, old_addr) addr,coalesce(new_start_dt, old_start_dt) start_dt,coalesce(new_end_dt, old_end_dt) end_dt
from data_join
where old_addr is null or new_addr is null or (old_addr != new_addr and old_start_dt < new_start_dt);

这里处理的数据是这几条:
在这里插入图片描述

c)old与new同时不为空且不相同,保留old数据并对old数据的结束日期做处理

此时这条数据的new部分已经在第二种情形中做了保留,而old数据需要做一个闭链处理,也就是用新增数据的开始日期做填充。

selectold_userid userid,old_addr addr,old_start_dt start_dt,date_format(from_unixtime(unix_timestamp(new_start_dt, 'yyyyMMdd')-24*3600, 'yyyy-MM-dd'), 'yyyyMMdd') end_dt
from data_join
where old_addr != new_addr and old_start_dt < new_start_dt;

这里处理的是这条数据:
在这里插入图片描述

完整的代码如下:

with history_data as (select '01' as userid, 'ab' as addr, '20220101' as start_dt, '20220107' as end_dt union allselect '01' as userid, 'cd' as addr, '20220108' as start_dt, '99991231' as end_dt union allselect '02' as userid, 'ab' as addr, '20220101' as start_dt, '20220103' as end_dt union allselect '02' as userid, 'bc' as addr, '20220104' as start_dt, '20220108' as end_dt union allselect '02' as userid, 'cd' as addr, '20220109' as start_dt, '99991231' as end_dt union allselect '03' as userid, 'ab' as addr, '20220101' as start_dt, '99991231' as end_dt
)
, new_data as (select '01' as userid, 'ab' as addr, '20220121' as start_dt, '99991231' as end_dt union allselect '02' as userid, 'cd' as addr, '20220121' as start_dt, '99991231' as end_dt union allselect '04' as userid, 'xg' as addr, '20220121' as start_dt, '99991231' as end_dt union allselect '05' as userid, 'xy' as addr, '20220121' as start_dt, '99991231' as end_dt
)
, data_join as (select t1.userid old_userid, t1.addr old_addr, t1.start_dt old_start_dt, t1.end_dt old_end_dt,t2.userid new_userid, t2.addr new_addr, t2.start_dt new_start_dt, t2.end_dt new_end_dtfrom (select userid, addr, start_dt, end_dtfrom history_datawhere end_dt = '99991231') t1full join new_data t2on t1.userid = t2.userid
)
selectold_userid userid, old_addr addr, old_start_dt start_dt, old_end_dt end_dt
from data_join
where old_addr = new_addr or (old_addr != new_addr and old_start_dt >= new_start_dt)
union all
selectcoalesce(new_userid, old_userid) userid,coalesce(new_addr, old_addr) addr,coalesce(new_start_dt, old_start_dt) start_dt,coalesce(new_end_dt, old_end_dt) end_dt
from data_join
where old_addr is null or new_addr is null or (old_addr != new_addr and old_start_dt < new_start_dt)
union all
selectold_userid userid,old_addr addr,old_start_dt start_dt,date_format(from_unixtime(unix_timestamp(new_start_dt, 'yyyyMMdd')-24*3600, 'yyyy-MM-dd'), 'yyyyMMdd') end_dt
from data_join
where old_addr != new_addr and old_start_dt < new_start_dt;

最终的结果如下:
在这里插入图片描述

分区拉链表

分区拉链表其实只要将end_dt当作分区日期即可,这样每次取历史数据的开链数据与新增数据计算,得到的数据中包含了一部分99991231分区数据,一部分是新增日期分区(通常是该日期前一天)数据。之后采用动态分区写入的方式,覆盖写指定分区即可。

分区拉链表的优势:

  • 写入时只需要按分区写入,不需要全表覆盖写,当数据表的体量较大时,优势比较大;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1542732.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【软件工程】需求分析概念

一、定义 二、为什么要进行需求分析&#xff1f; 三、需求分析任务 四、与用户沟通获取需求的方法 五、分析建模 六、软件需求规格说明 例题 选择题

【题解】【枚举,数学】——小 Y 拼木棒

【题解】【枚举&#xff0c;数学】——小 Y 拼木棒 小 Y 拼木棒题目背景题目描述输入格式输出格式输入输出样例输入 #1输出 #1 提示数据规模与约定 1.题意简述2.思路解析3.AC代码 前置知识&#xff1a;排列组合&#xff0c;暴力枚举基础知识。 小 Y 拼木棒 通往洛谷的传送门 …

基于SpringBoot+Vue+MySQL的医院信息管理系统

系统展示 用户前台界面 管理员后台界面 系统背景 在当今社会&#xff0c;随着医疗服务需求的不断增长和医疗信息化的快速发展&#xff0c;提升医院管理效率和服务质量成为了医疗行业的核心需求。传统的医院管理模式面临着效率低下、资源分配不均、患者就医体验差等问题。为了应…

图像处理基础知识点简记

简单记录一下图像处理的基础知识点 一、取样 1、释义 图像的取样就是图像在空间上的离散化处理,即使空间上连续变化的图像离散化, 决定了图像的空间分辨率。 2、过程 简单描述一下图象取样的基本过程,首先用一个网格把待处理的图像覆盖,然后把每一小格上模拟图像的各个…

一种求解无人机三维路径规划的高维多目标优化算法,MATLAB代码

在无人机三维路径规划的研究领域&#xff0c;高维多目标优化算法是一个重要的研究方向。这种算法能够同时考虑多个目标&#xff0c;如航迹距离、威胁代价、能耗代价以及多无人机协同性能等&#xff0c;以实现无人机路径的最优规划。 无人机路径规划算法的研究进展表明&#xf…

中国最厉害的改名大师,颜廷利教授的名字来自于国学易经元亨利贞

颜廷利教授&#xff0c;一位源自齐鲁大地山东济南的世界级文化名人&#xff0c;他的名字背后承载着深厚的家族易学传统。在颜廷利教授的童年记忆中&#xff0c;家族长辈常以《易经》中频繁出现的“元、亨、利、贞”四字&#xff0c;寓意四季之变换&#xff0c;将这四个字分别对…

Qt_对话框QDialog的介绍

目录 1、新建项目对话框 2、非模态对话框 3、模态对话框 4、自定义对话框 5、Qt内置对话框 5.1 消息对话框QMessageBox 5.2 颜色对话框QColorDialog 5.3 文件对话框QFileDialog 5.4 字体对话框QFontDialog 5.5 输入对话框QInputDialog 结语 前言: 在Qt中&…

使用Stream实现事件流

文章目录 1 概念介绍2 使用方法3 示例代码 我们在上一章回中介绍了Flutter中的异步操作&#xff0c;本章回中将介绍Flutter中的事件流.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1 概念介绍 我们在上一章回中介绍了异步操作相关的内容&#xff0c;本章回中将介绍如何把…

51.字符串比较实例-用户登录

//已知正确的用户名和密码&#xff0c;请用程序实现模拟用户登录 //总共三次机会&#xff0c;登录之后给出相应的提示 import java.util.Scanner;public class 登录 {public static void main(String[] args) {//1.定义两个变量&#xff0c;记录正确的用户名和密码String righ…

【kaggle竞赛】毒蘑菇的二元预测题目相关信息和思路求解代码

毒蘑菇的二元预测 您提供了很多关于不同二元分类任务的资源和链接&#xff0c;看起来这些都是Kaggle竞赛中的参考资料和高分解决方案。为了帮助您更好地利用这些资源&#xff0c;这里是一些关键点的总结&#xff1a; Playground Season 4 Episode 8 主要关注的竞赛: 使用银行…

深入理解 JavaScript 三大作用域:全局作用域、函数作用域、块级作用域

一. 作用域 对于多数编程语言&#xff0c;最基本的功能就是能够存储变量当中的值、并且允许我们对这个变量的值进行访问和修改。那么有了变量之后&#xff0c;应该把它放在哪里、程序如何找到它们&#xff1f;是否需要提前约定好一套存储变量、访问变量的规则&#xff1f;答案…

【线程池】ThreadPoolExecutor应用

ThreadPoolExecutor应用 每一步的坚持与积累,都是铸就高薪和大牛的必经的修炼 哈哈,不吹牛逼了,今天来分享最近在提升中的学习总结,无论是对在职场还是求职,看完,我相信都会有些许的收获和成长 也难得过了一个悠闲点的周末,哈哈哈,一起奥利给!! 本文总纲: 1.为什么要自定义线程…

java8 常用操作案例【经典版】超赞!

目录 一 案例 1.1 对象转list 1.2 过滤对象 1.3 排序 1.4 匹配 1.5 最大值最小值 1.6 拼接字符串 1.7 求和 1.8 分组 1.9 分组求和 1.10 综合案例 一 案例 1.1 对象转list /*** author admin对象转map ; mapper层实体类获取,到业务层转换为DTO,* return void…

《python语言程序设计》2018版第8章18题几何circle2D类(中部)

第一、重新分析 第一-1、我设计的第一模式第一-1-1、遇到的逻辑分析迷雾第一-1-2、无畏挣扎后的无奈 第二-1、我就把你们两个放到一起,第二-2、我的想法 当我看到了这个2个园并且比对. 第一-1、我设计的第一模式 设计一个最抽象的Circle2D类. 这个类只包含一个x,y和circle 这个…

初始C++中的string与迭代

常用的string构造相关类的接口 string类是一个管理字符串的字符数组&#xff0c;string类的出现方便管理我们日常所遇见的&#xff0c;字符名&#xff0c;字符串等等。下面们介绍一下常见的string类接口。 string(); 默认构造&#xff0c;构造空的string类 int main() { …

深度学习电脑独显GPU占用一直0%解决方式

在系统设置里面把硬件加速GPU计划关了 然后重启 再随便跑个模型 打开任务管理器可以看到独显开始工作了 再在GPU1中将3D改成Cuda即可

Vue项目之Element-UI(Breadcrumb)动态面包屑效果 el-breadcrumb

效果预览 需要导航的页面Vue.js 最笨的方法就是在每个需要面包屑的页面中固定写好 <template><div class="example-container"><el-breadcrumb separator="/"

【Linux-基础IO】C语言文件接口回顾 系统文件概念及接口

目录 一、C语言文件接口回顾 C语言基础知识 C中文件操作示例 二、系统文件概念及接口 重定向基本理解的回顾 文件的基本概念 系统调用接口 open read write close lseek 什么是当前路径 一、C语言文件接口回顾 引言&#xff1a;我们并不理解文件&#xff01;从语…

springboot实战学习(7)(JWT令牌的组成、JWT令牌的使用与验证)

接着上篇博客的学习。上篇博客是在基本完成用户模块的注册接口的开发以及注册时的参数合法性校验的基础上&#xff0c;基本完成用户模块的登录接口的主逻辑以及提到了问题&#xff1a;"用户未登录&#xff0c;需要通过登录&#xff0c;获取到令牌进行登录认证&#xff0c;…

TypeError: a bytes-like object is required, not ‘str‘ - 完美解决方法

&#x1f680;TypeError: a bytes-like object is required, not str - 完美解决方法&#x1f4a1; &#x1f680;TypeError: a bytes-like object is required, not str - 完美解决方法&#x1f4a1;摘要引言正文1. 错误背景&#xff1a;字节与字符串的区别&#x1f440;2. 错…