返回

MySQL 多表关联查询与聚合运算实战技巧

mysql

MySQL 多表关联查询与聚合运算实战

多表关联查询及聚合运算是数据库应用的常见需求,有效运用可提高数据处理效率。面临多表数据整合与计算,构建合理的 SQL 语句是核心。例如用户数据分布在不同的表里(userslessonsjobsoffers),为了得到统计结果, 进行排序展示。下面将探讨这个问题,分析其中的要点并提供具体方案。

场景及难点分析

用户(users)、课程(lessons)、工作(jobs)、工作邀请(offers)信息存储在各自独立的表里,且用户表记录着用户的关键数据(姓名)。每个课程记录着总费用(totalPay),jobs表和offers表仅作简单计数即可,如果相应的数据行不存在, 返回 0,并且计算结果要用用户表的其它字段呈现。lessons表做 SUM 操作,并且表间关系为:users表通过usersid分别与其余三张表关联,进行 LEFT JOIN,保证无论其余三张表有无数据, 都能返回users表所有的数据。 统计得到课程费用总额 (totalSum)、jobs表数据总数(greens)、offers表数据总数(golds)后需要做如下运算并对结果排序:

  user_score = 0;
  if (greens>0 && golds>0){
     user_score = (greens/golds) * totalSum;
     user_score = round(user_score);
  }

最后返回usersiduser_score和用户的姓名及其他必要信息,以user_score降序排列。这里关键在于合理构建 JOIN 语句、运用聚合函数(SUM, COUNT)、正确处理 NULL 值(赋予 0 值)以及公式计算,最后以计算得出的user_score作为排序依据,整合出最终结果。

方案一: 分步查询与计算

思路:按步骤拆解需求,针对各个表先进行聚合查询,随后汇总计算。

步骤:

  1. 单独查询lessons表,得到每个用户的totalSum
  2. 单独查询jobs表,得到每个用户的greens
  3. 单独查询offers表,得到每个用户的golds
  4. users表为主表,将上述三个查询结果通过usersid关联,使用COALESCE函数处理空值为0。
  5. 应用计算公式计算user_score
  6. 根据user_score排序。
-- Step 1: 查询 lessons 表
CREATE TEMPORARY TABLE LessonSummary AS
SELECT usersid, COALESCE(SUM(totalPay), 0) AS totalSum
FROM lessons
GROUP BY usersid;

-- Step 2: 查询 jobs 表
CREATE TEMPORARY TABLE JobSummary AS
SELECT usersid, COUNT(jobsid) AS greens
FROM jobs
GROUP BY usersid;

-- Step 3: 查询 offers 表
CREATE TEMPORARY TABLE OfferSummary AS
SELECT usersid, COUNT(offersid) AS golds
FROM offers
GROUP BY usersid;

-- Step 4 & 5 & 6: 关联查询并计算 user_score
SELECT 
    u.usersid,
    u.name,
    COALESCE(ls.totalSum, 0) AS totalSum,
    COALESCE(js.greens, 0) AS greens,
    COALESCE(os.golds, 0) AS golds,
    CASE
        WHEN COALESCE(js.greens, 0) > 0 AND COALESCE(os.golds, 0) > 0 THEN ROUND((COALESCE(js.greens, 0) / COALESCE(os.golds, 0)) * COALESCE(ls.totalSum, 0))
        ELSE 0
    END AS user_score
FROM users u
LEFT JOIN LessonSummary ls ON u.usersid = ls.usersid
LEFT JOIN JobSummary js ON u.usersid = js.usersid
LEFT JOIN OfferSummary os ON u.usersid = os.usersid
ORDER BY user_score DESC;

使用临时表的好处在于可以清晰看到数据的流转步骤。不过使用临时表也可能会在一定程度上增加服务器开销。如果业务环境比较在意单次执行速度,可以参照方案二,直接对子查询结果进行组合和计算。

方案二: 子查询与公式计算

思路:将每个表的聚合结果作为子查询,与users表关联后,统一进行计算。

步骤:

  1. users表分别与lessonsjobsoffers表的子查询结果进行 LEFT JOIN。
  2. 子查询里分别得到每个表的统计值,并且使用COALESCE把统计结果为 NULL 的变为 0。
  3. JOIN 之后, 应用计算公式得到user_score
  4. 根据user_score对整个结果排序。
SELECT
    u.usersid,
    u.name,
    COALESCE(ls.totalSum, 0) AS totalSum,
    COALESCE(js.greens, 0) AS greens,
    COALESCE(os.golds, 0) AS golds,
    CASE
        WHEN COALESCE(js.greens, 0) > 0 AND COALESCE(os.golds, 0) > 0 THEN ROUND((COALESCE(js.greens, 0) / COALESCE(os.golds, 0)) * COALESCE(ls.totalSum, 0))
        ELSE 0
    END AS user_score
FROM
    users u
LEFT JOIN
    (SELECT usersid, SUM(totalPay) AS totalSum FROM lessons GROUP BY usersid) ls ON u.usersid = ls.usersid
LEFT JOIN
    (SELECT usersid, COUNT(jobsid) AS greens FROM jobs GROUP BY usersid) js ON u.usersid = js.usersid
LEFT JOIN
    (SELECT usersid, COUNT(offersid) AS golds FROM offers GROUP BY usersid) os ON u.usersid = os.usersid
ORDER BY
    user_score DESC;

这个方案比上一个方案执行起来效率高。在实际选择时需要注意, MySQL对复杂 SQL 语句的优化不一定有预期好, 具体效果如何还得进行基准测试后才好定论。

安全建议

防范 SQL 注入是重中之重, 杜绝手动拼接 SQL 语句。处理用户输入时要进行输入验证,或者使用参数化查询。生产环境不要轻易调整表结构或者修改运行中系统的配置,表结构修改需要做好备份,调整配置应该尽量避免高峰时间段, 必要情况下需要回滚预案。执行权限的管理也必不可少。不要用太高级别的用户运行后台程序,用户要设置强口令。