分类课程智能体AI
文章
订阅
分类课程AI导师
文章
价格
课程进度
3 / 12
上一节先把数据质量说清楚下一节把四张表连接成销售明细
自在学

© 2025 - 2026 株洲市自在学教育科技有限公司 版权所有

公网安备湘公网安备43020302000292号 | 湘ICP备2025148919号-1

关于我们隐私政策使用条款

© 2025 - 2026 株洲市自在学教育科技有限公司 版权所有

公网安备湘公网安备43020302000292号湘ICP备2025148919号-1

编程SQL + Excel 数据分析实战用 SQL 回答第一个业务问题

用 SQL 回答第一个业务问题

现在数据已经可以查询。我们先不急着写复杂语句,而是用 SELECT、WHERE、GROUP BY 和 HAVING 回答几个具体问题:有多少完成订单、各支付方式有多少订单、哪些月份取消较多。


从一小块数据开始看

sql
SELECT
    order_id,
    customer_id,
    order_date,
    status,
    payment_method
FROM v_orders_clean
ORDER BY order_date, order_id
LIMIT 10;

SELECT 决定返回哪些列,FROM 指定数据来源,ORDER BY 决定展示顺序,LIMIT 控制查看行数。探索数据时先看 10 行,比直接导出整表更容易发现日期、状态或编号是否异常。


用 WHERE 定义分析范围

经营收入通常只统计已完成订单,所以先过滤订单状态。

sql
SELECT COUNT(*) AS completed_orders
FROM v_orders_clean
WHERE status = 'COMPLETED';

结果是 509 笔。取消和退货订单仍保留在数据库里,但不进入销售额。这个口径以后会写进销售明细视图,避免每个查询各写一遍。

多个条件可以用 AND 或 OR 组合:

sql
SELECT
    order_id,
    order_date,
    status
FROM v_orders_clean
WHERE order_date >= '2025-01-01'
  AND status <> 'COMPLETED'
ORDER BY order_date;

日期使用 YYYY-MM-DD 文本格式时可以直接比较。这个格式从年到日由大到小排列,字符串顺序与时间顺序一致。


用 GROUP BY 汇总类别

sql
SELECT
    status,
    COUNT(*) AS orders
FROM v_orders_clean
GROUP BY status
ORDER BY orders DESC;
statusorders
COMPLETED509
CANCELLED18
RETURNED13

GROUP BY status 把相同状态的订单放进一组,COUNT(*) 再数每组有多少行。聚合之后,SELECT 中的普通列必须出现在 GROUP BY 中,否则结果的含义会不清楚。

再看已完成订单的支付方式:

sql
SELECT
    payment_method,
    COUNT(*) AS orders,
    ROUND(
        100.0 * COUNT(*) /
        SUM(COUNT(*)) OVER (),
        1
    ) AS order_share_pct
FROM v_orders_clean
WHERE status = 'COMPLETED'
GROUP BY payment_method
ORDER BY orders DESC;

这里的 SUM(COUNT(*)) OVER () 会把各组的订单数再次求和,用作占比的分母。乘以 100.0 是为了让 SQLite 使用小数除法。


WHERE 和 HAVING 不做同一件事

WHERE 在分组前过滤明细行,HAVING 在分组后过滤汇总结果。下面的查询只保留取消或退货超过 1 笔的月份:

sql
SELECT
    strftime('%Y-%m', order_date) AS order_month,
    COUNT(*) AS abnormal_orders
FROM v_orders_clean
WHERE status IN ('CANCELLED', 'RETURNED')
GROUP BY strftime('%Y-%m', order_date)
HAVING COUNT(*) > 1
ORDER BY order_month;

如果把 COUNT(*) > 1 写进 WHERE,数据库会报错,因为分组尚未发生,还不存在每月的计数。


给查询加上自检

一个汇总最好能和总数对上。先按状态汇总,再把汇总结果相加:

sql
WITH status_counts AS (
    SELECT status, COUNT(*) AS orders
    FROM v_orders_clean
    GROUP BY status
)
SELECT SUM(orders) AS checked_total
FROM status_counts;

checked_total 应该等于清洗订单表的 540 行。如果对不上,说明过滤条件或分组逻辑改变了范围。


用执行顺序理解 WHERE 与 HAVING

SQL 写出来的顺序和数据库理解它的顺序不同。可以先记住这个简化版本:

text
FROM -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORDER BY -> LIMIT

先确定数据来源,再过滤明细行,然后分组。HAVING 过滤分组结果,SELECT 才决定最后展示哪些列。

这解释了两个常见疑问。

第一,为什么 COUNT(*) > 1 不能写进 WHERE?执行 WHERE 时还没有分组,也就没有每组计数。

第二,为什么有时不能在 WHERE 中直接使用 SELECT 别名?别名在更后面的 SELECT 阶段才产生。为了让查询更容易移植,筛选条件里直接写原表达式,或先放进 CTE。


用一个查询看清分组粒度

下面两条查询都合法,但回答的问题不同。

按状态分组:

sql
SELECT status, COUNT(*) AS orders
FROM v_orders_clean
GROUP BY status;

结果一行一个状态。

按状态和支付方式分组:

sql
SELECT
    status,
    payment_method,
    COUNT(*) AS orders
FROM v_orders_clean
GROUP BY status, payment_method
ORDER BY status, orders DESC;

结果一行是一个“状态 + 支付方式”组合。每增加一个分组字段,结果粒度就更细。

如果你想要一行一个状态,却把 payment_method 也放进 GROUP BY,行数会增加。这不是数据库算错,而是问题已经变成了另一件事。


空值不会自动参与比较

SQL 中 NULL 表示未知。它既不等于某个值,也不等于另一个 NULL。

下面的写法找不到空值:

sql
SELECT *
FROM v_customers_clean
WHERE channel = NULL;

正确写法是:

sql
SELECT *
FROM v_customers_clean
WHERE channel IS NULL;

本项目的清洗视图已经把空渠道标记成“未归因”,所以查询清洗视图时应筛选这个明确类别:

sql
SELECT COUNT(*) AS unattributed_customers
FROM v_customers_clean
WHERE channel = '未归因';

不要把 NULL 与 0 混为一谈。0 是已知没有,NULL 是不知道。后面环比首月保持为空,也是同一个道理。


养成从小范围开始调试的习惯

一条查询结果不对时,不要一次改很多地方。按下面的顺序缩小问题:

  1. 先去掉聚合,只看 10 行明细。
  2. 只保留一个过滤条件,检查它减少了多少行。
  3. 再加入第二个条件,比较行数变化。
  4. 最后添加 GROUP BY 和聚合函数。

例如要统计 2025 年的非完成订单,可以分步运行:

sql
SELECT COUNT(*)
FROM v_orders_clean
WHERE order_date >= '2025-01-01';

再加入状态条件:

sql
SELECT COUNT(*)
FROM v_orders_clean
WHERE order_date >= '2025-01-01'
  AND status <> 'COMPLETED';

这样能看出是哪一个条件改变了范围。直接从一条很长的查询开始,报错和口径问题会混在一起。


为聚合查询加上分母检查

支付方式占比的分母应该是 509 笔完成订单。可以把结果包进 CTE 再核对:

sql
WITH payment_counts AS (
    SELECT
        payment_method,
        COUNT(*) AS orders
    FROM v_orders_clean
    WHERE status = 'COMPLETED'
    GROUP BY payment_method
)
SELECT
    SUM(orders) AS completed_orders,
    ROUND(SUM(100.0 * orders / 509), 1) AS share_total_pct
FROM payment_counts;

完成订单应为 509,占比合计应接近 100%。因为四舍五入,展示后的占比合计可能有 0.1 个百分点差异,这与漏数不是同一种问题。

下一节会把订单、明细、客户和商品连接起来,开始计算销售额与毛利。连接后行数会显著增加,我们会专门检查这种增加是不是合理的一对多展开。

  • 从一小块数据开始看
  • 用 WHERE 定义分析范围
  • 用 GROUP BY 汇总类别
  • WHERE 和 HAVING 不做同一件事
  • 给查询加上自检
  • 用执行顺序理解 WHERE 与 HAVING
  • 用一个查询看清分组粒度
  • 空值不会自动参与比较
  • 养成从小范围开始调试的习惯
  • 为聚合查询加上分母检查

目录

  • 从一小块数据开始看
  • 用 WHERE 定义分析范围
  • 用 GROUP BY 汇总类别
  • WHERE 和 HAVING 不做同一件事
  • 给查询加上自检
  • 用执行顺序理解 WHERE 与 HAVING
  • 用一个查询看清分组粒度
  • 空值不会自动参与比较
  • 养成从小范围开始调试的习惯
  • 为聚合查询加上分母检查