FactTable = (F, D, M, T) 的物理含义:
公式:
∏ d ∈ D σ t = T ( F ⋈ M ( d ) ) \prod_{d \in D} \sigma_{t=T}(F \bowtie M(d)) d∈D∏σt=T(F⋈M(d))
分步解读:
SELECT *
FROM fact_sales
JOIN dim_time ON fact_sales.date_key = dim_time.date_key
WHERE dim_time.full_date BETWEEN '2023-01-01' AND '2023-01-31'
SELECT product_category, SUM(amount)
类型 | 数学特征 | 业务示例 | 聚合方式 |
---|---|---|---|
完全可加 | 满足加法交换律 | 销售数量 | SUM() |
半可加 | 仅在特定维度可加 | 账户余额 | AVG()/LAST_VALUE() |
不可加 | 违反基本运算规则 | 转化率 | 分子分母分别聚合 |
案例演示:
/* 错误做法:直接求平均转化率 */
SELECT AVG(conversion_rate) -- 错误!违反不可加性原则
/* 正确做法:先聚合分子分母 */
SELECT SUM(orders)/SUM(visitors) AS true_cvr
def validate_granularity(fact_table, dimensions):
# 生成候选键:维度组合+时间戳
candidate_key = dimensions + ['event_time']
# 验证记录数是否等于分组数
group_count = fact_table.groupby(candidate_key).ngroups
total_count = len(fact_table)
if group_count == total_count:
print("✅ 粒度验证通过")
else:
raise Exception(f"❌ 发现{total_count - group_count}条重复记录")
执行示例:
# 测试数据
dimensions = ['store_id', 'product_id']
sales_data = [
{'store_id':1, 'product_id':'A', 'event_time':'2023-10-01 10:00', 'qty':2},
{'store_id':1, 'product_id':'A', 'event_time':'2023-10-01 10:00', 'qty':3} # 重复粒度
]
validate_granularity(pd.DataFrame(sales_data), dimensions)
# 输出:Exception: ❌ 发现1条重复记录
3NF在事实表中的特殊处理:
-- 允许的计算列(反范式化)
CREATE TABLE fact_orders (
order_id INT,
unit_price DECIMAL(10,2),
quantity INT,
total_price DECIMAL(10,2) GENERATED ALWAYS AS (unit_price * quantity)
);
/* 优点:
1. 避免每次查询重复计算
2. 保证数据一致性
*/
SCD类型4特点:
CREATE TABLE fact_sales (
...
product_sk_current INT,
product_sk_historical INT
);
HBase行键设计策略:
// 组合键结构:反转时间戳 + 维度哈希
RowKey = reverse(timestamp) + MD5(store_id|product_id).substring(0,8)
/* 优势:
1. 反转时间实现按时间倒序查询
2. 哈希值保证数据分布均衡
*/
CONSTRAINT chk_ttl CHECK (
(trade_type = 'SPOT' AND settlement_date = trade_time::DATE + 2)
OR
(trade_type = 'FUTURES' AND settlement_date > trade_time::DATE)
)
业务规则验证:
assert settlement == trade_date + timedelta(days=2)
assert settlement > trade_date
class FactValidator:
def check_temporal(self):
# 验证时间逻辑:事件时间不晚于当前时间
now = datetime.now()
invalid = self.df[self.df['event_time'] > now]
return len(invalid) == 0
def check_orphans(self):
# 验证维度外键有效性
valid_dim_ids = dim_table['sk'].tolist()
invalid = self.df[~self.df['dim_sk'].isin(valid_dim_ids)]
return len(invalid) == 0
编码方式 | 适用场景 | 压缩比 |
---|---|---|
RLE_DICTIONARY | 低基数维度(如性别) | 10:1 |
DELTA_BINARY_PACKED | 时间戳/自增ID | 20:1 |
BYTE_STREAM_SPLIT | 浮点型指标(如金额) | 3:1 |
# 云原生存储策略
storage_policy:
hot_data:
retention: 7d
storage: AWS io2 Block Express
compression: LZ4
access_tier: 纳秒级延迟
warm_data:
retention: 90d
storage: Azure Premium SSD
compression: ZSTD
access_tier: 毫秒级延迟
cold_data:
retention: 10y
storage: Google Coldline
compression: Brotli
access_tier: 秒级延迟
解决方案对比:
方法 | 优点 | 缺点 |
---|---|---|
位图索引 | 快速布尔运算 | 更新代价高 |
字典编码 | 减少存储空间 | 需要维护映射表 |
降维处理 | 提升查询速度 | 可能丢失细节信息 |
实施步骤:
SELECT COUNT(DISTINCT user_id) FROM fact_table; -- 1,234,567
# 生成映射表
user_ids = df['user_id'].unique()
id_map = {id: idx for idx, id in enumerate(user_ids)}
df['user_id_encoded'] = df['user_id'].map(id_map)
assert df['user_id'].nunique() == df['user_id_encoded'].max() + 1
下期预告:《维度表基础》
互动话题:你在学习SQL时遇到过哪些坑?欢迎评论区留言讨论!
️温馨提示:我是[随缘而动,随遇而安], 一个喜欢用生活案例讲技术的开发者。如果觉得有帮助,点赞关注不迷路