用户行为分析系列实践 8 – 变化的维表
目标任务
用户事件表T结构和部分数据示例如下:
Time |
UserID |
ProductID |
Quantity |
… |
2022/6/1 10:20 |
1072755 |
1 |
7 |
… |
2022/6/1 12:12 |
1078030 |
2 |
8 |
… |
2022/6/1 12:36 |
1005093 |
3 |
3 |
… |
2022/6/1 13:21 |
1048655 |
4 |
9 |
… |
2022/6/1 14:46 |
1037824 |
5 |
5 |
… |
2022/6/1 15:19 |
1049626 |
6 |
4 |
… |
2022/6/1 16:00 |
1009296 |
7 |
6 |
… |
2022/6/1 16:39 |
1070713 |
8 |
7 |
… |
2022/6/1 17:40 |
1090884 |
9 |
4 |
… |
T表字段说明:
字段名 |
数据类型 |
字段含义 |
Time |
日期时间 |
事件发生的时间戳,精确到毫秒 |
UserID |
字符串 |
用户ID |
ProductID |
整数 |
用户购买的产品ID |
Quantity |
数值 |
用户购买的产品数量 |
维表Product:
ProductID |
ProductName |
Unit |
Origin |
ProductType |
1 |
Apple |
Pound |
Shandong |
Fruits |
2 |
Tissue |
Packs |
Guangdong |
Home&Personalcare |
3 |
Beef |
Pound |
Qingdao |
Meat |
4 |
Wine |
Bottles |
Shanxi |
Beverage |
5 |
Pork |
Pound |
Xizang |
Meat |
6 |
Bread |
Packs |
Beijing |
Bakery |
7 |
Juice |
Bottles |
Xinjiang |
Beverage |
… |
… |
… |
… |
… |
维表Product字段说明:
字段名 |
数据类型 |
字段含义 |
ProductID |
字符串 |
产品ID |
ProductName |
字符串 |
产品名称 |
Unit |
字符串 |
销售单位 |
ProductTypeID |
字符串 |
产品类别 |
Origin |
数值 |
产地 |
计算任务:
统计指定时间段内每种产品类别下每个产地的销售数量。
需要考虑的问题是,产品的产地会不定期调整,统计销售数量时要使用销售事件发生时的产品产地。
实践技能
使用时间键维表,时间键相关知识可参考:
1. 根据生产系统的维表定期生成有时间键的维表
Product.btx中要增加时间键字段,结构如下:
字段名 |
数据类型 |
字段含义 |
eTime |
日期时间 |
当前记录生效时间 |
ProductID |
字符串 |
产品ID |
ProductName |
字符串 |
产品名称 |
Unit |
字符串 |
销售单位 |
ProductTypeID |
字符串 |
产品类别 |
Origin |
数值 |
产地 |
根据生产系统中Product表每天的变更信息补充到转储后的Product.btx中。
2. 使用有时间键的维表进行统计
指定eTime和ProductID共同作为产品维表的主键,并指定eTime为时间键,T和Product关联时,使用Time和ProductID两字段关联,SPL会自动处理和时间键有关的计算逻辑,使用者不必再关心内部实现,和普通的维表用法一样即可。
示例代码
1、 转储Product.btx维表
初始数据:直接增加时间键字段
A |
|
1 |
=connect("demo").query@x("select * from Product").derive(now():eTime) |
2 |
= file("Product.btx").export@b(A1) |
更新数据:维表有更新时,重新读出完整维表,并且从已转储过的btx中读出每种产品的时间最新的记录,和新读的维表进行对比,如果数据有变化,则追加新记录,标注新的生效时间。
A |
|
1 |
=connect("demo").cursor@x("select * from Product") |
2 |
= T("Product.btx").keys@it(ProductID,eTime) |
3 |
=A1.select(cmp(~.array(),A2.find(A1.ProductID).array().to(2,))!=0) |
4 |
= A3.fetch().derive(now():eTime) |
5 |
=file("Product.btx").export@ab(A4) |
A1 读新维表,设置主键索引
A2 从集文件中读出原Product维表,取每个产品的时间最新的记录,设置主键索引
@t表示后一个键是时间键
A3 选出新维表中和原维表中同主键中最新记录(eTime最大)有不同的记录,使用find时会自动选出同一主键下最新的记录,
A4 把A3中的记录添加生效时间字段
A5 把A4追加写出到集文件
2、 关联事实表和维表,并统计
假设T.ctx已经按照前面章节介绍的办法生成,并按Time排序:
A |
|
1 |
>Product=T("Product.btx").keys@ti(ProductID,eTime) |
2 |
>start=date("2022-03-15","yyyy-MM-dd"),end=date("2022-06-16","yyyy-MM-dd") |
3 |
=file("T.ctx").open().cursor(ProductID,Quantity,Time;Time>=start && Time<=end) |
4 |
=A3.switch(ProductID:Time,Product) |
5 |
=A4.groups(ProductID.ProductType,ProductID.Origin; sum(Quantity):Quantity) |
A1 读取维表,设置主键索引的同时指定时间键
A4 事实表和维表关联,和正常写法一样,关联字段多了一个时间字段将找出该时间之前的最新(eTime小于该时间且最大的)记录
运行结果:
ProductType |
Origin |
Quantity |
Fruits |
Shandong |
1241628 |
Fruits |
Xinjiang |
546357 |
Fruits |
Hainan |
24526 |
Home&Personalcare |
Guangdong |
7411008 |
Meat |
Qingdao |
3303230 |
Meat |
Neimeng |
657546 |
Meat |
Xizang |
2456235 |
Bakery |
Beijing |
247673 |
Beverage |
Xinjiang |
3526574 |
Beverage |
Shanxi |
6090112 |
… |
… |
… |
英文版