转化率计算应优先用SUM(is_converted)替代COUNT,分母须过滤脏数据,GROUP BY需匹配分析粒度,除零用NULLIF防护,多表JOIN前应先聚合防重复计数。

转化率公式里 COUNT 和 SUM 怎么选?
转化率 = COUNT(转化事件) / COUNT(曝光或点击事件),但实际表结构常把「是否转化」存为 is_converted(0/1)字段。这时候用 SUM(is_converted) 比 COUNT(CASE WHEN is_converted = 1 THEN 1 END) 更简洁、可读性更高,且避免漏掉 NULL 导致计数偏少。
-
COUNT(column) 会自动忽略 NULL,但若字段本身允许 NULL 且未显式过滤,可能把未埋点的记录也计入分母
-
SUM(is_converted) 要求 is_converted 是整型 0/1;如果它是布尔类型(如 PostgreSQL 的 BOOLEAN),得先转成整数:SUM(CASE WHEN is_converted THEN 1 ELSE 0 END)
- 分母必须是有效曝光或点击量,不能直接用
COUNT(*),否则包含测试流量、机器人请求等脏数据
GROUP BY 维度错位导致转化率失真
广告数据常需按 campaign_id、adset_id、date 等多维聚合,但容易漏掉关键维度或加了冗余维度。例如:在按 campaign_id 计算时,却把 user_id 也放进 GROUP BY,结果每行只有一条记录,转化率全是 0% 或 100%。
- 正确做法是先确认分析目标:是看计划粒度表现?那就只
GROUP BY campaign_id,并在 SELECT 中明确写出所有非聚合字段
- 若需下钻到小时级,注意
created_at 字段是否带时区;用 DATE(created_at AT TIME ZONE 'UTC') 或 DATE_TRUNC('hour', created_at)(PostgreSQL)保证时间对齐
- MySQL 用户注意:
ONLY_FULL_GROUP_BY 模式下,SELECT 中所有非聚合字段都必须出现在 GROUP BY 中,否则报错
NULL 和除零错误怎么安全处理?SUM(clicks) 可能为 0(比如新上线广告还没流量),直接除会导致数据库报错或返回 NULL(取决于方言)。别依赖应用层兜底,SQL 层就要拦截。
- 推荐写法:
ROUND(SUM(conversions) * 100.0 / NULLIF(SUM(clicks), 0), 2)
-
NULLIF(SUM(clicks), 0) 把分母为 0 的情况转成 NULL,整个表达式结果也为 NULL,不会崩溃
- 如果希望显示 0.00% 而不是 NULL,可套一层
COALESCE(..., 0),但要注意这会掩盖“无点击”的业务异常,建议保留 NULL 并在 BI 工具中单独标注
JOIN 多表时重复计数怎么破?
常见场景:曝光日志表(impressions)和转化日志表(conversions)是两张独立宽表,用 user_id + event_time 关联。但一个用户一天多次点击+多次转化,直接 LEFT JOIN 会导致笛卡尔积,SUM(conversions) 被放大。
- 解决方案一(推荐):先分别聚合再
JOIN SELECT i.campaign_id,
SUM(i.impression_count) AS impressions,
SUM(c.convert_count) AS conversions
FROM (SELECT campaign_id, COUNT(*) AS impression_count FROM impressions GROUP BY campaign_id) i
LEFT JOIN (SELECT campaign_id, COUNT(*) AS convert_count FROM conversions GROUP BY campaign_id) c
ON i.campaign_id = c.campaign_id
- 解决方案二:用
DISTINCT 去重,但仅适用于小数据量;COUNT(DISTINCT c.user_id) 在大表上性能差,且无法区分同一用户的多次转化是否应计为 1 次还是多次(业务定义决定)
- 切记:不要在 JOIN 后直接
COUNT(*) 或 SUM(),那是陷阱最密集的地方
COUNT(column) 会自动忽略 NULL,但若字段本身允许 NULL 且未显式过滤,可能把未埋点的记录也计入分母SUM(is_converted) 要求 is_converted 是整型 0/1;如果它是布尔类型(如 PostgreSQL 的 BOOLEAN),得先转成整数:SUM(CASE WHEN is_converted THEN 1 ELSE 0 END)
COUNT(*),否则包含测试流量、机器人请求等脏数据campaign_id、adset_id、date 等多维聚合,但容易漏掉关键维度或加了冗余维度。例如:在按 campaign_id 计算时,却把 user_id 也放进 GROUP BY,结果每行只有一条记录,转化率全是 0% 或 100%。
- 正确做法是先确认分析目标:是看计划粒度表现?那就只
GROUP BY campaign_id,并在SELECT中明确写出所有非聚合字段 - 若需下钻到小时级,注意
created_at字段是否带时区;用DATE(created_at AT TIME ZONE 'UTC')或DATE_TRUNC('hour', created_at)(PostgreSQL)保证时间对齐 - MySQL 用户注意:
ONLY_FULL_GROUP_BY模式下,SELECT中所有非聚合字段都必须出现在GROUP BY中,否则报错
NULL 和除零错误怎么安全处理?SUM(clicks) 可能为 0(比如新上线广告还没流量),直接除会导致数据库报错或返回 NULL(取决于方言)。别依赖应用层兜底,SQL 层就要拦截。
- 推荐写法:
ROUND(SUM(conversions) * 100.0 / NULLIF(SUM(clicks), 0), 2)
-
NULLIF(SUM(clicks), 0) 把分母为 0 的情况转成 NULL,整个表达式结果也为 NULL,不会崩溃
- 如果希望显示 0.00% 而不是 NULL,可套一层
COALESCE(..., 0),但要注意这会掩盖“无点击”的业务异常,建议保留 NULL 并在 BI 工具中单独标注
JOIN 多表时重复计数怎么破?
常见场景:曝光日志表(impressions)和转化日志表(conversions)是两张独立宽表,用 user_id + event_time 关联。但一个用户一天多次点击+多次转化,直接 LEFT JOIN 会导致笛卡尔积,SUM(conversions) 被放大。
- 解决方案一(推荐):先分别聚合再
JOIN SELECT i.campaign_id,
SUM(i.impression_count) AS impressions,
SUM(c.convert_count) AS conversions
FROM (SELECT campaign_id, COUNT(*) AS impression_count FROM impressions GROUP BY campaign_id) i
LEFT JOIN (SELECT campaign_id, COUNT(*) AS convert_count FROM conversions GROUP BY campaign_id) c
ON i.campaign_id = c.campaign_id
- 解决方案二:用
DISTINCT 去重,但仅适用于小数据量;COUNT(DISTINCT c.user_id) 在大表上性能差,且无法区分同一用户的多次转化是否应计为 1 次还是多次(业务定义决定)
- 切记:不要在 JOIN 后直接
COUNT(*) 或 SUM(),那是陷阱最密集的地方
ROUND(SUM(conversions) * 100.0 / NULLIF(SUM(clicks), 0), 2)
NULLIF(SUM(clicks), 0) 把分母为 0 的情况转成 NULL,整个表达式结果也为 NULL,不会崩溃COALESCE(..., 0),但要注意这会掩盖“无点击”的业务异常,建议保留 NULL 并在 BI 工具中单独标注user_id + event_time 关联。但一个用户一天多次点击+多次转化,直接 LEFT JOIN 会导致笛卡尔积,SUM(conversions) 被放大。
- 解决方案一(推荐):先分别聚合再
JOINSELECT i.campaign_id, SUM(i.impression_count) AS impressions, SUM(c.convert_count) AS conversions FROM (SELECT campaign_id, COUNT(*) AS impression_count FROM impressions GROUP BY campaign_id) i LEFT JOIN (SELECT campaign_id, COUNT(*) AS convert_count FROM conversions GROUP BY campaign_id) c ON i.campaign_id = c.campaign_id - 解决方案二:用
DISTINCT去重,但仅适用于小数据量;COUNT(DISTINCT c.user_id)在大表上性能差,且无法区分同一用户的多次转化是否应计为 1 次还是多次(业务定义决定) - 切记:不要在 JOIN 后直接
COUNT(*)或SUM(),那是陷阱最密集的地方
真实广告数据里,转化路径长、归因窗口复杂、设备 ID 易丢失,SQL 层只能解决确定性聚合。一旦涉及跨设备归因或延迟转化,就得靠更上层的归因模型,而不是硬调 AVG() 或 PERCENTILE_CONT()。

















