拉链维度的区间设计
拉链维度用 start_date 与 end_date 描述每个版本的生效区间。推荐使用闭开区间 [start, end):起始日包含、结束日不包含,这样相邻版本不会在边界日期重叠,同一天只会命中一条记录。当前有效行的 end_date 建议取远期极大值(如 9999-12-31)而不是 NULL,原因是 NULL 参与比较时索引与分区裁剪往往失效,查询条件也变得难以统一书写。
基本信息
| 区间约定 | 闭开区间 [start_date, end_date) |
|---|---|
| 当前行表示 | end_date = 9999-12-31,避免使用 NULL |
| 约束 | 同一自然键的版本区间不重叠、不断档 |
| 查询写法 | 业务时间 >= start_date AND 业务时间 < end_date |
典型使用场景
- 历史归属还原:订单按下单时间关联当时生效的用户等级版本
- 当前值查询:end_date 取远期值后可用同一套条件覆盖当前与历史
- 区间完整性校验:用窗口函数检查同键版本是否存在重叠或断档
- 分区裁剪:非 NULL 的区间端点让过滤条件能走分区与索引
- 增量校验:每次加载后验证单键单日仅命中一行
区间查询与校验
区间查询与校验
-- 按业务时间取当时版本(闭开区间)
SELECT * FROM dim_user d
WHERE :dt >= d.start_date
AND :dt < d.end_date; -- 结束日不包含,避免边界重复命中
-- 校验一:同一自然键版本区间是否重叠
SELECT user_id, start_date, end_date,
lead(start_date) OVER (PARTITION BY user_id ORDER BY start_date) AS next_start
FROM dim_user;
-- 若 next_start < end_date 则存在区间重叠
-- 校验二:当前版本是否唯一
-- SELECT user_id, count(1) FROM dim_user WHERE is_current = 1
-- GROUP BY user_id HAVING count(1) > 1;区间方案对照
| 方案 | 写法 | 影响 |
|---|---|---|
| 闭开区间 | [start, end) | 相邻版本不重叠,推荐 |
| 全闭区间 | [start, end] | 边界日期命中两条,指标翻倍 |
| 当前行用 NULL | end_date IS NULL | 索引与分区裁剪失效,查询变慢 |
| 当前行取远期值 | 9999-12-31 | 条件统一,性能与可读性都好 |
区间约定必须在建模规范里写死并全局统一。混用闭开与全闭的团队,问题往往表现为偶发的金额翻倍,排查时很难联想到区间边界。
版本区间还应覆盖完全、不留空洞:某成员在两个版本之间出现日期空档时,按时间关联会整段丢失该成员的数据,且不会报错。