本数仓 TDM 层(主题域模型 / 画像层)的字段建模与设计约定。建模方法论见
20-数仓分层与建模.md§5;标签体系总览见23-标签体系.md;命名规则见21-命名规范.md;时间语义见26-时间语义.md。本文档按"表一节"组织,每节包含表用途 / 粒度 / 来源 / tag_code 清单 / 写入策略。
所有 TDM 表必带 etl_time TIMESTAMP + 分区 dt STRING,STORED AS ORC。
所有标签表共用 EAV 7 字段,不扩展:
| 字段 | 类型 | 说明 |
|---|---|---|
| entity_id | BIGINT | 实体 id(用户场景 = user_id) |
| tag_code | STRING | 标签编码(维度全 encode 到此字段,命名约定见 §4) |
| tag_value | STRING | 标签值(统一 STRING;数值标签如 "1500.00",下游 CAST 处理) |
| tag_type | STRING | attr / stat / rule(预留枚举值 algo 给机器学习标签) |
| confidence | DECIMAL(5,4) | 置信度(规则标签 1.0;模型标签按模型输出) |
| etl_time | TIMESTAMP | ETL 处理时间 |
| dt | STRING | 分区,按表语义取(见各表节) |
收益:schema 不绑定标签维度组合;新增维度(如 channel / device 等)只需新 tag_code 命名,不需 ALTER。
代价:维度切片需 tag_code LIKE 'usr_pref_trade_%_amt_30d' 模式匹配;数值标签聚合 / 排序需 CAST(tag_value AS DECIMAL)。
按更新周期拆表(业界主流 OneData / 字节 / 美团做法):
| 后缀 | 写入策略 | 含哪些标签 |
|---|---|---|
_d |
每天 INSERT OVERWRITE 单分区 dt=T-1 | 属性 snap + 偏好滚动窗口(30d)+ 偏好当年累计(y{当年}) |
_o |
一次性写入永远不动 | 偏好往年总额(y{往年},跨年凝固时新建一张) |
为什么分两类:
manual/backfill/,跑一次写完)| 表 | dt 锚点 | 写入 |
|---|---|---|
_d |
T-1(${dt}) |
INSERT OVERWRITE TABLE ... PARTITION (dt='${dt}') 静态单分区 |
_o |
凝固年 12-31(如 20251231) |
INSERT OVERWRITE TABLE ... PARTITION (dt='20251231') 静态单分区 |
_d sche:DS DEPENDENT 同 dt dim_usr_user_ful_d(属性源)+ 同 dt dws_usr_user_trade_1d(偏好源)_o:不挂日调度,手跑 once承载 1 期用户标签的日更部分:7 个属性 + 16 品类 × 4 偏好窗口(30d 金额/次数 + y{当年} 金额/次数)。
每个 (entity_id, tag_code) 在 dt 分区内唯一。
dim_usr_user_ful_d WHERE dt='${dt}'dws_usr_user_trade_1d WHERE dt BETWEEN $[yyyyMMdd-30] AND ${dt} 滚动聚合dws_usr_user_trade_1d WHERE dt BETWEEN '{当年}0101' AND ${dt} 滚动覆盖(1 期当年 = 2026)| tag_code | 描述 | 来源 / 算法 | tag_value 格式 |
|---|---|---|---|
usr_level |
用户等级 | dim_usr_user.member_level | 数字字符串(如 "3") |
usr_is_cert |
实名认证情况 | dim_usr_user.is_cert | "true" / "false" |
usr_sex |
性别 | dim_usr_user.sex_cert(未实名 NULL) | "M" / "F" |
usr_city |
城市 | dim_usr_user.cert_city(未实名 NULL) | 城市名 |
usr_register_time |
注册时间 | dim_usr_user.reg_create_time | yyyyMMdd |
usr_birth_month |
生日年月 | dim_usr_user.birthday_cert 派生(未实名 NULL) | yyyyMM |
usr_generation |
出生世代 | dim_usr_user.birthday_cert 派生(按 §6 切片) | 世代名("90后" / "00后" 等) |
| tag_code 模板 | 描述 | 来源 |
|---|---|---|
usr_pref_trade_{category}_amt_30d |
某品类近 30 天金额 | SUM(pay_amt_cny) WHERE dt IN [T-30, T-1] |
usr_pref_trade_{category}_cnt_30d |
某品类近 30 天次数 | SUM(pay_order_cnt) 同口径 |
usr_pref_trade_{category}_amt_y2026 |
某品类 26 年累计金额 | SUM(pay_amt_cny) WHERE dt IN [20260101, T-1](每天滚动覆盖) |
usr_pref_trade_{category}_cnt_y2026 |
某品类 26 年累计次数 | SUM(pay_order_cnt) 同口径 |
{category} 是 16 品类占位(kb/28 §3.2 DIM 已清洗):Basketball / Soccer / MLB / TCG / PTCG / 影视收藏 / 综合体育 / 综合收藏 / NFL / NHL / UFC / WWE / Tennis / Esports / F1 / Golf。
金额口径:SUM(pay_amt_cny) = Net Revenue 偏好金额(A3 锁 1 期不扣减退款;GMV = payable_amt_cny,偏好 amt 用 pay_amt_cny,见 kb/27 §2.5 / kb/29 §2.5)。
次数口径:SUM(pay_order_cnt) = COUNT(DISTINCT order_id)(A2 锁定,1 期不用份数)。
按 EAV 7 字段(同 §1.2),dt = ${dt} = T-1。
承载历年(往年)总额标签长表。1 期落地 25 年凝固(16 品类 × 金额 / 次数 = 32 个 tag_code),开发完一次性跑入永远不动;后续每年 1-1 凝固上一年累计新落 dt 分区,不分新表。
每个 (entity_id, tag_code) 在 dt 分区内唯一。每个 dt 分区对应一个凝固年(25 年 → dt='20251231',26 年 → dt='20261231',以此类推)。
dws_usr_user_trade_1d WHERE dt BETWEEN '{凝固年}0101' AND '{凝固年}1231' 一次性聚合。1 期凝固 25 年用 [20250101, 20251231]。
| tag_code 模板 | 描述 | 来源 |
|---|---|---|
usr_pref_trade_{category}_amt_y{凝固年} |
某品类该年总额 | SUM(pay_amt_cny) WHERE dt IN [该年 01-01, 12-31] |
usr_pref_trade_{category}_cnt_y{凝固年} |
某品类该年总次数 | SUM(pay_order_cnt) 同口径 |
{category} 同 §2.4 16 品类。1 期 {凝固年}=2025,落 usr_pref_trade_basketball_amt_y2025 等 32 个 tag_code。
dt='{yyyy}1231'(25 年 = dt='20251231',26 年 = dt='20261231'),永久固定manual/backfill/{yyyymmdd}_tdm_usr_tag_o_y{yyyy}.sql 一次性 SQL,不挂日调度WHERE dt IN (...) 切片即可)30 万用户 × ~5 活跃品类 × 2 度量 ≈ 300 万行 / 凝固年,ORC 压缩后 < 100 MB / 分区。N 年累积 N × 300 万行单表 OK。
| 类型 | 模板 | 示例 |
|---|---|---|
| 属性 | usr_{属性名} |
usr_sex / usr_city / usr_generation |
| 偏好 | usr_pref_{主题}_{category}_{metric}_{window} |
usr_pref_trade_basketball_amt_30d / usr_pref_trade_mlb_cnt_y2025 |
所有维度(品类 / 窗口 / 度量)全部 encode 到 tag_code。下游按维度切片用 tag_code LIKE 'usr_pref_trade_%_amt_30d' 等模式匹配。
{window} 取值约定:
snap(当前快照,T-1 的属性)30d(近 30 天滚动 [T-30, T-1])y{yyyy}(按年累计 / 总额;当年 = _d 表滚动,往年 = _o 表凝固)每年 1-1 凝固上一年累计为 tdm_usr_tag_o 新 dt 分区(不新建表):
| 时点 | 动作 |
|---|---|
| 2027-01-01 | tdm_usr_tag_o 新落 dt='20261231' 分区(凝固 26 年累计);tdm_usr_tag_d 移除 y2026 偏好 tag_code + 新增 y2027 tag_code |
| 2028-01-01 | tdm_usr_tag_o 新落 dt='20271231' 分区;同理 |
_o 表数据永久保留。下游想看历年总额按 WHERE dt IN ('20251231', '20261231', ...) 或 dt='{凝固年}1231' 查 tdm_usr_tag_o 一张表。
按 tdm.md 7 个属性,业务侧未给精确口径前先用合理默认(源字段已在 kb/28 §2 确认);调字段不动 schema(EAV 收益)。
| tag_code | 默认口径 | 待业务确认 |
|---|---|---|
usr_level |
member_level |
业务库另有 level 字段,两字段语义区别待澄清;1 期取 member_level |
usr_is_cert |
is_cert 布尔 → "true" / "false" |
是否区分"未实名 / 审核中 / 已实名"3 态?1 期 2 态 |
usr_sex |
sex_cert(M / F),未实名 NULL |
未实名是否回退取自填 sex?1 期 cert 一源 |
usr_city |
cert_city,未实名 NULL |
未实名是否走 login_addr / register_addr?1 期 cert_city 一源 |
usr_register_time |
reg_create_time → DATE_FORMAT 'yyyyMMdd' |
是否要月 / 周粒度?1 期日粒度 |
usr_birth_month |
birthday_cert → DATE_FORMAT 'yyyyMM',未实名 NULL |
是否只取月(MM)?1 期 yyyyMM 留年信息 |
usr_generation |
birthday_cert 派生 10 年切片:60 前 / 60 后 / 70 后 / 80 后 / 85 后 / 90 后 / 95 后 / 00 后 / 05 后 / 10 后 |
切片粒度(5 年 / 10 年)?切片名(中文"90后" / 英文"1990s")?1 期中文 N 后 |
tdm_usr_profile_ful_d:1 期标签未完善(业界规范长表稳定后再做宽表,见 kb/23 §3.2),2 期再评估tdm_usr_crowd_ful_d:1 期不做圈选tag_type 预留枚举值,1 期无实际数据;将来接 RFM / 用户生命周期预测等 ML 标签时启用