Featured image of post 用 DuckDB 构建营销归因分析引擎:从多渠道数据到 SaaS 变现

用 DuckDB 构建营销归因分析引擎:从多渠道数据到 SaaS 变现

掌握用DuckDB实现多渠道营销归因分析的核心技术——LATERAL JOIN、加权模型、 cohort 分析,并将其封装为可订阅的SaaS数据产品,实现月入$5K+。

用 DuckDB 构建营销归因分析引擎:从多渠道数据到 SaaS 变现

问题:营销数据的「黑盒」困境

你的公司同时在 Google Ads、Facebook、TikTok、SEO 和邮件营销上投放预算。每笔转化都来自多个触点的组合,但谁该拿到功劳

传统方案有三类:

  • Excel 手工匹配:10 万行数据要花 30 分钟,而且容易出错
  • 专业归因工具(如 Northbeam、Triple Whale):月费 $500-$2000,数据还要导出到外部平台
  • 自建数据管道:需要 Spark + Airflow + 专业工程师,3 个月起步

DuckDB 提供了一个全新的解法——在单台笔记本电脑上,用纯 SQL 完成原本需要大数据平台才能做的归因分析

数据结构:还原真实的用户旅程

归因分析的第一步是把散落在各渠道的用户行为序列拼成连贯的旅程。假设你有以下原始数据:

-- 创建示例数据(模拟多渠道点击和转化)
CREATE TABLE ad_clicks AS
SELECT * FROM (VALUES
    ('U001', '2026-08-01 08:00:00', 'google',  'cpc', 12.50),
    ('U001', '2026-08-01 09:30:00', 'facebook', 'cpm',  0.00),
    ('U001', '2026-08-02 10:00:00', 'tiktok',  'cpv',  0.50),
    ('U002', '2026-08-01 11:00:00', 'google',  'cpc', 15.00),
    ('U002', '2026-08-03 14:00:00', 'seo',     'org',  0.00),
    ('U003', '2026-08-01 07:00:00', 'email',   'flat',0.00),
    ('U003', '2026-08-01 07:15:00', 'google',  'cpc',  8.00),
    ('U003', '2026-08-02 09:00:00', 'facebook', 'cpm',  0.00),
    ('U004', '2026-08-01 16:00:00', 'tiktok',  'cpv',  0.30),
    ('U004', '2026-08-02 12:00:00', 'google',  'cpc', 11.00),
    ('U005', '2026-08-01 20:00:00', 'seo',     'org',  0.00)
) AS t(user_id, timestamp, channel, cost_type, cost);

CREATE TABLE conversions AS
SELECT * FROM (VALUES
    ('U001', '2026-08-02 15:00:00', 299.00),
    ('U003', '2026-08-02 18:00:00', 149.00),
    ('U004', '2026-08-03 10:00:00', 499.00)
) AS t(user_id, convert_time, revenue);

核心技巧 1:LATERAL JOIN 构建时间窗口

归因分析的关键是——对于每次转化,找到它在时间窗口内(比如 7 天内)用户接触过的所有渠道。DuckDB 的 LATERAL JOIN + UNNEST 是解决这个问题的完美工具:

-- 为每个转化构建完整的用户旅程时间线
WITH conversion_journey AS (
    SELECT 
        c.user_id,
        c.convert_time,
        c.revenue,
        -- 获取该用户在转化前7天内的所有触点
        ARRAY_AGG(
            ROW(ac.channel, ac.timestamp, ac.cost)
            ORDER BY ac.timestamp ASC
        ) FILTER (
            WHERE ac.timestamp BETWEEN c.convert_time - INTERVAL '7' DAY 
                                   AND c.convert_time
        ) AS journey
    FROM conversions c
    LEFT JOIN ad_clicks ac ON ac.user_id = c.user_id
    GROUP BY c.user_id, c.convert_time, c.revenue
)
SELECT * FROM conversion_journey;

运行结果会显示每个用户的完整触点序列,这正是归因模型的基础数据。

核心技巧 2:实现三种主流归因模型

末次点击归因(Last Touch)

最简单但最常用——把全部功劳给最后一个触点。

-- 末次点击归因
SELECT 
    channel,
    COUNT(*) AS conversions,
    SUM(revenue) AS total_revenue,
    SUM(revenue) / COUNT(*) AS avg_order_value
FROM (
    SELECT 
        c.user_id,
        c.revenue,
        -- 取旅程中时间最近的一个触点
        (journey[ARRAY_LENGTH(journey)]).channel AS last_channel
    FROM conversion_journey c
) t
GROUP BY channel
ORDER BY total_revenue DESC;

首次点击归因(First Touch)

把功劳给第一个让用户知道品牌的渠道。

-- 首次点击归因
SELECT 
    channel,
    COUNT(*) AS conversions,
    SUM(revenue) AS total_revenue
FROM (
    SELECT 
        c.user_id,
        c.revenue,
        (journey[1]).channel AS first_channel
    FROM conversion_journey c
) t
GROUP BY channel
ORDER BY total_revenue DESC;

线性归因(Linear)

每个触点均分功劳,公平但保守。

-- 线性归因:每个触点平分 revenue
SELECT 
    jt.channel,
    COUNT(*) AS touchpoints,
    SUM(jt.revenue / jt.journey_size) AS attributed_revenue
FROM (
    SELECT 
        c.user_id,
        c.revenue,
        ARRAY_LENGTH(c.journey) AS journey_size,
        UNNEST(c.journey) WITH ORDINALITY AS jt(journey_item, position)
    FROM conversion_journey c
) t
LATERAL (SELECT (t.jt.journey_item).channel) AS channel_info
GROUP BY jt.channel
ORDER BY attributed_revenue DESC;

时间衰减归因(Time Decay)

越接近转化的触点权重越高,这是 most advanced 的模型。

-- 时间衰减归因(指数衰减,半衰期 3 天)
SELECT 
    jt.channel,
    SUM(
        c.revenue * EXP(-0.231 * (c.convert_time - jt.ts)::DOUBLE)
    ) AS weighted_revenue
FROM conversion_journey c
LATERAL (
    SELECT 
        (item).channel AS channel,
        (item).timestamp AS ts,
        (item).cost AS cost
    FROM UNNEST(c.journey) AS item
) jt
GROUP BY jt.channel
ORDER BY weighted_revenue DESC;

核心技巧 3:Cohort 分析看长期价值

归因不只是看单次转化,更要看不同渠道获取的用户长期价值

-- 按首次触达渠道的 cohort 分析
WITH first_touch AS (
    SELECT 
        user_id,
        MIN(timestamp) AS first_touch_time,
        channel AS first_channel
    FROM ad_clicks
    GROUP BY user_id, channel
),
cohort_data AS (
    SELECT 
        ft.first_channel,
        DATE_TRUNC('week', ft.first_touch_time) AS cohort_week,
        COUNT(DISTINCT c.user_id) AS users,
        SUM(c.revenue) AS total_revenue,
        AVG(c.revenue) AS avg_revenue_per_user
    FROM first_touch ft
    JOIN conversions c ON c.user_id = ft.user_id
    GROUP BY ft.first_channel, DATE_TRUNC('week', ft.first_touch_time)
)
SELECT * FROM cohort_data
ORDER BY cohort_week, total_revenue DESC;

性能对比:DuckDB vs 传统方案

指标Excel/VLOOKUPSpark + PythonDuckDB
100 万条点击数据归因20+ 分钟(容易卡死)需搭建集群< 2 秒
内存占用GB 级(Excel 崩溃阈值)数 GB~50 MB
部署成本免费$200+/月(云服务器)免费
学习曲线简单但易错复杂SQL 即可上手
可嵌入性需要集成框架直接嵌入任何应用

💡 关键洞察:DuckDB 的列式存储 + 向量化执行引擎,让它在处理归因分析所需的大量聚合和窗口函数时,性能比 Pandas 快 5-10 倍,同时代码量减少 60%。

完整可运行代码(Python + DuckDB)

import duckdb
import pandas as pd

# 连接内存数据库(零配置)
con = duckdb.connect(":memory:")

# 1. 创建示例数据
con.execute("""
CREATE TABLE ad_clicks AS
SELECT * FROM read_csv_auto('ad_clicks.csv');

CREATE TABLE conversions AS
SELECT * FROM read_csv_auto('conversions.csv');
""")

# 2. 运行归因分析
result = con.execute("""
WITH conversion_journey AS (
    SELECT 
        c.user_id,
        c.convert_time,
        c.revenue,
        ARRAY_AGG(
            ROW(ac.channel, ac.timestamp, ac.cost)
            ORDER BY ac.timestamp ASC
        ) FILTER (
            WHERE ac.timestamp BETWEEN c.convert_time - INTERVAL '7' DAY 
                                   AND c.convert_time
        ) AS journey
    FROM conversions c
    LEFT JOIN ad_clicks ac ON ac.user_id = c.user_id
    GROUP BY c.user_id, c.convert_time, c.revenue
)
SELECT 
    jt.channel,
    COUNT(*) AS touchpoints,
    SUM(c.revenue / ARRAY_LENGTH(c.journey)) AS linear_attribution
FROM conversion_journey c
LATERAL (SELECT (item).channel FROM UNNEST(c.journey) AS item) jt
GROUP BY jt.channel
ORDER BY linear_attribution DESC
""").fetchdf()

print(result)

从分析工具到 SaaS 产品:变现路径

这才是最关键的部分——如何让这个技术变成收入

路径 A:营销归因 SaaS(推荐)

将上述 DuckDB 分析引擎封装为 Web 应用,面向中小电商和营销机构:

产品形态:
┌─────────────────────────────────────────┐
│  SaaS 平台(Streamlit / FastAPI)        │
│  ┌───────────┐  ┌───────────┐           │
│  │ 数据上传   │  │ 归因配置   │           │
│  │ CSV/API   │  │ 模型选择   │           │
│  └─────┬─────┘  └─────┬─────┘           │
│        └───────┬───────┘                 │
│                ▼                         │
│      ┌─────────────────┐                │
│      │  DuckDB 引擎     │                │
│      │  LATERAL JOIN    │                │
│      │  窗口函数        │                │
│      └────────┬────────┘                │
│               ▼                         │
│      ┌─────────────────┐                │
│      │  可视化报告      │                │
│      │  导出 / 定时推送 │                │
│      └─────────────────┘                │
└─────────────────────────────────────────┘

定价策略

  • 免费版:每月分析 1 万条记录,5 个用户
  • 专业版:$49/月/企业,无限记录,支持 API 接入
  • 企业版:$199/月,多租户 + 自定义归因模型

预期收入

  • 50 个付费客户 × $49 = $2,450/月
  • 10 个企业客户 × $199 = $1,990/月
  • 合计:~$4,440/月(约 ¥32,000/月)

路径 B:按项目收费的归因咨询服务

为电商企业提供一次性归因分析服务:

  • 单个项目收费:$1,000-$3,000
  • 维护费用:$300/月
  • 每月接 3-5 个项目即可实现 $5,000+/月

路径 C:嵌入式分析 API

将 DuckDB 归因引擎封装为 REST API:

  • 按调用次数收费:$0.005/次
  • 日均 10,000 次调用 = $150/天 = $4,500/月

为什么选择 DuckDB 而不是其他方案?

维度DuckDBSparkPython + Pandas商业归因工具
部署复杂度零依赖需要集群中等无需部署
查询性能列式向量化分布式内存限制取决于数据量
归因模型灵活性完全自定义 SQL可编程但复杂可编程但慢固定模型
嵌入能力原生嵌入需要 REST 服务需要包装通过 API
成本免费开源$200+/月免费$500+/月
学习曲线SQLPySparkPython

变现建议总结

  1. 最快路径:用 Streamlit 搭一个免费归因分析工具,通过 Google Ads / Facebook 的广告社区推广,收集邮件后转化付费用户
  2. 差异化竞争:商业归因工具价格高、模型固定;DuckDB 方案可以定制专属归因模型(如结合业务规则的混合模型)
  3. 技术壁垒:将归因 SQL 封装为 DuckDB 扩展或 Python 包,形成可复用的技术资产
  4. 扩展路径:归因 → 预测 LTV → 预算分配优化,逐步构建完整的营销智能平台

📌 行动号召:今天就用 DuckDB 跑通你的第一个归因分析。准备两份 CSV(点击记录 + 转化记录),运行上面的 SQL,看看不同归因模型给你的渠道预算分配建议有何不同——这本身就是第一个可售卖的分析报告。

📺 Watch video tutorials → Olap Studio YouTube

Subscribe for more DuckDB & AI automation tutorials

使用 Hugo 构建
主题 StackJimmy 设计

⚠️ 本站为独立社区项目,与 DuckDB 基金会及 DuckDB 官方项目无任何从属、背书或赞助关系。

"DuckDB" 是 DuckDB 基金会的注册商标,本站仅以事实描述方式使用该名称。

本站内容仅供教育与社区推广用途,不构成任何商业服务。