单个渠道CAC=该渠道广告支出总和÷该渠道首次成交且来源可追溯的独立用户数,需按订单日期对齐支出、用首次或最后触达渠道归因,并确保时区与时间粒度一致。

怎么算单个渠道的获客成本(CAC)
获客成本不是简单把广告费除以新增客户数,得先确认「谁算新客」——必须是首次成交且来源可追溯的用户。常见错误是直接用 users 表里的注册数,但注册不等于成交,真正该用的是 orders 表中 first_order_date 对应的记录。
- 确保订单表里有明确的渠道标识字段,比如
utm_source或channel_id,且该字段在用户首次下单时就已写入(不能靠后期打标) - 广告支出要按天/按渠道对齐到订单日期,不能只用月度总投放额去除——比如 5 月投了 10 万,但 6 月才转化出 200 单,这笔成本得记在 6 月
- 如果多个渠道协同触达(如先看了抖音广告,又通过微信搜索进站),需约定归因逻辑,SQL 里通常用「最后触达渠道」,即取每个用户最后一次会话的
utm_source
基础计算式:sum(ad_spend) / count(distinct user_id),但前提是 ad_spend 和 user_id 已按相同时间粒度和渠道维度对齐。
ROI 的分母为什么不能只用广告费
ROI = (收入 − 成本)/ 成本,这里的「成本」必须包含与获客强相关的可变支出,光算广告费会严重高估 ROI。实际中常漏掉三类:
- 渠道佣金:比如某宝成交扣 5%,这部分要从订单收入里扣除后再参与 ROI 计算
- 销售人力分摊:如果是电销团队专属渠道(如 400 电话),对应坐席的小时工资需按通话时长或接单量折算
- 优惠券核销成本:渠道专属券(如「小红书新人减 30」)的实际核销金额,得从
coupon_redemptions表里捞出来加进分母
典型错误是写成:sum(order_amount) / sum(ad_spend) —— 这其实是 ROAS,不是 ROI。正确写法得先算净毛利:sum(order_amount - commission - coupon_amount) / sum(ad_spend + commission + coupon_amount)。
如何处理跨渠道归因和重复计算
用户可能在 A 渠道看到广告,隔两天从 B 渠道搜索进站下单,这时 utm_source 是 B,但 A 的投放其实起了作用。硬按最后渠道分摊会导致头部渠道虚高、种草型渠道(如小红书、B 站)被低估。
- 最简方案:用「首次渠道」代替「最后渠道」,即查每个
user_id在user_sessions表里最早的非空utm_source - 进阶做法:用线性归因,需要窗口期(如 7 天内所有会话渠道均分权重),但 SQL 实现复杂,容易拖慢查询;建议先导出中间结果再用 Python/Pandas 处理
- 绝对要避免的做法:在同一个订单上同时计入多个渠道的
ad_spend,这会让总成本翻倍,ROI 崩盘
验证方法:把所有渠道 CAC 加总,应该接近公司整体 CAC;如果偏差超 15%,大概率是归因逻辑或数据口径没对齐。
MySQL 和 PostgreSQL 在时间对齐上的坑
广告花费和订单时间不在同一时区或未按天截断,会导致 JOIN 错位。比如广告数据是 UTC 时间,订单是东八区,直接 date(ad_time) = date(order_time) 会漏掉跨零点的转化。
- 统一转为业务时区再截断:
date(convert_tz(ad_time, '+00:00', '+08:00'))(MySQL)或(ad_time at time zone 'UTC' at time zone 'Asia/Shanghai')::date(PostgreSQL) - 别用
BETWEEN做时间范围匹配,它包含边界值,易重复或遗漏;改用ad_date >= order_date AND ad_date - 如果广告系统只提供汇总日报(无明细曝光点击),而订单表有精确到秒的时间戳,就不要强行按秒 JOIN,否则会爆炸式膨胀;应先聚合广告数据到日粒度再关联
最隐蔽的问题:某些渠道(如信息流)的转化窗口长达 3 天,但财务要求按订单日期记成本。这时候必须提前和业务方确认——成本归属以哪个时间为准,SQL 逻辑得跟着制度走,不是技术上能实现就行。

















