4. ETL/数据仓库层权限实现:数据分层中的权限标记字段设计、ETL过程中权限标签的注入、事实表与维度表的权限关联、缓慢变化维(SCD)下的权限处理
好,我们进入第四章节。前面几章我们聊了权限模型和需求分析,说白了都是在「纸上谈兵」。真正到了数据仓库这一层,才是硬仗。
我记得刚带团队做第一个行级权限项目时,大家觉得「不就是加个字段吗?」结果上线第一周,某大区经理看到了隔壁大区的销售数据,差点闹到CEO那里去。问题出在哪?就出在ETL环节——权限标签没跟着数据一起走。
这一章,我带你从数据分层的视角,把权限标记字段的设计、注入、关联,以及最头疼的缓慢变化维处理,一个一个拆开讲清楚。
4.1 数据分层中的权限标记字段设计
先问一个问题:权限标记字段应该放在哪一层?ODS?DWD?DWS?还是ADS?
我的建议是:越早注入越好,但只在需要的地方保留。
核心原则:权限标记字段在 ODS 层完成原始标签接入,在 DWD 层完成标准化和清洗,在 DWS 层完成聚合降维,在 ADS 层只保留最终用于过滤的字段。
具体来说,我习惯这样设计各层的权限字段:
| 数据分层 | 权限字段设计 | 说明 |
|---|---|---|
| ODS(源数据层) | org_code_raw, dept_id_raw |
保留源系统原始组织编码,不做任何转换 |
| DWD(明细层) | org_id, dept_id, region_id |
标准化为统一编码体系,建立组织层级关系 |
| DWS(汇总层) | org_id, region_id |
按汇总粒度保留必要的权限维度,减少冗余 |
| ADS(应用层) | auth_org_list(数组) |
预计算用户可访问的组织列表,加速查询 |
你可能会问:「为什么不在ODS层就标准化?」嗯,这里有个坑。我曾经遇到一个项目,源系统的组织编码是字符串 '01001',但另一个源系统是数字 1001。如果在ODS层就强行统一,一旦源系统回滚或数据重抽,你会丢失原始信息。所以,ODS层只做「接入」,不做「改造」。
4.2 ETL过程中权限标签的注入
权限标签怎么注入?说白了,就是在ETL的映射逻辑里,把用户/组织的权限属性「粘」到数据行上。
我常用的做法是:在DWD层的ETL脚本中,通过一张「组织权限映射表」做左连接。
-- 示例:DWD层注入权限标签
INSERT INTO dwd_sales_order (
order_id, product_id, amount,
org_id, dept_id, region_id, auth_level
)
SELECT
s.order_id,
s.product_id,
s.amount,
o.org_id,
o.dept_id,
o.region_id,
CASE
WHEN o.org_level = 'HQ' THEN 5
WHEN o.org_level = 'REGION' THEN 4
WHEN o.org_level = 'DEPT' THEN 3
ELSE 2
END AS auth_level
FROM ods_sales_order s
LEFT JOIN dim_organization o
ON s.source_org_code = o.source_org_code;
小技巧:我习惯在ETL脚本里单独写一个 -- === AUTH TAG INJECTION === 的注释段,把权限注入逻辑和业务逻辑分开。这样后续排查权限问题时,一眼就能找到关键代码。
这里要注意一个细节:权限标签的注入时机。是在数据入仓时注入,还是在数据出仓时注入?
我的经验是:入仓时注入基础权限标签,出仓时计算最终权限。举个例子,入仓时我们只标记「这条数据属于哪个组织」,出仓时再根据用户角色计算「这个用户能看到哪些组织的数据」。这样设计的好处是——ETL过程不依赖用户上下文,保持幂等性。
4.3 事实表与维度表的权限关联
事实表和维度表的权限关联,是行级权限落地的核心难点。为什么?因为权限往往挂在维度表上,但过滤条件要作用在事实表上。
我画了一张图,帮你理清这个关系:
从这张图你能看到,权限过滤的路径是:用户权限表 → 组织维度表 → 事实表。而不是直接去过滤事实表。
为什么?因为维度表有层级关系。举个例子,如果一个用户能看到「华东大区」的数据,你需要在维度表里找到华东大区下所有子组织(上海、江苏、浙江...),然后再去事实表里过滤这些组织的数据。
注意:千万不要在事实表上直接写 WHERE org_id IN ('SH', 'JS', 'ZJ')。一旦组织层级发生变化,你的SQL就得跟着改。正确的做法是:先通过维度表展开组织树,再关联事实表。
4.4 缓慢变化维(SCD)下的权限处理
终于到了最头疼的部分——SCD。为什么头疼?因为权限和维度一样,是会变的。
我遇到过这样一个场景:某销售经理在2023年1月负责华东区,3月调到了华南区。那么,他应该看到华东区1-2月的数据吗?还是只能看到华南区3月以后的数据?
这就涉及到SCD类型的选择了。我总结了一张表:
| SCD类型 | 权限处理方式 | 适用场景 | 我踩过的坑 |
|---|---|---|---|
| SCD1(覆盖) | 权限直接覆盖,历史不可查 | 组织名称变更,但归属关系不变 | 用户投诉「我上个月的数据怎么没了」 |
| SCD2(新增行) | 权限按时间区间生效,事实表关联时取对应版本 | 组织架构调整,人员调动 | 忘记处理「生效日期为空」的边界情况 |
| SCD3(新增列) | 保留当前和历史两个权限字段 | 只需要「当前」和「上一个」两个版本 | 列数膨胀,维护成本高 |
我个人最推荐的是SCD2。为什么?因为它能完整保留权限的历史状态。
具体怎么做?看这段SQL:
-- SCD2 下的权限关联查询
SELECT
f.order_id,
f.amount,
o.org_name,
o.region_name
FROM dwd_sales_order f
JOIN dim_organization_scd2 o
ON f.org_id = o.org_id
AND f.order_date BETWEEN o.effective_date
AND COALESCE(o.expiry_date, '9999-12-31')
JOIN user_auth u
ON o.org_id IN (
SELECT org_id FROM user_org_access
WHERE user_id = :current_user_id
AND effective_date <= :query_date
AND (expiry_date IS NULL OR expiry_date >= :query_date)
);
避坑指南:我曾经在SCD2的权限表里忘记处理 expiry_date IS NULL 的情况,结果导致当前生效的权限记录被过滤掉了。记住:当前生效的记录,过期时间要么是NULL,要么是一个未来的大日期。
还有一个细节:权限维度表本身的SCD处理。比如,一个组织从「销售部」改名为「商务拓展部」,但组织ID没变。这时候,事实表里的 org_id 不需要变,但维度表里的 org_name 需要按SCD2处理。这样,查询历史报表时,显示的是当时的组织名称,而不是现在的。
嗯,说到这,我想起一个真实案例。某次审计发现,2022年的报表里显示的组织名称是「华东事业部」,但2023年的报表里变成了「华东大区」。财务总监问:「这两个是不是同一个组织?」我们查了SCD2的历史记录,确认是同一个组织,只是改了名。如果没有SCD2,这种问题根本解释不清。
所以,我的建议是:权限相关的维度表,一律使用SCD2。虽然存储成本高一点,但换来的是可追溯、可审计、可解释。在数据安全领域,这三点比什么都重要。