在数据库业务开发中,我们常常需要关联多张表获取数据,同时对这些数据进行统计汇总,这时候就需要将SQL聚合函数和JOIN多表查询结合使用。这种组合查询可以让我们在一次查询中完成关联数据提取和统计计算,避免多次查询带来的性能损耗。

核心执行逻辑
SQL中JOIN和聚合函数的组合执行顺序遵循查询的逻辑执行顺序:首先执行FROM和JOIN阶段,将多张表按照关联条件拼接成一张临时结果集;然后执行WHERE阶段过滤临时结果集的行;接着执行GROUP BY阶段对过滤后的结果集分组;之后执行聚合函数对每个分组进行计算;最后执行HAVING阶段过滤分组结果,SELECT阶段提取最终需要的字段。
基础组合示例
假设我们有两张表,一张是用户表user,存储用户基础信息;一张是订单表order,存储用户的订单记录,两张表通过user_id字段关联。现在需要统计每个用户的订单总数和订单总金额,就可以用以下查询实现:
-- 用户表结构
CREATE TABLE user (
user_id INT PRIMARY KEY,
user_name VARCHAR(50) NOT NULL
);
-- 订单表结构
CREATE TABLE order (
order_id INT PRIMARY KEY,
user_id INT NOT NULL,
order_amount DECIMAL(10,2) NOT NULL,
FOREIGN KEY (user_id) REFERENCES user(user_id)
);
-- 组合查询:统计每个用户的订单数和总金额
SELECT
u.user_id,
u.user_name,
COUNT(o.order_id) AS order_count,
SUM(o.order_amount) AS total_amount
FROM user u
LEFT JOIN order o ON u.user_id = o.user_id
GROUP BY u.user_id, u.user_name;
这个查询中先通过LEFT JOIN将用户表和订单表关联,保留所有用户记录,即使该用户没有订单也会显示,订单数为0,总金额为NULL。然后通过GROUP BY按照用户ID和用户名分组,再使用COUNT和SUM两个聚合函数分别统计订单数和总金额。
带过滤条件的组合查询
如果我们需要统计2024年1月1日之后每个用户的订单情况,就需要在聚合之前先过滤订单数据,这时候要使用WHERE条件:
SELECT
u.user_id,
u.user_name,
COUNT(o.order_id) AS order_count,
SUM(o.order_amount) AS total_amount
FROM user u
LEFT JOIN order o ON u.user_id = o.user_id
WHERE o.order_time >= '2024-01-01' OR o.order_time IS NULL
GROUP BY u.user_id, u.user_name;
这里要注意,如果过滤条件放在WHERE中,对于没有订单的用户,o.order_time会是NULL,所以需要加上OR o.order_time IS NULL才能保证这类用户被保留。如果过滤条件放在HAVING中,则是在分组之后过滤,逻辑会有所不同。
常见错误和避坑点
1. 分组字段不全
SELECT中出现的非聚合字段必须全部出现在GROUP BY子句中,否则查询会报错或者返回不确定的结果。比如上面的例子中如果只写GROUP BY u.user_id,而SELECT中还有u.user_name,在部分数据库严格模式下会直接报错。
2. 聚合函数位置错误
聚合函数只能出现在SELECT、HAVING子句中,不能出现在WHERE子句中。如果需要对聚合后的结果过滤,要使用HAVING而不是WHERE,比如统计订单数大于3的用户:
SELECT
u.user_id,
u.user_name,
COUNT(o.order_id) AS order_count,
SUM(o.order_amount) AS total_amount
FROM user u
LEFT JOIN order o ON u.user_id = o.user_id
GROUP BY u.user_id, u.user_name
HAVING COUNT(o.order_id) > 3;
3. JOIN类型选择错误
如果需要保留主表所有记录,即使关联表没有匹配数据也要显示,要使用LEFT JOIN;如果只需要保留两表都有匹配的数据,使用INNER JOIN即可。如果错误使用INNER JOIN,会导致没有订单的用户被过滤掉,统计结果不准确。
多表关联的组合场景
如果是三张及以上表关联,逻辑也是一致的,先完成所有JOIN操作得到临时结果集,再进行分组和聚合。比如新增一张订单商品表order_item,存储每个订单的商品明细,现在需要统计每个用户购买的商品总数量:
-- 订单商品表结构
CREATE TABLE order_item (
item_id INT PRIMARY KEY,
order_id INT NOT NULL,
product_count INT NOT NULL,
FOREIGN KEY (order_id) REFERENCES order(order_id)
);
-- 统计每个用户的商品总购买量
SELECT
u.user_id,
u.user_name,
SUM(oi.product_count) AS total_product_count
FROM user u
LEFT JOIN order o ON u.user_id = o.user_id
LEFT JOIN order_item oi ON o.order_id = oi.order_id
GROUP BY u.user_id, u.user_name;
这个查询先关联用户表和订单表,再关联订单表和订单商品表,最后对用户的商品数量求和,同样遵循先JOIN再分组聚合的逻辑。