简介:《数据中台实战:手把手教你搭建数据中台》电子书面向企业管理者、产品经理、运营设计开发人员及数据领域学习者,聚焦数据中台从概念认知到落地搭建的完整路径,帮助读者判断企业是否需要中台,并掌握采集、存储、打通、应用四个核心环节。压缩包为PDF格式,仅含1个文件,体积约11.3MB,便于在电脑或移动设备上阅读与检索。目前已有402人学习下载,具备一定关注度。书中结合作者在大型企业参与中台从0到1建设的实战经历,围绕数据埋点、指标管理、分层建模、标签平台、用户与商品流量交易分析、自助分析平台及自动化营销等模块展开,配有较多案例与方法论,可帮助读者理解中台建设的适用条件、实施流程和常见陷阱,也为中台产品经理及企业数据化转型项目提供可操作的实践参考。
1. 先别急着买引擎:这份大数据中台资料该在什么阶段翻开
很多团队一上大数据中台,先买计算引擎、再拉埋点,最后发现指标口径对不上,看板越做越多,决策却越来越慢。这份数据中台实战资料的价值不在概念,而在它把采集、存储、打通、应用四段拆成了能照着做的工序:埋点表怎么设计、ODS 到 ADS 怎么分层、标签平台怎么从宽表长出来、交易看板怎么给不同角色用。适合正在做数据化转型的产品、运营、开发和准备面试数据中台岗位的人;不适合只有一条产品线、数据量还没起来的初创团队。读的时候别从第一页顺读,先拿自己公司的一条业务线做对照,缺哪段补哪段。
2. 数据采集:埋点表、业务库同步与 T-1 链路的落地拆解
采集做不扎实,后面所有分层建模都是空中楼阁。资料里反复强调业务中台和数据中台是两条腿:前者把通用业务模块收口,后者把跨产品线数据收口;没有业务中台也能做数据中台,但采集阶段要多跑几条产品线做调研,沟通成本会高很多。落到执行,采集不是“把日志丢进数仓”这么简单,至少要同时管住三件事:埋点事件有没有统一 schema、业务库同步有没有可重跑的 T-1 链路、采集结果有没有验收标准。
2.1 埋点事件表先定 schema,再谈采集
埋点最容易翻车的地方是事件名和属性名各写各的:同一个“提交订单”,A 产品线叫submit_order,B 产品线叫order_commit,属性里一个用order_id,一个用orderNo。资料里的做法是先定事件命名规范,再让开发按表填报。常见命名是“业务域_对象_动作”,比如trade_order_submit、goods_detail_view、cart_add_click;属性分公共属性和业务属性,公共属性由 SDK 统一带上,业务属性由埋点方案声明。事件表不要一上来就宽表化,先保证一行一个事件、事件 ID 可去重、时间字段统一到服务端时间。
-- 埋点事件明细表:按天分区,properties 存业务属性 CREATE TABLE IF NOT EXISTS ods_user_event ( event_id STRING COMMENT '事件唯一ID,前端生成+服务端补发', event_name STRING COMMENT '事件名,域_对象_动作', user_id STRING COMMENT '登录用户ID,未登录为空', anonymous_id STRING COMMENT '设备匿名ID,未登录时必填', session_id STRING COMMENT '会话ID', page_id STRING COMMENT '页面ID', module_id STRING COMMENT '模块ID', platform STRING COMMENT '端:web/app/mini', app_version STRING COMMENT '客户端版本', server_ts BIGINT COMMENT '服务端接收时间戳,毫秒', properties STRING COMMENT 'JSON属性,限制2KB以内', dt STRING COMMENT '分区日期 yyyy-MM-dd' ) PARTITIONED BY (dt) STORED AS PARQUET;这段建表语句的关键不是语法,而是把“可去重”和“可分区”提前写进 schema。event_id用来处理网络重试导致的重复上报;user_id和anonymous_id至少有一个不为空,否则后续无法做用户行为串联;server_ts不要用客户端时间代替,客户端时间容易被改;properties限制大小,避免有人把整个商品对象塞进埋点。分区字段dt按服务端接收日期落,跨天补发的事件要单独标记,否则日报会对不上。
埋点方案评审时,我一般会强迫产品把下面这张表填完,填不完就不排期。事件名、触发时机、必填属性、属性类型、责任人、验收指标,这六列缺一不可。尤其是“触发时机”,要写到“点击提交按钮且接口返回成功后上报”,不能写“用户下单时”。很多转化率异常不是数仓算错,而是前端在按钮点击时就上报,接口失败也算了一笔。
| 字段 | 示例 | 约束 |
|---|---|---|
| 事件名 | trade_order_submit | 小写字母+下划线,最长 48 字符 |
| 触发时机 | 提交订单接口返回成功 | 不能写“用户下单时” |
| 必填属性 | order_id、pay_amount、goods_id | 空值率超过 1% 阻断发布 |
| 属性类型 | pay_amount为 decimal(16,2) | 金额统一单位:元 |
| 责任人 | 某业务线产品负责人 | 变更需同步数据团队 |
| 验收指标 | 日事件量、UV、空值率、重复率 | 发布后 24 小时看板核对 |
埋点验收不要靠人眼看日志,直接跑 SQL 看四个数:事件量、去重后事件量、必填属性空值率、event_id重复率。下面这段查询按事件名聚合,适合放进每日调度,超过阈值就告警。参数上,dt取业务日期,event_name可以先限定核心转化事件,不用全量跑;重复率阈值我一般设 0.5%,空值率设 1%,超过就回滚 SDK 版本或修埋点方案,不要先去改数仓清洗逻辑。
-- 埋点验收:核心事件量、空值率、重复率 SELECT event_name, COUNT(1) AS pv, COUNT(DISTINCT event_id) AS pv_dedup, ROUND(1 - COUNT(DISTINCT event_id) / COUNT(1), 4) AS dup_rate, ROUND(SUM(CASE WHEN user_id IS NULL AND anonymous_id IS NULL THEN 1 ELSE 0 END) / COUNT(1), 4) AS id_null_rate, ROUND(SUM(CASE WHEN properties IS NULL OR properties = '' THEN 1 ELSE 0 END) / COUNT(1), 4) AS prop_null_rate FROM ods_user_event WHERE dt = '${biz_date}' AND event_name IN ('trade_order_submit', 'goods_detail_view', 'cart_add_click') GROUP BY event_name;提示:埋点验收不通过时,先回滚 SDK 版本,再查网络重试和页面曝光重复上报,不要直接改数仓逻辑把异常藏掉。
2.2 业务库同步与日志采集的 T-1 链路
业务库数据是交易、订单、商品、用户这些“结果数据”,埋点日志是浏览、点击、加购这些“过程数据”。资料里把采集链路分成两条:业务库走实时同步加 T-1 全量校对,日志走消息队列落地后按天分区。常见做法是业务库变更用 binlog 追增量,每天凌晨再跑一次全量或增量校对,防止漏数;日志采集在前端埋点后先落消息队列,再离线落到对象存储或分布式文件系统,按dt分区。两条链路最后都收敛到 ODS 层,表名和责任人登记到元数据表里。
调度脚本不要写成“一把梭”,至少把业务日期、任务名、重跑标记、失败告警四个参数留出来。下面是一个通用 T-1 同步脚本骨架,job_config指向同步任务配置,biz_date由调度系统传入,rerun用来控制是否覆盖分区。脚本里先做源表行数探查,再执行同步,最后写一条元数据日志;这样出问题时能查到是源表空了,还是同步任务失败了。
#!/bin/bash set -euo pipefail BIZ_DATE="${1:?业务日期不能为空,格式 yyyy-MM-dd}" JOB_CONFIG="${2:?同步任务配置不能为空}" RERUN="${3:-false}" # 1. 同步前探查源表行数,结果写入临时文件 python /opt/sync/bin/check_source.py \ --config "${JOB_CONFIG}" \ --biz-date "${BIZ_DATE}" \ --output "/tmp/source_count_${BIZ_DATE}.json" # 2. 执行同步,rerun=true 时覆盖目标分区 python /opt/sync/bin/run_job.py \ --config "${JOB_CONFIG}" \ --biz-date "${BIZ_DATE}" \ --mode "overwrite" \ --rerun "${RERUN}" # 3. 写元数据日志:任务名、业务日期、源行数、目标行数、状态 python /opt/sync/bin/write_meta.py \ --job "${JOB_CONFIG}" \ --biz-date "${BIZ_DATE}" \ --status "success"参数说明上,BIZ_DATE是业务日期,不是执行日期;很多团队凌晨跑数时把执行日期当业务日期,导致 T-1 数据错位一天。RERUN只在补数时传true,日常调度传false,避免误覆盖。同步任务配置里要明确源表、目标表、主键、增量字段、分区字段、字段映射;主键用于去重,增量字段用于追变更,分区字段用于重跑。日志采集侧则要在 ODS 建外部表或分区表,按dt挂载,不要每天改表结构。
采集完成后必须登记元数据,不然三个月后没人知道ods_order是谁负责、几点更新、下游有谁用。资料里把中台团队拆成十类角色,从负责人、产品、数据开发、分析师到测试和运维,采集阶段至少要有业务责任人、数据开发责任人、运维责任人三方签字。元数据表不用复杂,下面这几列就够用:表名、来源系统、更新频率、SLA、主键、责任人、下游任务。缺元数据的表,我一般不允许进 DWD,宁可先卡在 ODS。
| 元数据项 | 示例 | 用途 |
|---|---|---|
| 表名 | ods_order | 唯一标识 |
| 来源系统 | 某业务线订单库 | 排查源端问题 |
| 更新频率 | 每日 T-1,09:00 前 | 约定 SLA |
| 主键 | order_id | 去重与对账 |
| 责任人 | 某数据开发 | 变更联系人 |
| 下游任务 | dwd_trade_order | 影响面分析 |
2.3 采集验收:量、质、时三层卡点
采集验收要卡三层:量、质、时。量看日环比和周同比,核心事件量突然掉 30% 或涨 50% 都要查;质看主键重复、必填字段空值、枚举值越界;时看 SLA,比如每天 09:00 前 ODS 分区必须就绪,晚了就触发告警。资料里很多实战案例都指向一个结论:采集阶段放过的小问题,到应用层会变成大问题。比如订单表里混入测试订单,交易看板会多算;埋点里platform出现unknown,流量分析会少一块;日志补发没有标记,日报和实时看板会对不上。
采集验收 SQL 可以固定成模板,每个 ODS 表接进来时改表名和主键即可。下面这段检查主键重复、分区行数、关键字段空值,结果输出到质量表,调度系统根据status决定是否阻断下游。参数上,pk换成实际主键,dt用业务日期,min_rows设一个业务可接受的下限,比如订单表日行数不低于 1000,低于就告警而不是直接阻断,避免节假日误杀。
-- 采集质量检查:主键重复、行数下限、关键字段空值 SELECT 'ods_order' AS table_name, '${biz_date}' AS dt, COUNT(1) AS row_cnt, COUNT(1) - COUNT(DISTINCT order_id) AS pk_dup_cnt, SUM(CASE WHEN order_id IS NULL THEN 1 ELSE 0 END) AS pk_null_cnt, SUM(CASE WHEN pay_amount IS NULL THEN 1 ELSE 0 END) AS amount_null_cnt, CASE WHEN COUNT(1) < 1000 THEN 'WARN' WHEN COUNT(1) - COUNT(DISTINCT order_id) > 0 THEN 'FAIL' WHEN SUM(CASE WHEN order_id IS NULL THEN 1 ELSE 0 END) > 0 THEN 'FAIL' ELSE 'PASS' END AS status FROM ods_order WHERE dt = '${biz_date}';这一层做完,采集才算有“黑匣子”可查。后面指标对不上时,先看 ODS 行数和主键,再看 DWD 清洗规则,最后才怀疑汇总逻辑。顺序反了,会浪费很多时间。
3. 存储与计算:ODS 到 ADS 的分层建模和指标口径怎么落表
分层建模最怕做成表名游戏:ODS、DWD、DWS 建了一堆,业务一问“昨天支付金额为什么和财务对不上”,还是答不出来。资料里的分层不是照搬概念,而是把“原始数据、清洗明细、主题汇总、应用结果”分开,让每层只干一件事。ODS 保留原样,方便回溯;DWD 做清洗、去重、统一字段;DWS 按主题做轻度汇总;ADS 面向看板和接口。层级越往下越稳定,越往上越贴近业务,变更频率也越高。
3.1 分层边界:每层只做该做的事
ODS 层不要做复杂清洗,尤其不要在这里过滤测试订单或修改金额,否则回溯时找不到原始值。DWD 层负责把 ODS 的脏数据变成可信明细:去重、过滤测试账号、统一金额单位、补全维度、解析 JSON 属性。DWS 层按主题汇总,比如交易主题按日、品类、渠道汇总支付金额和退款金额;用户主题按日汇总活跃、下单、复购。ADS 层直接服务看板和接口,可以宽表化,但要在指标口径固定之后再做。
选型上,离线链路优先保证可重跑和可对账,实时链路只承接真正需要秒级响应的场景。资料里把采集、存储、打通、应用串起来,但并没有鼓励所有表都实时化。交易看板 T+1 足够,实时大屏才需要流式计算;标签平台大部分标签 T+1 更新,只有“最近 1 小时活跃”这类标签走实时。分层表建议统一按天分区,小文件多的任务加合并步骤,冷数据按保留周期归档,避免存储成本失控。
| 层级 | 职责 | 典型表 | 更新频率 | 禁止事项 |
|---|---|---|---|---|
| ODS | 原样落地、可回溯 | ods_order、ods_user_event | 每日/实时 | 不做业务过滤 |
| DWD | 清洗、去重、统一口径 | dwd_trade_order | 每日 | 不直接出看板 |
| DWS | 主题汇总、轻度聚合 | dws_trade_day | 每日 | 不堆过多维度 |
| ADS | 看板、接口、人群包 | ads_trade_board | 每日/按需 | 不反写明细 |
注意:指标口径变更必须留版本,否则历史看板会集体失真。DWD 的过滤条件一改,DWS 和 ADS 都要重跑,不能只改最上层。
3.2 指标管理体系:一个指标一条 YAML
指标口径对不上,根因通常不是 SQL 写错,而是同一个词在不同团队嘴里含义不同。资料里强调指标要有业务口径、技术口径、计算周期、计算方式、责任人。我的做法是一个指标一条 YAML,进 Git 管理,变更走评审。指标码用“域_主题_度量_周期”,比如trade_order_pay_amt_1d;业务口径写清楚“支付成功订单金额,扣除退款,不含测试订单”;技术口径写清楚源表、过滤条件、去重主键、金额单位;维度列出常用切分,比如日期、品类、渠道、产品线。
metric_code: trade_order_pay_amt_1d name: 日支付订单金额 biz_caliber: 支付成功订单金额,扣除当日退款,不含测试订单和风控拦截订单 tech_caliber: > dwd_trade_order 按 order_id 去重, pay_status = 'PAID', order_type <> 'TEST', pay_amount 单位为元,保留两位小数 owner: 某交易数据负责人 period: 1d dimensions: - dt - category_id - channel_id - product_line source_table: dwd_trade_order filter: pay_status = 'PAID' AND order_type <> 'TEST' version: v1.2这份 YAML 的关键是“可执行”。biz_caliber不是写给领导看的,而是给开发和分析师对照的;tech_caliber要能直接翻译成 SQL;dimensions决定 DWS 建哪些汇总维度;version让历史看板能追到口径版本。指标变更时不要直接改原文件,新增版本并保留旧版本,ADS 表按版本号切换。否则一个“支付金额”改了口径,历史报表全部对不上,业务方会直接不信数据。
指标管理还有一条血泪经验:不要让每个看板自己写 SQL 算指标。看板只消费 ADS 或指标服务,指标服务背后统一走 YAML 定义。这样新看板接入时不用重新讨论口径,直接选指标码和维度即可。自助分析平台也一样,业务人员可以拖拽维度,但指标必须是注册过的,不能让他们在页面上随便写sum(pay_amount)。
3.3 从 ODS 到 ADS 的 SQL 骨架
先看 DWD 清洗。下面这段 SQL 从 ODS 订单表取数,按order_id去重,过滤测试订单,统一金额单位,补上品类和渠道维度。参数上,dt是业务日期,row_number()按update_time倒序取最新一条,处理业务库同一订单多次变更;pay_amount如果源表是分,要除以 100,这里假设源表已经是元。测试订单的识别条件用order_type <> 'TEST',不要用订单号前缀猜,规则要写在指标 YAML 里。
-- DWD 订单明细:去重、过滤测试单、统一金额单位 INSERT OVERWRITE TABLE dwd_trade_order PARTITION (dt = '${biz_date}') SELECT order_id, user_id, product_line, category_id, channel_id, pay_status, pay_amount, refund_amount, create_time, update_time FROM ( SELECT order_id, user_id, product_line, category_id, channel_id, pay_status, CAST(pay_amount AS DECIMAL(16,2)) AS pay_amount, CAST(refund_amount AS DECIMAL(16,2)) AS refund_amount, create_time, update_time, ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY update_time DESC) AS rn FROM ods_order WHERE dt = '${biz_date}' AND order_type <> 'TEST' ) t WHERE rn = 1;DWS 汇总按日、品类、渠道、产品线聚合,支付金额和退款金额分开算,最后在 ADS 里做净额。这样退款政策变化时不用重跑 DWD,只改汇总逻辑。下面这段 SQL 的pay_amt只统计支付成功订单,refund_amt统计当日退款,net_amt是净额。参数上,dt分区对齐,category_id和channel_id允许为空,空值单独归到“未知”维度,不要直接丢掉,否则大盘对不上。
-- DWS 交易日汇总:按品类、渠道、产品线 INSERT OVERWRITE TABLE dws_trade_day PARTITION (dt = '${biz_date}') SELECT '${biz_date}' AS dt, product_line, COALESCE(category_id, 'UNKNOWN') AS category_id, COALESCE(channel_id, 'UNKNOWN') AS channel_id, COUNT(DISTINCT CASE WHEN pay_status = 'PAID' THEN order_id END) AS pay_order_cnt, SUM(CASE WHEN pay_status = 'PAID' THEN pay_amount ELSE 0 END) AS pay_amt, SUM(CASE WHEN refund_amount > 0 THEN refund_amount ELSE 0 END) AS refund_amt, SUM(CASE WHEN pay_status = 'PAID' THEN pay_amount ELSE 0 END) - SUM(CASE WHEN refund_amount > 0 THEN refund_amount ELSE 0 END) AS net_amt FROM dwd_trade_order WHERE dt = '${biz_date}' GROUP BY product_line, COALESCE(category_id, 'UNKNOWN'), COALESCE(channel_id, 'UNKNOWN');ADS 看板表可以直接从 DWS 取数,按角色拆指标。领导层看大盘净额、同比环比;运营看品类、渠道、退款率;产品看转化漏斗。下面这段只做一张交易看板样例,net_amt和refund_rate是核心指标。参数上,dt用业务日期,refund_rate分母为 0 时要返回 0,避免除零;同比环比可以在调度里另算,不要在看板查询时实时算,否则数据量大时页面会卡。
-- ADS 交易看板:大盘核心指标 INSERT OVERWRITE TABLE ads_trade_board PARTITION (dt = '${biz_date}') SELECT dt, SUM(pay_amt) AS pay_amt, SUM(refund_amt) AS refund_amt, SUM(net_amt) AS net_amt, SUM(pay_order_cnt) AS pay_order_cnt, CASE WHEN SUM(pay_amt) = 0 THEN 0 ELSE ROUND(SUM(refund_amt) / SUM(pay_amt), 4) END AS refund_rate FROM dws_trade_day WHERE dt = '${biz_date}' GROUP BY dt;跑完还要做对账。至少查三项:DWD 主键是否重复、DWS 汇总金额是否等于 DWD 明细金额、ADS 展示金额是否等于 DWS 汇总金额。下面这段对账 SQL 输出差异行,差异不为 0 就阻断下游看板发布。参数上,${biz_date}同一天,金额比较允许 0.01 的浮点误差,超过就查清洗规则或汇总维度是否丢数。
-- 交易链路对账:DWD 明细 vs DWS 汇总 SELECT d.dt, d.pay_amt AS dwd_pay_amt, s.pay_amt AS dws_pay_amt, d.pay_amt - s.pay_amt AS diff_amt FROM ( SELECT dt, SUM(pay_amount) AS pay_amt FROM dwd_trade_order WHERE dt = '${biz_date}' AND pay_status = 'PAID' GROUP BY dt ) d LEFT JOIN ( SELECT dt, SUM(pay_amt) AS pay_amt FROM dws_trade_day WHERE dt = '${biz_date}' GROUP BY dt ) s ON d.dt = s.dt WHERE ABS(COALESCE(d.pay_amt,0) - COALESCE(s.pay_amt,0)) > 0.01;4. 数据打通:标签平台从宽表到人群圈选的最小闭环
标签平台不是把用户属性拼成一张大宽表就完事,真正难的是身份映射和标签更新节奏。资料里把标签平台放在“打通”环节,是因为用户行为数据和业务数据往往存在不同产品线、不同系统里:行为数据知道用户点了什么,业务数据知道用户买了什么,两边对不上,用户画像就是残缺的。打通的目标不是把所有字段都塞进一张表,而是让“用户是谁、干了什么、买了什么”能在同一个用户 ID 下被查询和圈选。
4.1 标签体系先分三层:基础、行为、业务
标签体系建议先分三层:基础标签、行为标签、业务标签。基础标签是注册天数、会员等级、所在城市等级这类变化慢的属性;行为标签是近 30 天点击品类数、近 7 天访问频次、最近一次活跃时间;业务标签是首单金额、近 90 天订单数、退款次数。每类标签都要有元数据:标签码、名称、类型、更新方式、源表、责任人、有效期。不要一上来做几百个标签,先做 20 个业务真用的,跑通“宽表生成—标签计算—人群圈选—营销调用”闭环。
宽表按天分区,只放常用标签,长尾标签按需计算。下面建表语句把用户 ID、基础标签、行为标签、业务标签放在一张日分区表里,dt是标签日期,user_id是主键。参数上,标签列允许为空,空值代表“未知”而不是“否”;更新方式用全量覆盖,避免增量 merge 写错导致标签漂移。宽表字段不要无限扩,超过 100 列就拆成基础宽表和行为宽表,否则查询和跑批都会变慢。
-- 用户标签宽表:按天分区,常用标签放一张表 CREATE TABLE IF NOT EXISTS dws_user_tag_wide ( user_id STRING COMMENT '统一用户ID', register_days INT COMMENT '注册天数', member_level STRING COMMENT '会员等级', last_active_time STRING COMMENT '最近活跃时间', click_category_cnt_30d INT COMMENT '近30天点击品类数', visit_cnt_7d INT COMMENT '近7天访问频次', first_order_amt DECIMAL(16,2) COMMENT '首单金额', order_cnt_90d INT COMMENT '近90天订单数', refund_cnt_90d INT COMMENT '近90天退款次数', dt STRING COMMENT '标签日期 yyyy-MM-dd' ) PARTITIONED BY (dt) STORED AS PARQUET;标签计算不要每个标签一条独立任务,按源表分组批量算。行为标签从 DWD 事件表算,业务标签从 DWD 订单表算,基础标签从用户维度表算,最后按user_id全外连接。连接时注意空值:行为表里没有行为的用户也要保留,否则宽表只剩活跃用户,圈选“沉默用户”就圈不出来。标签有效期也要写进元数据,比如“近 30 天点击品类数”每天重算,“会员等级”每天同步,过期标签要标记不可用。
4.2 身份映射:打通产品线数据的硬骨头
跨产品线打通最难的是身份映射。同一个用户在产品线 A 用手机号注册,在产品线 B 用设备号浏览,在 product_line C 用会员 ID 下单,如果没有映射表,三个 ID 就是三个人。常见做法是建一张身份映射表,字段包括统一用户 ID、手机号哈希、设备 ID、会员 ID、来源产品线、最近活跃时间、分区日期。优先级一般是会员 ID 高于手机号哈希,手机号哈希高于设备 ID;冲突时按最近活跃时间保留,人工可修正。手机号只存哈希,不落明文,设备 ID 也要做脱敏。
-- 身份映射表:多产品线 ID 归一到 union_id CREATE TABLE IF NOT EXISTS dim_user_identity ( union_id STRING COMMENT '统一用户ID', user_id STRING COMMENT '产品线内用户ID', phone_hash STRING COMMENT '手机号哈希,不存明文', device_id STRING COMMENT '设备ID,脱敏后存储', member_id STRING COMMENT '会员ID', product_line STRING COMMENT '来源产品线', last_active STRING COMMENT '最近活跃时间', dt STRING COMMENT '分区日期' ) PARTITIONED BY (dt) STORED AS PARQUET;映射逻辑分两步:先按强 ID 配对,手机号哈希相同或会员 ID 相同就归到同一个union_id;再用设备 ID 补弱关联,但设备 ID 冲突多,不能单独作为唯一依据。同一设备多账号、同一手机号多产品线都要保留映射明细,不能直接覆盖。下面这段 SQL 是归一的骨架,ROW_NUMBER()按last_active倒序取最近一条,COALESCE按优先级选 ID。参数上,dt用映射日期,phone_hash要统一哈希算法和盐值,否则跨产品线对不上。
-- 身份归一:强ID优先,设备ID兜底 INSERT OVERWRITE TABLE dim_user_identity PARTITION (dt = '${biz_date}') SELECT CONCAT('U', MD5(CONCAT(COALESCE(phone_hash, member_id, device_id), '${salt}'))) AS union_id, user_id, phone_hash, device_id, member_id, product_line, last_active, '${biz_date}' AS dt FROM ( SELECT user_id, phone_hash, device_id, member_id, product_line, last_active, ROW_NUMBER() OVER ( PARTITION BY COALESCE(phone_hash, member_id, device_id) ORDER BY last_active DESC ) AS rn FROM ods_user_identity WHERE dt = '${biz_date}' ) t WHERE rn = 1;提示:身份映射表一定要保留来源产品线和最近活跃时间,后续
本文还有配套的精品资源,点击获取