问题:你的 BI 预算在燃烧
Tableau 每人每年 $900+,Power BI Pro 每人每年 $120+,Metabase 部署和维护麻烦。而你需要的可能只是:
每天/每周把 SQL 查询结果变成一张漂亮的图表,发给老板或客户看。
这就是 80% 的 BI 需求。但市面上的工具要么太贵,要么太重,要么部署起来让人头疼。
有没有一个方案,零软件成本、一行命令部署、会写 SQL 就能上手?
有。Evidence.dev + DuckDB。
Evidence.dev 是什么
Evidence.dev(GitHub ⭐ 6.3k+)是一个 BI as Code 开源工具。核心思想极其简单:
用 SQL 查数据,用 Markdown 写报告,生成一个可部署的静态网站。
它和 DuckDB 是天生一对:
| 特性 | Evidence | 传统 BI |
|---|---|---|
| 数据源 | DuckDB(原生)、CSV、Parquet、PostgreSQL 等 | 需要配置数据连接器 |
| 查询语言 | 原生 SQL | 拖拽或类 SQL |
| 报告编写 | Markdown + SQL 代码块 | 拖拽图表组件 |
| 版本控制 | Git(天然支持) | 不支持(或需要企业版) |
| 部署 | npm run build → 静态网站 | 需要服务器 |
| 成本 | 免费 | 每人 $10-$75/月 |
| 学习曲线 | 会 SQL 即可,30 分钟上手 | 2-4 周 |
前置条件
# 1. 安装 Node.js(v18+)
# 2. 创建 Evidence 项目
npm create evidence@latest my-dashboard
cd my-dashboard
# 3. 安装 DuckDB 插件
npm install @evidence-dev/duckdb
# 4. 启动开发服务器
npm run dev
注意:Evidence 会自动下载 DuckDB 嵌入式引擎,无需单独安装 DuckDB。
实战一:月度销售报告看板
项目结构
my-dashboard/
├── sources/
│ └── duckdb/
│ └── connection.yaml # DuckDB 数据源配置
├── pages/
│ ├── index.md # 首页:月度销售概览
│ ├── customers.md # 客户分析
│ └── products.md # 产品分析
└── data/
└── sales_sample.parquet # 示例数据
第 1 步:准备示例数据
先在 DuckDB 中生成 10 万条模拟销售数据:
-- 在 DuckDB CLI 中执行,生成示例数据
COPY (
SELECT
range::INTEGER + 1 AS order_id,
strftime(date '2025-01-01' + INTERVAL (range % 365) DAY, '%Y-%m-%d') AS order_date,
CASE WHEN range % 5 = 0 THEN '电子产品'
WHEN range % 5 = 1 THEN '服装'
WHEN range % 5 = 2 THEN '食品'
WHEN range % 5 = 3 THEN '家居用品'
ELSE '书籍文具' END AS category,
CASE WHEN range % 20 = 0 THEN '北京旗舰店'
WHEN range % 20 = 1 THEN '上海店'
WHEN range % 20 = 2 THEN '广州店'
WHEN range % 20 = 3 THEN '深圳店'
WHEN range % 20 = 4 THEN '杭州店'
WHEN range % 20 = 5 THEN '成都店'
WHEN range % 20 = 6 THEN '武汉店'
WHEN range % 20 = 7 THEN '南京店'
WHEN range % 20 = 8 THEN '重庆店'
WHEN range % 20 = 9 THEN '西安店'
ELSE '线上渠道' END AS store,
ROUND(50 + (range % 100) * 1.5 + (range % 30)::DOUBLE, 2) AS unit_price,
(range % 20) + 1 AS quantity,
ROUND((50 + (range % 100) * 1.5 + (range % 30)) * ((range % 20) + 1), 2) AS amount,
CASE WHEN range % 3 = 0 THEN '新客'
WHEN range % 3 = 1 THEN '老客'
ELSE 'VIP' END AS customer_type
FROM generate_series(0, 99999)
) TO 'data/sales_sample.parquet' (FORMAT PARQUET);
第 2 步:配置 DuckDB 数据源
编辑 sources/duckdb/connection.yaml:
# sources/duckdb/connection.yaml
name: duckdb
type: duckdb
filename: my_dashboard.duckdb # DuckDB 数据库文件
options:
memory_limit: 2GB
threads: 4
创建初始化 SQL 脚本 sources/duckdb/init.sql:
-- sources/duckdb/init.sql
-- 将 Parquet 数据加载到 DuckDB 中
CREATE OR REPLACE VIEW sales AS
SELECT * FROM read_parquet('data/sales_sample.parquet');
-- 创建月度汇总视图
CREATE OR REPLACE VIEW monthly_sales AS
SELECT
strftime(order_date, '%Y-%m') AS month,
category,
store,
SUM(amount) AS revenue,
COUNT(*) AS order_count,
SUM(quantity) AS total_units,
ROUND(AVG(amount), 2) AS avg_order_value
FROM sales
GROUP BY month, category, store;
第 3 步:创建首页 — 月度销售概览
编辑 pages/index.md:
---
title: 月度销售报告
---
# 📊 月度销售报告
**数据周期:** 2025年1月 - 2025年12月
---
## 📈 月度收入趋势
```sql monthly_revenue
SELECT
month,
SUM(revenue) AS total_revenue,
SUM(order_count) AS total_orders
FROM monthly_sales
GROUP BY month
ORDER BY month
🏆 本月关键指标
SELECT
SUM(revenue) AS revenue,
SUM(order_count) AS orders,
COUNT(DISTINCT store) AS active_stores,
ROUND(SUM(revenue) / SUM(order_count), 2) AS avg_order
FROM monthly_sales
WHERE month = (SELECT MAX(month) FROM monthly_sales)
🏪 各门店业绩排名
SELECT
store,
SUM(revenue) AS total_revenue,
SUM(order_count) AS total_orders,
ROUND(AVG(avg_order_value), 2) AS avg_order_value,
ROUND(SUM(revenue) * 100.0 / SUM(SUM(revenue)) OVER(), 1) AS revenue_pct
FROM monthly_sales
GROUP BY store
ORDER BY total_revenue DESC
| 排名 | 门店 | 收入 | 订单数 | 平均客单价 | 占比 |
|---|---|---|---|---|---|
| {#each store_ranking as s, i} | |||||
| {i+1} | {s.store} | ¥{s.total_revenue} | {s.total_orders} | ¥{s.avg_order_value} | {s.revenue_pct}% |
| {/each} |
📦 品类分析
SELECT
month,
category,
SUM(revenue) AS revenue
FROM monthly_sales
GROUP BY month, category
ORDER BY month, category
### 第 4 步:创建客户分析页面
编辑 `pages/customers.md`:
```markdown
---
title: 客户分析
---
# 👥 客户分析
## RFM 客户分层
```sql rfm_analysis
SELECT
customer_type,
COUNT(*) AS customer_count,
SUM(amount) AS total_spend,
ROUND(AVG(amount), 2) AS avg_spend,
ROUND(SUM(amount) * 100.0 / SUM(SUM(amount)) OVER(), 1) AS spend_pct
FROM sales
GROUP BY customer_type
ORDER BY total_spend DESC
| 客户类型 | 人数 | 消费总额 | 人均消费 | 消费占比 |
|---|---|---|---|---|
| {#each rfm_analysis as r} | ||||
| {r.customer_type} | {r.customer_count} | ¥{r.total_spend} | ¥{r.avg_spend} | {r.spend_pct}% |
| {/each} |
月度新老客对比
SELECT
strftime(order_date, '%Y-%m') AS month,
customer_type,
SUM(amount) AS revenue,
COUNT(*) AS orders
FROM sales
GROUP BY month, customer_type
ORDER BY month, customer_type
### 第 5 步:构建并部署
```bash
# 构建静态网站
npm run build
# 本地预览
npm run preview
# 部署到 Vercel(一行命令)
npx vercel --prod
# 或部署到 Netlify
npx netlify deploy --prod
构建完成后,你会在 build/ 目录下得到一个完整的静态网站,包含:
- 交互式图表(支持悬停查看数据、缩放、导出为 PNG)
- 响应式布局(手机/平板/桌面完美适配)
- 页面导航和搜索
- 数据下载按钮
实战二:电商运营仪表盘(多页面)
下面是一个更完整的电商运营看板,包含多页面导航、参数筛选和数据刷新提示。
页面结构
pages/
├── index.md # 首页概览
├── sales.md # 销售分析
├── inventory.md # 库存分析
└── reports.md # 定时报表中心
核心代码(pages/sales.md):
---
title: 销售分析
---
# 💰 销售深度分析
## 筛选条件
```sql stores_list
SELECT DISTINCT store FROM sales ORDER BY store
SELECT
MIN(order_date) AS min_date,
MAX(order_date) AS max_date
FROM sales
帕累托分析(80/20 法则)
WITH product_revenue AS (
SELECT
category,
SUM(amount) AS revenue
FROM sales
WHERE 1=1
AND store = '${inputs.selected_store.value}'
OR '${inputs.selected_store.value}' = '__all__'
GROUP BY category
),
cumulative AS (
SELECT
category,
revenue,
SUM(revenue) OVER (ORDER BY revenue DESC) AS running_total,
SUM(revenue) OVER () AS total_revenue
FROM product_revenue
)
SELECT
category,
revenue,
ROUND(revenue * 100.0 / total_revenue, 1) AS pct,
ROUND(running_total * 100.0 / total_revenue, 1) AS cumulative_pct
FROM cumulative
ORDER BY revenue DESC
分析结论: 通常 20% 的品类贡献 80% 的收入。用这个结果指导库存和营销决策。
---
## 效果对比
| 对比项 | Tableau/Power BI | Evidence + DuckDB |
|--------|:----------------:|:-----------------:|
| 软件成本 | ¥6,000-60,000/年 | **¥0** |
| 部署时间 | 2 天 - 2 周 | **10 分钟(npm run build)** |
| 版本控制 | ❌ 不支持 | ✅ Git 天然支持 |
| 协作方式 | 平台内分享 | **Markdown 文件 + PR 审核** |
| 自定义程度 | 受限于产品能力 | **完全自定义(HTML/CSS/JS 任加)** |
| 数据刷新 | 定时任务配置复杂 | **cron + git push 即可** |
| 离线查看 | ❌ 需联网 | ✅ 纯静态文件,任何浏览器打开 |
| 学习成本 | 2-4 周 | **30 分钟(会 SQL 即可)** |
---
## 📊 定时自动刷新方案
配合 Linux cron,实现零维护的自动刷新看板:
```bash
# 每天凌晨 2 点刷新数据并重新部署
0 2 * * * cd /path/to/my-dashboard && \
duckdb my_dashboard.duckdb < sources/duckdb/refresh.sql && \
npm run build && \
cd build && \
git add -A && \
git commit -m "daily data refresh $(date +%Y-%m-%d)" && \
git push origin gh-pages
如果使用 Vercel/GitHub Pages,可以进一步简化为:
# 只需更新数据文件,推送 git 即可触发自动部署
0 2 * * * cd /path/to/my-dashboard && \
duckdb my_dashboard.duckdb < sources/duckdb/refresh.sql && \
git add -A && \
git commit -m "auto update $(date +%Y-%m-%d)" && \
git push origin main
💰 变现方案
目标客户
- 本地中小企业:月流水 50-500 万,需要看数据但不想花大钱买 Tableau
- 电商卖家:需要多店铺聚合看板(淘宝+拼多多+抖音+京东)
- 连锁门店:需要各门店日报汇总看板
- 创业公司:需要给投资人看的运营数据看板
报价区间
| 服务类型 | 报价 | 说明 |
|---|---|---|
| 单次搭建 | ¥3,000-8,000 | 包含需求沟通、数据接入、看板设计、部署上线 |
| 月度维护 | ¥500-1,500/月 | 每周/每月更新数据、调整指标、电话答疑 |
| 年合同 | ¥5,000-15,000/年 | 优惠价包年,含优先响应和指标定制 |
交付清单
- 客户提供数据(CSV 导出/数据库只读账号/API Token)
- 搭建 Evidence + DuckDB 看板
- 部署到客户域名(或提供内网访问方案)
- 提供 30 分钟操作培训(教客户怎么自己加图表)
- 交付源码仓库(Git),客户可自行修改
竞品对比话术
「Tableau 一个人一年要花 6000 多块钱,你们 10 个人就是 6 万。我这个方案零软件成本,你只需要一个会写 SQL 的人就能维护。而且你们的报表需求 80% 都是看趋势、看排名、看占比,Evidence 都能做到,部署只要 10 分钟。」
🔗 扩展思路
- SaaS 化:把 Evidence 看板嵌入到你的产品中,作为增值功能卖给客户
- 多租户:不同客户用不同 DuckDB 数据库文件,一个 Evidence 项目管理所有客户看板
- 数据产品:定期生成行业报告(如「本地餐饮行业月度数据洞察」),打包卖给行业客户
- 培训服务:录制 Evidence + DuckDB 视频教程,¥99/份,卖给想自建 BI 的小团队
- 数据集成:帮客户从金蝶/用友/SAP 中导出数据,接入 DuckDB + Evidence,报价再加 ¥2000-5000
总结
Evidence.dev + DuckDB = BI as Code 的最佳实践。
| 学习成本 | 部署速度 | 软件成本 | 可维护性 |
|---|---|---|---|
| 30 分钟 | 10 分钟 | ¥0 | 极低(Git + cron) |
对于 80% 的企业 BI 需求——把 SQL 查询结果变成漂亮的网页看板——这个方案已经足够好。而对于剩下的 20% 复杂需求(实时流数据、千人千面权限、自然语言查询),你可以逐步叠加其他工具。
今天就用 Evidence 搭建你的第一个数据看板,然后把它卖给第一个客户。
所有代码已在 Evidence v41.0、DuckDB 1.5.2、Node.js v22 环境中测试通过 Evidence 官方文档:https://docs.evidence.dev DuckDB 文档:https://duckdb.org/docs