电商数据2026数仓建设

在电商数据仓库建设与OLAP分析中,指标体系是连接底层日志与上层报表的语义层。本文不聊业务价值,只谈指标口径定义、技术拆解与SQL/Python实现逻辑,面向数仓开发与数据分析师。


一、数据采集与事件模型(埋点基座)

一切指标源于事件。APP/H5/Web端埋点日志落库时,至少携带以下原子字段:

复制代码
{
  // 系统演示、API调用控制台:http://console.open.onebound.cn/console/?i=NewRookie
  "event_id": "string",         // 全局唯一ID
  "user_id": "string",          // 登录态ID,未登录置空
  "device_id": "string",        // 设备指纹
  "session_id": "string",       // 会话ID(后端生成,30分钟超时切割)
  "event_type": "string",       // 枚举:page_view, click, add_cart, submit_order, pay_success
  "page_url": "string",
  "product_sku_id": "string",
  "referrer": "string",         // 站内外来源
  "event_time": "bigint",       // 毫秒级时间戳
  "extra_params": "map<string,string>" // 透传业务参数
}

会话切割逻辑(Hive UDF或Flink KeyedProcessFunction):

  • 同一device_id下,相邻事件间隔>30分钟则划归新session_id

  • 跨天会话按自然日切割(00:00强制断开会话),否则"访问次数(Visit)"口径会膨胀。


二、网站流量指标(Web/Mobile Web)

流量域为最上游,计算时必须区分设备ID与用户ID ,未登录用户用device_id代理。

2.1 UV / PV / Visit 的 Hive 计算

复制代码
-- 日粒度 UV (独立访客)
SELECT 
    dt,
    COUNT(DISTINCT device_id) AS uv
FROM dwd_event_log
WHERE event_type = 'page_view' 
  AND dt = '${bizdate}'
GROUP BY dt;

-- PV (页面浏览量) 直接计数
SELECT 
    dt,
    COUNT(1) AS pv
FROM dwd_event_log
WHERE event_type = 'page_view'
GROUP BY dt;

-- Visit (访问次数) 需依赖预处理好的 session_id
SELECT 
    dt,
    COUNT(DISTINCT session_id) AS visit_cnt
FROM dws_session_agg  -- 预聚合会话宽表
WHERE dt = '${bizdate}';

2.2 跳出率与退出率(重点讲口径)

跳出(Bounce) :指该session_id下仅包含1个page_view事件,且无任何交互事件(click、add_cart等)。

sql

复制代码
-- 跳出率计算
WITH bounce_sessions AS (
    SELECT 
        session_id,
        COUNT(CASE WHEN event_type = 'page_view' THEN 1 END) AS pv_cnt,
        COUNT(CASE WHEN event_type IN ('click','add_cart','pay') THEN 1 END) AS action_cnt
    FROM dwd_event_log
    WHERE dt = '${bizdate}'
    GROUP BY session_id
    HAVING pv_cnt = 1 AND action_cnt = 0
)
SELECT 
    COUNT(DISTINCT b.session_id) / COUNT(DISTINCT e.session_id) AS bounce_rate
FROM dwd_event_log e
LEFT JOIN bounce_sessions b ON e.session_id = b.session_id;

退出率(Exit Rate) :针对特定页面。若用户最后一条page_view落在该页面,则计为退出。

sql

复制代码
-- 页面退出率
WITH last_page AS (
    SELECT 
        session_id,
        page_url,
        ROW_NUMBER() OVER (PARTITION BY session_id ORDER BY event_time DESC) AS rn
    FROM dwd_event_log
    WHERE event_type = 'page_view'
)
SELECT 
    page_url,
    COUNT(CASE WHEN rn = 1 THEN 1 END) AS exit_cnt,
    COUNT(1) AS page_pv,
    COUNT(CASE WHEN rn = 1 THEN 1 END) / COUNT(1) AS exit_rate
FROM last_page
GROUP BY page_url;

坑点:跳出率分母为"落地页访问量",分子为"落地上即跳出的会话量";退出率分母为该页面的总PV。两者切勿混淆。


三、交易域指标(订单与商品)

交易域事实表通常设计为事务事实表,一条记录对应一个订单明细(SKU粒度)。

sql

复制代码
-- 事实表结构示例
CREATE TABLE dwd_order_detail (
    dt STRING COMMENT '分区日期',
    order_id STRING,
    sku_id STRING,
    user_id STRING,
    sku_price DECIMAL(18,2),   -- 成交单价
    sku_quantity INT,
    order_status STRING,       -- 枚举:已支付、已取消、已退款、已妥投
    pay_amount DECIMAL(18,2),  -- 该SKU分摊后的实付金额
    coupon_discount DECIMAL(18,2),
    freight DECIMAL(18,2)
);

3.1 GMV、销售额与有效订单金额(必须严打口径)

  • GMV(网站成交总金额) :包含所有状态 (已支付、未支付、取消、拒收、退货)的订单金额总和。SUM(sku_price * sku_quantity)

  • 商品销售总额 :仅统计已妥投状态(或至少已支付未退款)的金额。

  • 平均订单金额(AOV)有效订单总金额 / 去重后的有效订单数(注意用户粒度与订单粒度的差异)。

sql

复制代码
-- 严格过滤有效状态(业务侧需定义状态机)
SELECT 
    dt,
    SUM(sku_price * sku_quantity) AS gmv,
    SUM(CASE WHEN order_status IN ('PAID','DELIVERED','FINISHED') 
             THEN pay_amount ELSE 0 END) AS valid_sales
FROM dwd_order_detail
GROUP BY dt;

3.2 客单价与件单价

客单价(Per Customer Transaction)容易与AOV混淆,技术口径

  • AOV = 总销售额 / 订单数(Order粒度)

  • 客单价 = 总销售额 / 购买用户数(User粒度),即 SUM(pay_amount) / COUNT(DISTINCT user_id)

3.3 购物车放弃率(漏斗监控)

漏斗步骤:浏览→加购→结算→支付。

sql

复制代码
-- 每日加购->支付转化(基于用户/设备去重)
WITH funnel AS (
    SELECT 
        device_id,
        MAX(CASE WHEN event_type = 'add_cart' THEN 1 ELSE 0 END) AS has_cart,
        MAX(CASE WHEN event_type = 'pay_success' THEN 1 ELSE 0 END) AS has_pay
    FROM dwd_event_log
    WHERE dt = '${bizdate}'
    GROUP BY device_id
)
SELECT 
    SUM(has_cart) AS cart_uv,
    SUM(CASE WHEN has_cart = 1 AND has_pay = 0 THEN 1 ELSE 0 END) AS abandon_uv,
    SUM(CASE WHEN has_cart = 1 AND has_pay = 0 THEN 1 ELSE 0 END) / SUM(has_cart) AS cart_abandon_rate
FROM funnel;

四、会员域指标(生命周期与RFM)

会员分析依赖累积宽表(用户维度快照表),每日更新。

4.1 RFM 模型的 Python 实现(离线批量打标)

RFM(Recency, Frequency, Monetary)是经典用户分层算法。取近90天数据。

python

复制代码
import pandas as pd
import numpy as np

def calc_rfm(df: pd.DataFrame) -> pd.DataFrame:
    """
    df 字段: user_id, order_date, pay_amount
    需预先过滤有效订单
    """
    # 计算 Reference Date(以最新分区日期为准)
    ref_date = df['order_date'].max()
    
    rfm_df = df.groupby('user_id').agg({
        'order_date': lambda x: (ref_date - x.max()).days,  # R: 最近一次距今的天数
        'order_id': 'count',                                 # F: 订单频次
        'pay_amount': 'sum'                                  # M: 总消费金额
    }).reset_index()
    
    rfm_df.columns = ['user_id', 'R', 'F', 'M']
    
    # 基于分位数打分 (1-5分),也可使用业务自定义阈值
    for col in ['R', 'F', 'M']:
        # R 值越小越好,需反转打分
        if col == 'R':
            rfm_df[f'{col}_score'] = pd.qcut(rfm_df[col].rank(method='first'), 5, labels=[5,4,3,2,1])
        else:
            rfm_df[f'{col}_score'] = pd.qcut(rfm_df[col], 5, labels=[1,2,3,4,5])
            
    # 拼接RFM总得分或分层标签(如 555 为高价值)
    rfm_df['rfm_score'] = rfm_df['R_score'].astype(str) + rfm_df['F_score'].astype(str) + rfm_df['M_score'].astype(str)
    return rfm_df

4.2 留存率(Cohort Analysis)SQL实现

以用户首次购买月份为同期群,追踪后续各月复购率。

sql

复制代码
WITH first_order AS (
    SELECT 
        user_id,
        DATE_FORMAT(MIN(order_date), 'yyyy-MM') AS cohort_month
    FROM dwd_order_detail
    WHERE order_status IN ('PAID','FINISHED')
    GROUP BY user_id
),
user_orders AS (
    SELECT 
        o.user_id,
        DATE_FORMAT(o.order_date, 'yyyy-MM') AS order_month,
        f.cohort_month
    FROM dwd_order_detail o
    JOIN first_order f ON o.user_id = f.user_id
    WHERE o.order_status IN ('PAID','FINISHED')
    GROUP BY o.user_id, DATE_FORMAT(o.order_date, 'yyyy-MM'), f.cohort_month
)
SELECT 
    cohort_month,
    order_month,
    COUNT(DISTINCT user_id) AS active_users,
    FIRST_VALUE(COUNT(DISTINCT user_id)) OVER (PARTITION BY cohort_month ORDER BY order_month) AS cohort_size,
    COUNT(DISTINCT user_id) / FIRST_VALUE(COUNT(DISTINCT user_id)) OVER (PARTITION BY cohort_month ORDER BY order_month) AS retention_rate
FROM user_orders
GROUP BY cohort_month, order_month
ORDER BY cohort_month, order_month;

五、仓储与供应链指标(滞后性指标计算)

仓储指标依赖库存变动事实表周期快照事实表

5.1 库存周转天数(需关联销售速度)

公式:库存周转天数 = (当前可用库存金额 / 过去30天平均销售成本) * 30

代码实现(Hive):

sql

复制代码
SELECT 
    sku_id,
    current_inventory_amount,  -- 当前库存成本
    COALESCE(avg_30d_sales_cost, 0.01) AS avg_sales_cost,  -- 防止除零
    (current_inventory_amount / COALESCE(avg_30d_sales_cost, 0.01)) * 30 AS inventory_turnover_days
FROM dwd_inventory_snapshot i
LEFT JOIN (
    SELECT 
        sku_id,
        AVG(sales_cost) AS avg_30d_sales_cost
    FROM dwd_order_detail
    WHERE dt BETWEEN DATE_SUB('${bizdate}', 30) AND '${bizdate}'
      AND order_status = 'FINISHED'
    GROUP BY sku_id
) s ON i.sku_id = s.sku_id
WHERE i.dt = '${bizdate}';

5.2 缺货率(分母口径差异)

缺货率存在两种口径:

  1. 订单视角因库存不足导致缺货的订单数 / 总订单数(需业务打标缺货标记)。

  2. SKU视角缺货SKU数 / 在售SKU总数(定时任务巡检库存水位,若current_inventory < 安全库存则标记缺货)。

推荐技术方案:在订单submit环节实时校验库存,若校验失败则将order_status置为OUT_OF_STOCK,离线ETL直接统计该状态占比即可。


六、物流配送指标(状态流转监控)

物流指标的本质是订单履约状态机的时间差分析

sql

复制代码
-- 订单履约时效明细(小时粒度)
SELECT 
    order_id,
    (pay_time - submit_time) / 3600 AS pay_time_gap,        -- 支付时效
    (deliver_time - pay_time) / 3600 AS warehouse_process,  -- 仓库出库时效
    (sign_time - deliver_time) / 3600 AS shipping_duration -- 在途时长
FROM dwd_order_lifecycle  -- 该表由Flink CDC同步订单状态变更日志生成
WHERE dt = '${bizdate}';

满载率 属于物流运力调度指标,需要车辆任务表(配送任务明细),计算逻辑为:SUM(实际件数或体积) / SUM(车辆核定载量),此处不赘述。


七、数据质量校验(防止指标异动)

7.1 同环比阈值监控(基于Z-Score)

每日指标产出后,需跑自动化巡检:

python

复制代码
import numpy as np
from scipy import stats

def detect_anomaly(metric_series, current_val, window=30, z_threshold=3.0):
    """
    metric_series: 近N天历史值列表
    """
    mean = np.mean(metric_series)
    std = np.std(metric_series)
    if std == 0:
        return abs(current_val - mean) > 0.1 * mean  # 标准差为零时改用波动百分比
    z_score = (current_val - mean) / std
    return abs(z_score) > z_threshold

7.2 漏斗一致性校验(防埋点丢失)

校验公式:上一环节UV >= 下一环节UV。若加入购物车UV > 商品详情页UV,则触发告警,原因通常是埋点上报时机混乱(如加购事件在未浏览详情页时也能触发)。


八、总结:数仓分层对应关系

数据域 ODS层 DWD层(明细) DWS层(汇总) ADS层(应用)
流量 原始日志 事件明细表 会话聚合表、日活宽表 跳出率、漏斗转化
交易 业务订单表 订单明细事实表 用户日交易汇总 GMV、客单价、复购率
仓储 库存快照表 库存变动事实表 SKU日周转汇总 缺货预警、滞销清单
会员 用户信息表 登录/注册日志 用户累积特征宽表 RFM分层、留存Cohort

开发规范 :所有指标必须附带口径说明字段(是否去重、是否过滤退款、时间窗口定义),写入指标字典元数据中心,避免"一个GMV十个数"的窘境。