SQL148 返回产品名称和每一项产品的总订单数

描述

Products表为产品信息表含有字段prod_id产品id、prod_name产品名称

|---------|-----------|
| prod_id | prod_name |
| a0001 | egg |
| a0002 | sockets |
| a0013 | coffee |
| a0003 | cola |
| a0023 | soda |

OrderItems表为订单信息表含有字段order_num订单号和产品id prod_id

|---------|-----------|
| prod_id | order_num |
| a0001 | a105 |
| a0002 | a1100 |
| a0002 | a200 |
| a0013 | a1121 |
| a0003 | a10 |
| a0003 | a19 |
| a0003 | a5 |

【问题】

使用 OUTER JOIN 联结 Products 表和 OrderItems 表,返回产品名称(prod_name)和每一项产品的总订单数(不是订单号),并按产品名称升序排序。

【示例结果】

返回产品名称prod_name和订单号订单数orders

|-----------|--------|
| prod_name | orders |
| coffee | 1 |
| cola | 3 |
| egg | 1 |
| sockets | 2 |
| soda | 0 |

【示例解析】

返回产品和产品对应的实际支付的订单数,但是无实际订单的产品soda也返回,最后根据产品名称升序排序。

复制代码
SELECT P.prod_name, IFNULL(T.orders, 0) AS orders 
FROM Products P 
LEFT JOIN (
    SELECT prod_id, COUNT(*) AS orders 
    FROM OrderItems 
    GROUP BY prod_id
) T ON T.prod_id = P.prod_id
ORDER BY P.prod_name;

关键代码解释

1. 处理 NULLIFNULL(T.orders, 0)

在使用 LEFT JOIN 时,如果右表(这里是子查询 T)中没有匹配的记录,对应字段(如 T.orders)的值就会是 NULL

但在业务展示中,我们通常希望"没有订单"显示为 0,而不是 NULL 或空值。

IFNULL()MySQL 特有的函数 ,用于判断并替换 NULL 值:

复制代码
IFNULL(expression, replacement)
  • 如果 expression 不是 NULL,返回该表达式的值;
  • 如果 expressionNULL,则返回 replacement

✅ 应用示例:

复制代码
IFNULL(T.orders, 0)

T.ordersNULL(即产品从未被下单)时,返回 0,实现"零订单显示为 0"的需求。

⚠️ 注意IFNULL 仅适用于 MySQL 数据库。在其他数据库系统(如 PostgreSQL、SQL Server、Oracle)中不支持。


2. 更通用的替代方案:COALESCE(T.orders, 0)

如果你使用的是其他数据库,或希望写出兼容性更强的标准 SQL ,应使用 COALESCE() 函数。

复制代码
COALESCE(value1, value2, ..., valueN)
  • 返回参数列表中第一个非 NULL 的值
  • 至少需要两个参数。
  • SQL 标准函数,几乎在所有关系型数据库中都支持(MySQL、PostgreSQL、SQL Server、Oracle、SQLite 等)。

✅ 应用示例:

复制代码
COALESCE(T.orders, 0)

含义与 IFNULL(T.orders, 0) 完全相同:如果 T.ordersNULL,就返回 0

优势

  • 跨数据库兼容性好
  • 功能更强大(可接受多个参数,例如 COALESCE(col1, col2, 0) 表示优先取 col1,若为 NULL 则取 col2,都为 NULL 才返回 0

推荐使用场景

  • 你不确定数据库类型
  • 项目要求使用标准 SQL
  • 需要更灵活的 NULL 处理逻辑

✅ 总结对比

函数 语法 数据库支持 是否标准 SQL 推荐场景
IFNULL IFNULL(expr, default) 仅 MySQL ❌ 否 MySQL 项目,追求简洁
COALESCE COALESCE(expr1, expr2, ...) 所有主流数据库 ✅ 是 通用项目、跨数据库、标准 SQL

易错点

📝 为什么统计订单要"先聚合,再连接"?

❌ 错误做法:直接连接

复制代码
SELECT P.prod_name, COUNT(*) 
FROM Products P 
LEFT JOIN OrderItems O ON P.prod_id = O.prod_id
GROUP BY P.prod_name;

问题:

  • 逻辑混乱:COUNT(*) 数的是连接后的行数,看似正确,实则侥幸

  • 性能差:先产生大量重复数据,再分组统计。

  • 难扩展:一旦要加条件或字段,容易出错。


✅ 正确做法:先聚合,再连接

复制代码
SELECT P.prod_name, COALESCE(T.orders, 0) AS orders
FROM Products P
LEFT JOIN (
    SELECT prod_id, COUNT(*) AS orders 
    FROM OrderItems 
    GROUP BY prod_id
) T ON P.prod_id = T.prod_id;

优势:

优点 说明
✅ 逻辑清晰 子查询专注"统计",主查询专注"拼接"
✅ 性能更好 聚合在小表上完成,连接更高效
✅ 避免重复 不会因多订单产生重复行
✅ 易维护 修改统计条件只需动子查询

💡 类比理解

  • OrderItems 是几千张小票。

  • 你想知道"每个产品卖了多少"。

❌ 别把所有小票和产品表连起来数!

✅ 先让系统出个《销量统计表》,再贴到产品表旁边。

这个"统计表"就是子查询。


✅ 核心原则

"聚合在前,连接在后"

先用子查询生成统计结果,再用 LEFT JOIN 关联主表,

配合 COALESCE 处理 NULL,确保未销售产品显示为 0

相关推荐
数智启示录3 小时前
Apache Kafka 同 Key 仍会乱序:从分区 Offset 到业务可见的四道边界 【Kafka 合集】
大数据·数据库·经验分享·分布式·面试·kafka·apache
烬羽4 小时前
给 Agent 一个检索式记忆:把对话历史存进 Milvus,该记的都记得
javascript·数据库·agent
星云API技术支持4 小时前
企业微信二次开发外部群:群聊数据与联系人信息如何建立关联
数据库·php·企业微信
我不是程序员三三4 小时前
桌面管理软件哪个最好?从一次上线翻车事故,拆解选型评估与配置落地全流程
数据库
落木萧萧8254 小时前
MyBatis 关联查询的四种写法,为什么最后都变回了手写 XML
java·数据库·后端
春风解人意4 小时前
从零开始学习嵌入式P35----数据库
数据库·嵌入式硬件·学习
sunoo-2294 小时前
【网络编程 + 数据库】select 与 epoll 核心区别详解 + SQLite 入门到 C 接口全攻略
linux·网络·数据库·vscode·学习·sqlite
TDengine (老段)4 小时前
TDengine 错误处理 — 错误码、异常传播、故障恢复
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
zbyyd4 小时前
Linux 系统编程 :select/epoll 多路复用与 SQLite 数据库实战
linux·数据库·sqlite
俊昭喜喜里4 小时前
c#中的构造函数
开发语言·数据库·c#