用户埋点平台-数仓建模
1、数据仓库
1.1 数据仓库的构建
1.1.1 数据模型
数据模型就是数据组织和存储方法,它强调从业务、数据存取和使用角度合理存储数据。只有将数据有序的组织和存储起来之后,数据才能得到高性能、低成本、高效率、高质量的使用。
- 高性能:良好的数据模型能够帮助我们快速查询所需要的数据。
- 低成本:良好的数据模型能减少重复计算,实现计算结果的复用,降低计算成本。
- 高效率:良好的数据模型能极大的改善用户使用数据的体验,提高使用数据的效率。
- 高质量:良好的数据模型能改善数据统计口径的混乱,减少计算错误的可能性。
1.1.2 数仓建模理论
1.1.2.1 维度模型
维度模型将复杂的业务通过事实和维度两个概念进行呈现。事实通常对应业务过程,而维度通常对应业务过程发生时所处的环境。
注:业务过程可以概括为一个个不可拆分的行为事件,例如交易中的下单,取消订单,付款,退单等,都是业务过程。
下图为一个典型的维度模型,其中位于中心的 SalesOrder 为事实表,其中保存的是下单这个业务过程的所有记录。位于周围每张表都是维度表,包括 Date(日期),Customer(顾客),Product(产品),Location(地区)等,这些维度表就组成了每个订单发生时所处的环境,即何人、何时、在何地下单了何种产品。从图中可以看出,模型相对清晰、简洁。
维度建模以数据分析作为出发点,为数据分析服务,因此它关注的重点的用户如何更快的完成需求分析以及如何实现较好的大规模复杂查询的响应性能。
1.1.2.2 维度建模理论之事实表
事实表作为数据仓库维度建模的核心,紧紧围绕着业务过程来设计。其包含与该业务过程有关的维度引用(维度表外键)以及该业务过程的度量(通常是可累加的数字类型字段)。
事实表特点
事实表通常比较“细长”,即列较少,但行较多,且行的增速快。
事实表分类
事实表有三种类型:分别是事务事实表、周期快照事实表和累积快照事实表,每种事实表都具有不同的特点和适用场景,下面逐个介绍。
事务事实表
事务事实表用来记录各业务过程,它保存的是各业务过程的原子操作事件,即最细粒度的操作事件
设计事务事实表时一般可遵循四个步骤:选择业务过程 → 声明粒度 → 确认维度 → 确认事实
周期快照事实表
周期快照事实表以具有规律性的、可预见的时间间隔来记录事实,主要用于分析一些存量型(例如商品库存,账户余额)或者状态型(空气温度,行驶速度)指标,它们的值往往是连续的
累积快照事实表
累计快照事实表是基于一个业务流程中的多个关键业务过程联合处理而构建的事实表,如交易流程中的下单、支付、发货、确认收货业务过程
1.1.2.3 维度建模理论之维度表
维度表包含了事实表中指定属性的相关详细信息
维度表的实时更新策略:
在实时数仓中,我们不对业务数据库中的维度表进行合并,仅对一些不需要的字段进行过滤,然后将维度数据写入 HBase 的维度表中,业务数据库的维度表和 HBase 的维度表是一一对应的。
写入维度数据使用 HBase 的 Phoenix 客户端提供的 upsert 语法,实现幂等写入。当维度数据发生变化时,程序会用变化后的新数据覆盖 Phoenix 维表中相同主键的旧数据。从而保证 Phoenix 表中保存的是一份全量最新的维度数据。
通过 Flink CDC 检测维度表的实时变化
2. 数据仓库设计
2.1 数据设计
2.1.1 数据仓库设计流程
2.1.2 数据域划分
数据仓库模型设计除横向的分层外,通常也需要根据业务情况进行纵向划分数据域。
划分数据域的意义是便于数据的管理和应用。
通常可以根据业务过程或者部门进行划分,本项目根据业务过程进行划分,需要注意的是一个业务过程只能属于一个数据域。
2.1.3 构建业务总线矩阵
业务总线矩阵中包含维度模型所需的所有事实(业务过程)以及维度,以及各业务过程与各维度的关系。矩阵的行是一个个业务过程,矩阵的列是一个个的维度,行列的交点表示业务过程与维度的关系。
2.1.3.1 明确统计指标
1)指标体系相关概念
(1)原子指标
原子指标基于某一业务过程的度量值,是业务定义中不可再拆解的指标,原子指标的核心功能就是对指标的聚合逻辑进行了定义。我们可以得出结论,原子指标包含三要素,分别是业务过程、度量值和聚合逻辑。
例如订单总额就是一个典型的原子指标,其中的业务过程为用户下单、度量值为订单金额,聚合逻辑为 sum()求和。
(2)派生指标
派生指标基于原子指标,其与原子指标的关系如下图所示。
(3)衍生指标
衍生指标是在一个或多个派生指标的基础上,通过各种逻辑运算复合而成的。例如比率、比例等类型的指标。衍生指标也会对应实际的统计需求。
2.1.3.2 指标分类
主题 | 子主题 | 衍生指标/派生指标 | 派生指标/被依赖的派生指标 | 统计周期 | 统计粒度 | 原子指标 | 业务限定 | ||
---|---|---|---|---|---|---|---|---|---|
业务过程 | 度量值 | 聚合逻辑 | |||||||
流量主题 | 各渠道流量统计 | 当日各渠道独立访客数(UV) | 各窗口各版本各渠道各类别独立访客数 | 窗口 | 渠道 | 操作系统 | 版本 | 页面浏览 | device_id |
当日各渠道页面访问量 (PV) | 各窗口各版本各渠道各类别页面访问量 | 窗口 | 渠道 | 操作系统 | 版本 | 页面浏览 | page_url | ||
新老访客流量统计 | 各类页面访问数 | 各窗口各版本各渠道各类页面独立访问数 | 窗口 | 渠道 | 操作系统 | 版本 | 页面浏览 | ||
各类页面点击/关闭数 | 各窗口各版本各渠道各类页面点击/关闭数 | 窗口 | 渠道 | 操作系统 | 版本 | 页面点击 | |||
各类访客平均使用时长 | 各窗口各版本各渠道各类别应用停留时长 | 窗口 | 渠道 | 操作系统 | 版本 | online_time | |||
各窗口各版本各渠道各类别独立访客数 | 窗口 | 渠道 | 操作系统 | 版本 | |||||
当日启动次数 | 各窗口各版本各渠道独立用户启动次数 | 窗口 | 渠道 | 操作系统 | 版本 | ||||
当日人均启动次数 | 各窗口各版本各渠道独立用户启动次数 | 窗口 | 渠道 | 操作系统 | 版本 | ||||
各窗口各版本各渠道独立访客数 | 窗口 | 渠道 | 操作系统 | 版本 | |||||
用户主题 | 用户新增活跃统计 | 当日新增用户数(DNU) | 各窗口各版本各渠道注册用户数 | 窗口 | 渠道 | 操作系统 | 版本 | 用户注册 | is_new |
当日活跃用户数(DAU) | 各窗口各版本各渠道启动独立用户数 | 窗口 | 渠道 | 操作系统 | 版本 | 用户启动 | device_id | ||
当日用户留存率 | 各窗口各版本各渠道注册用户数 | 窗口 | 渠道 | 操作系统 | 版本 | 用户注册 | |||
各窗口各版本各渠道活跃用户数 | 窗口 | 渠道 | 操作系统 | 版本 | 用户浏览 | 点击 | |||
当日启动次数 | 各窗口各版本各渠道独立用户启动次数 | 窗口 | 渠道 | 操作系统 | 版本 | 用户启动 | |||
当日人均启动次数 | 各窗口各版本各渠道独立用户启动次数 | 窗口 | 渠道 | 操作系统 | 版本 | 用户启动 | |||
各窗口各版本各渠道独立访客数 | 窗口 | 渠道 | 操作系统 | 版本 | 页面浏览 | ||||
交易主题 | 用户交易统计 | 当日订单支付总额 | 各窗口各渠道各支付方式订单支付总额 | 窗口 | 渠道 | 操作系统 | 交易方式 | 商品 | 用户支付成功 |
当日订单退款总额 | 各窗口各渠道各支付方式订单退款总额 | 窗口 | 渠道 | 操作系统 | 交易方式 | 商品 | 用户退款成功 | ||
当日订单数量 | 各窗口各渠道各支付方式订单数量 | 窗口 | 渠道 | 操作系统 | 交易方式 | 商品 | 用户创建订单 | ||
3 数据仓库分层
3.1 ODS 层
采集到 Kafka 的 topic_log 和 topic_db 主题的数据即为实时数仓的 ODS 层,这一层的作用是对数据做原样展示和备份
3.2 DIM 层
DIM 层设计要点:
(1)DIM 层的设计依据是维度建模理论,该层存储维度模型的维度表。
(2)DIM 层的数据存储在 HBase 表中
DIM 层表是用于维度关联的,要通过主键去获取相关维度信息,这种场景下 K-V 类型数据库的效率较高。常见的 K-V 类型数据库有 Redis、HBase,而 Redis 的数据常驻内存,会给内存造成较大压力,因而选用 HBase 存储维度数据。
(3)DIM 层表名的命名规范为 dim_表名
3.2.1 配置表
本层的任务是将业务数据直接写入到不同的 HBase 表中,需要让程序知道流中的哪些数据是维度数据,维度数据又应该写到 HBase 的哪些表
create database note_config;
CREATE TABLE `table_process` (
`source_table` varchar(200) NOT NULL COMMENT '来源表',
`sink_table` varchar(200) DEFAULT NULL COMMENT '输出表',
`sink_columns` varchar(2000) DEFAULT NULL COMMENT '输出字段',
`sink_pk` varchar(200) DEFAULT NULL COMMENT '主键字段',
`sink_extend` varchar(200) DEFAULT NULL COMMENT '建表扩展',
PRIMARY KEY (`source_table`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `table_process`(`source_table`, `sink_table`, `sink_columns`, `sink_pk`, `sink_extend`) VALUES ('t_pay_order', 'dim_t_pay_order', 'uid,order_no,pay_order_id,way_code,amount,state,product_id,product_name,user_id,create_time,success_time', 'uid', NULL);
INSERT INTO `table_process`(`source_table`, `sink_table`, `sink_columns`, `sink_pk`, `sink_extend`) VALUES ('t_refund_order', 'dim_t_refund_order', 'uid,refund_order_no,pay_order_id,way_code,pay_amount,refund_amount,state,product_id,product_name,user_id,create_time,success_time', 'uid', NULL);
3.2 主要任务
3.2.1 接收 Kafka 数据,过滤空值数据
对 Maxwell 抓取的数据进行 ETL,有用的部分保留,没用的过滤掉。
3.2.2 动态拆分维度表功能
由于 Maxwell 是把全部数据统一写入一个 Topic 中, 这样显然不利于日后的数据处理。所以需要把各个维度表拆开处理。
在实时计算中一般把维度数据写入存储容器,一般是方便通过主键查询的数据库比如 HBase,Redis,MySQL 等。
这样的配置不适合写在配置文件中,因为这样的话,业务端随着需求变化每增加一张维度表表,就要修改配置重启计算程序。所以这里需要一种动态配置方案,把这种配置长期保存起来,一旦配置有变化,实时计算可以自动感知。这种可以有三个方案实现:
一种是用 Zookeeper 存储,通过 Watch 感知数据变化;
另一种是用 mysql 数据库存储,周期性的同步;
再一种是用 mysql 数据库存储,使用广播流。
这里选择第三种方案,主要是 MySQL 对于配置数据初始化和维护管理,使用 FlinkCDC 读取配置信息表,将配置流作为广播流与主流进行连接。
3.2.3 把流中的数据保存到对应的维度表
维度数据保存到 HBase 的表中。
3.3 DWD 层
DWD 层设计要点:
(1)DWD 层的设计依据是维度建模理论,该层存储维度模型的事实表。
(2)DWD 层表名的命名规范为 dwd_数据域_表名
3.3.1 流量域未经加工的事务事实表
主要任务
1)数据清洗(ETL)
数据传输过程中可能会出现部分数据丢失的情况,导致 JSON 数据结构不再完整,因此需要对脏数据进行过滤。
2)分流
本节将通过分流对日志数据进行拆分,生成四张事务事实表写入 Kafka
- 流量域页面浏览事务事实表
- 流量域应用启动事务事实表
- 流量域页面动作事务事实表
- 流量域应用关闭事务事实表
3.3.2 流量域独立访客事实表
1)数据清洗(ETL)
过滤页面数据中的独立访客(UV)访问记录
3.3.3 流量域页面访问事实表
1)数据清洗(ETL)
过滤页面数据中的访客(PV、停留时间)访问记录
3.3.4 流量域页面行为事实表
1)数据清洗(ETL)
过滤页面动作数据中的访客(show click close)访问记录
3.3.5 流量域应用启动事实表
过滤应用启动数据中的访客(启动次数、冷热启动次数)访问记录
3.3.6 流量域应用使用时长事实表
过滤应用启停数据中的访客使用时长访问记录(数据可能会有延迟)
3.3.7 用户域用户活跃事实表
过滤应用启动数据中的访客启动记录,记录活跃用户(设备)
3.3.8 用户域用户新增事实表
过滤应用启动数据中的访客启动记录,记录新增用户(设备)
3.4 DWS 层
3.4.1 流量域版本-渠道-平台粒度页面浏览各窗口汇总表
ClickHouse 建表语句
页面浏览统计表
CREATE database DataCore;
drop table if exists DataCore.dws_traffic_page_view_window;
create table if not exists DataCore.dws_traffic_page_view_window
(
win_start_time DateTime COMMENT '窗口起始时间',
win_end_time DateTime COMMENT '窗口结束时间',
app_ver String COMMENT '版本号' ,
os String COMMENT '操作系统',
qid String COMMENT '渠道',
group_qid String COMMENT '渠道分组',
asc_qid String COMMENT '归因渠道',
event String COMMENT '事件类型' ,
sub_event String COMMENT '事件子类型',
is_new UInt64 COMMENT '新老用户状态标记' ,
code String COMMENT '平台code' ,
uv UInt64 COMMENT '独立用户访问数' ,
pv UInt64 COMMENT '用户访问数',
during_time UInt64 COMMENT '当前窗口页面访问总时长' ,
create_time DateTime COMMENT '创建时间',
ts UInt64 COMMENT '时间戳'
) engine = MergeTree() partition by toYYYYMMDD(win_start_time)
order by (win_start_time, win_end_time,app_ver,os,qid,group_qid,asc_qid,sub_event,is_new,code);
3.4.2 流量域版本-渠道-平台粒度页面行为各窗口汇总表
ClickHouse 建表语句
用户行为 actions 日志记录表
drop table if exists DataCore.dws_traffic_actions_window;
create table if not exists DataCore.dws_traffic_actions_window
(
win_start_time DateTime COMMENT '窗口起始时间',
win_end_time DateTime COMMENT '窗口结束时间',
app_ver String COMMENT '版本号' ,
os String COMMENT '操作系统',
qid String COMMENT '渠道',
group_qid String COMMENT '渠道分组',
asc_qid String COMMENT '归因渠道',
is_new UInt64 COMMENT '新老用户状态标记' ,
code String COMMENT '平台code' ,
event String COMMENT '事件类型' ,
sub_event String COMMENT '事件子类型',
action String COMMENT '动作类型',
action_cnt UInt64 COMMENT '动作次数' ,
device_cnt UInt64 COMMENT '动作设备次数' ,
distinct_device_cnt UInt64 COMMENT '去重动作设备次数' ,
create_time DateTime COMMENT '创建时间',
ts UInt64 COMMENT '时间戳'
) engine = MergeTree() partition by (toYYYYMMDD(win_start_time))
order by (win_start_time, win_end_time,app_ver,os,qid,group_qid,asc_qid,sub_event,action,is_new,code);
3.4.3 用户域版本-渠道-平台粒度用户启动明细表
ClickHouse 建表语句
用户注册表
drop table if exists DataCore.dws_user_register_window;
create table if not exists DataCore.dws_user_register_window
(
app_ver String COMMENT '版本号',
os String COMMENT '操作系统',
qid String COMMENT '渠道',
group_qid String COMMENT '渠道分组',
asc_qid String COMMENT '归因渠道',
code String COMMENT '平台code' ,
device_id String COMMENT '设备号(用来标识用户)',
register_time DateTime COMMENT '注册时间',
create_time DateTime COMMENT '创建时间',
ts UInt64 COMMENT '时间戳'
) engine = ReplacingMergeTree(ts) partition by toYYYYMMDD(register_time)
order by (app_ver,os,qid,group_qid,asc_qid,code,device_id);
用户启动表
drop table if exists DataCore.dws_user_start_window;
create table if not exists DataCore.dws_user_start_window
(
app_ver String COMMENT '版本号',
os String COMMENT '操作系统',
qid String COMMENT '渠道',
group_qid String COMMENT '渠道分组',
asc_qid String COMMENT '归因渠道',
code String COMMENT '平台code' ,
device_id String COMMENT '设备号(用来标识用户)',
start_time DateTime COMMENT '用户启动时间',
create_time DateTime COMMENT '创建时间',
ts UInt64 COMMENT '时间戳'
) engine = ReplacingMergeTree(ts) partition by toYYYYMMDD(start_time )
order by (app_ver,os,qid,group_qid,asc_qid,code,device_id);
用户安装表
drop table if exists DataCore.dws_user_install_window;
create table if not exists DataCore.dws_user_install_window
(
win_start_time DateTime COMMENT '窗口起始时间',
win_end_time DateTime COMMENT '窗口结束时间',
app_ver String COMMENT '版本号',
os String COMMENT '操作系统',
qid String COMMENT '渠道',
group_qid String COMMENT '渠道分组',
asc_qid String COMMENT '归因渠道',
code String COMMENT '平台code' ,
install_cnt UInt64 COMMENT '安装次数',
distinct_install_cnt UInt64 COMMENT '去重安装次数',
create_time DateTime COMMENT '创建时间',
ts UInt64 COMMENT '时间戳'
) engine = MergeTree() partition by toYYYYMMDD(win_start_time)
order by (win_start_time, win_end_time, app_ver,os,qid,group_qid,asc_qid,code);
3.4.4 用户域版本-渠道-平台活跃用户各窗口汇总表
ClickHouse 建表语句
活跃用户统计表
drop table if exists DataCore.dws_user_active_window;
create table if not exists DataCore.dws_user_active_window
(
win_start_time DateTime COMMENT '窗口起始时间',
win_end_time DateTime COMMENT '窗口结束时间',
app_ver String COMMENT '版本号',
os String COMMENT '操作系统',
qid String COMMENT '渠道',
group_qid String COMMENT '渠道分组',
asc_qid String COMMENT '归因渠道',
code String COMMENT '平台code' ,
dau UInt64 COMMENT '活跃用户数' ,
start_cnt UInt64 COMMENT '启动次数',
cold_start_cnt UInt64 COMMENT '冷启动次数' ,
hot_start_cnt UInt64 COMMENT '热启动次数',
use_time UInt64 COMMENT '使用总时长' ,
create_time DateTime COMMENT '创建时间',
ts UInt64 COMMENT '时间戳'
) engine = MergeTree() partition by toYYYYMMDD(win_start_time)
order by (win_start_time, win_end_time, app_ver,os,qid,group_qid,asc_qid, code);
3.4.5 用户域版本-渠道-平台新增用户各窗口汇总表
ClickHouse 建表语句
新增用户统计表
drop table if exists DataCore.dws_user_new_window;
create table if not exists DataCore.dws_user_new_window
(
win_start_time DateTime COMMENT '窗口起始时间',
win_end_time DateTime COMMENT '窗口结束时间',
app_ver String COMMENT '版本号' ,
os String COMMENT '操作系统',
qid String COMMENT '渠道',
group_qid String COMMENT '渠道分组',
asc_qid String COMMENT '归因渠道',
code String COMMENT '平台code' ,
dnu UInt64 COMMENT '新增用户数',
start_cnt UInt64 COMMENT '启动次数',
cold_start_cnt UInt64 COMMENT '冷启动次数' ,
hot_start_cnt UInt64 COMMENT '热启动次数',
use_time UInt64 COMMENT '使用总时长',
create_time DateTime COMMENT '创建时间',
ts UInt64 COMMENT '时间戳'
) engine = MergeTree() partition by toYYYYMMDD(win_start_time)
order by (win_start_time, win_end_time, app_ver,os,qid,group_qid,asc_qid,code);
3.4.6 支付域支付方式-客户端-商品 支付订单各窗口汇总表
ClickHouse 建表语句
支付订单统计表
drop table if exists DataCore.dws_trade_order_window;
CREATE TABLE DataCore.dws_trade_order_window
(
`win_start_time` DateTime COMMENT '窗口起始时间',
`win_end_time` DateTime COMMENT '窗口结束时间',
`os` String COMMENT '操作系统',
`product_id` String COMMENT '商品id',
`way_code` String COMMENT '支付方式,ALI_APP、WX_APP、APPLE_APP',
`code` String COMMENT '平台code',
`pay_amount` Decimal64(6) COMMENT '支付金额',
`refund_amount` Decimal64(6) COMMENT '退款金额',
`tax_amount` Decimal64(6) COMMENT '扣税金额',
`total_amount` Decimal64(6) COMMENT '总收益',
`order_num` UInt64 COMMENT '订单总数量',
`device_num` UInt64 COMMENT '付费用户总数量',
`first_day_device_num` UInt64 COMMENT '首日付费用户总数量',
`first_day_pay_amount` Decimal64(6) COMMENT '首日付费用户支付金额',
`first_day_order_num` UInt64 COMMENT '首日付费用户订单总数量',
`create_time` DateTime COMMENT '创建时间',
`ts` UInt64 COMMENT '时间戳'
)ENGINE = MergeTree() PARTITION BY toYYYYMMDD(win_start_time)
ORDER BY (win_start_time,win_end_time,os,product_id, way_code, code);
索引
CREATE INDEX idx_sub_event ON DataCore.dws_traffic_actions_window (sub_event) TYPE minmax GRANULARITY 8192;
CREATE INDEX idx_sub_event ON DataCore.dws_traffic_page_view_window (sub_event) TYPE minmax GRANULARITY 8192;
3.4.7 流量域版本-渠道-平台粒度用户浏览明细表
ClickHouse 建表语句
用户浏览详情表
**drop** **table** **if** **exists** DataCore.dws_user_page_view_detail;
**create** **table** **if** **not** **exists** DataCore.dws_user_page_view_detail
(
app_ver **String** **COMMENT** '版本号' ,
os **String** **COMMENT** '操作系统',
os_version **String** **COMMENT** '操作系统版本号',
device **String** **COMMENT** '手机型号',
device_brand **String** **COMMENT** '机型品牌',
qid **String** **COMMENT** '渠道',
group_qid **String** **COMMENT** '渠道分组',
asc_qid **String** **COMMENT** '归因渠道',
is_new **UInt64** **COMMENT** '新老用户状态标记' ,
code **String** **COMMENT** '平台code' ,
device_id **String** **COMMENT** '设备号(用来标识用户)',
event **String** **COMMENT** '事件类型' ,
sub_event **String** **COMMENT** '事件子类型',
last_sub_event **String** **COMMENT** '上一个事件类型',
during_time **UInt64** **COMMENT** '页面浏览时间' ,
create_time **DateTime** **COMMENT** '创建时间',
ts **UInt64** **COMMENT** '时间戳',
lab_code **String** **COMMENT** '实验code',
lab_group_code **String** **COMMENT** '实验分组code'
) **engine** =MergeTree() **partition** **by** **toYYYYMMDD**(create_time)
**order** **by** (app_ver,os,qid,group_qid,asc_qid,is_new,code,device_id,event,sub_event,last_sub_event,create_time);
3.4.8 流量域版本-渠道-平台粒度用户动作明细表
ClickHouse 建表语句
用户动作详情表
**drop** **table** **if** **exists** DataCore.dws_user_page_action_detail;
**create** **table** **if** **not** **exists** DataCore.dws_user_page_action_detail
(
app_ver **String** **COMMENT** '版本号' ,
os **String** **COMMENT** '操作系统',
os_version **String** **COMMENT** '操作系统版本号',
device **String** **COMMENT** '手机型号',
device_brand **String** **COMMENT** '机型品牌',
qid **String** **COMMENT** '渠道',
group_qid **String** **COMMENT** '渠道分组',
asc_qid **String** **COMMENT** '归因渠道',
is_new **UInt64** **COMMENT** '新老用户状态标记' ,
code **String** **COMMENT** '平台code' ,
device_id **String** **COMMENT** '设备号(用来标识用户)',
event **String** **COMMENT** '事件类型' ,
sub_event **String** **COMMENT** '事件子类型',
action_type **String** **COMMENT** '动作类型,展现传show、点击传click、关闭传close',
create_time **DateTime** **COMMENT** '创建时间',
ts **UInt64** **COMMENT** '时间戳' ,
lab_code **String** **COMMENT** '实验code',
lab_group_code **String** **COMMENT** '实验分组code'
) **engine** = MergeTree() **partition** **by** **toYYYYMMDD**(create_time)
**order** **by** (app_ver,os,qid,group_qid,asc_qid,is_new,code,device_id,event,sub_event,action_type,create_time);