返回

SQL 左连接求和问题排查与解决:获取学生付款总额

mysql

左连接(LEFT JOIN)查询中求和问题排查与解决

最近遇到一个数据库查询需求,要求获取 2024 年购买书籍的学生姓名列表,以及他们为书籍支付的总金额。起初写了下面这条 SQL 语句:

SELECT id_student, name_student, date_insc_student,  payment_student
FROM tblstud
LEFT JOIN 
( SELECT SUM(amount_payment) AS totalpay, idstud_payment FROM tblpay GROUP BY idstud_payment ) AS tblpay 
ON tblpay.idstud_payment = tblstud.id_student 
WHERE year(tblstud.date_insc_student) = 2024 
ORDER BY date_insc_student, name_student 

虽然可以逐行读取数据,但结果集中就是没有 totalpay 这一列。查阅了一些资料,还是没找到头绪。 问题出在哪儿了? 怎么修改才能拿到每个学生支付的总金额? 下面就来好好捋一捋。

一、问题根源分析

问题的核心在于查询语句对结果集的理解有偏差。这条 SQL 语句:

  1. tblstud 表通过 LEFT JOIN 连接了一个子查询。
  2. 子查询计算了 tblpay 表中每个学生的总支付金额(totalpay)。
  3. 主查询选择了 id_student, name_student, date_insc_student, 以及 payment_student (这个字段具体是什么,原始语句里没说明白,我们后面再细说)几个字段,但是, 最重要的, 计算出来的totalpay你没有在select 中展示啊!。

因为 SELECT 列表里没有明确指定 totalpay,所以结果里自然就没有这一列!

二、解决方案

解决这问题,其实挺简单。核心就是把totalpay加到主查询的SELECT列表里。同时,还可以针对不同场景做一些优化。

方案一:直接在 SELECT 列表中添加 totalpay

最直接的方法,就是在 SELECT 后面加上 totalpay。 就像这样:

SELECT id_student, name_student, date_insc_student,  payment_student, tblpay.totalpay
FROM tblstud
LEFT JOIN 
( SELECT SUM(amount_payment) AS totalpay, idstud_payment FROM tblpay GROUP BY idstud_payment ) AS tblpay 
ON tblpay.idstud_payment = tblstud.id_student 
WHERE year(tblstud.date_insc_student) = 2024 
ORDER BY date_insc_student, name_student

原理: 很简单,就是把子查询算出来的 totalpay 列,明确地放到最终结果集里。

代码解释: 就在SELECT 后面加了个 tblpay.totalpay

安全建议: 这个修改本身不涉及啥安全问题,不过平时写 SQL 还是要注意预防 SQL 注入。

方案二:使用 COALESCE 处理 NULL 值

如果有的学生在 tblpay 表里没有支付记录,totalpay 就会是 NULL。 要想让结果更清晰,可以用 COALESCE 函数把 NULL 变成 0:

SELECT id_student, name_student, date_insc_student,  payment_student, COALESCE(tblpay.totalpay, 0) AS total_payment
FROM tblstud
LEFT JOIN 
( SELECT SUM(amount_payment) AS totalpay, idstud_payment FROM tblpay GROUP BY idstud_payment ) AS tblpay 
ON tblpay.idstud_payment = tblstud.id_student 
WHERE year(tblstud.date_insc_student) = 2024 
ORDER BY date_insc_student, name_student

原理: COALESCE(tblpay.totalpay, 0) 的意思是,如果 tblpay.totalpay 不是 NULL,就用它本身的值;如果是 NULL,就用 0。 顺便,还给起了个别名total_payment

代码解释: COALESCE(tblpay.totalpay, 0) AS total_payment 替换了原来的 tblpay.totalpay

方案三:简化查询(如果 payment_student 不需要)

要是 payment_student 这一列其实没啥用,可以整个去掉,把查询简化一下:

SELECT tblstud.id_student, tblstud.name_student, tblstud.date_insc_student, COALESCE(SUM(tblpay.amount_payment), 0) AS total_payment
FROM tblstud
LEFT JOIN tblpay ON tblpay.idstud_payment = tblstud.id_student
WHERE year(tblstud.date_insc_student) = 2024
GROUP BY tblstud.id_student, tblstud.name_student, tblstud.date_insc_student
ORDER BY tblstud.date_insc_student, tblstud.name_student;

原理: 直接在 LEFT JOIN 之后,对 amount_payment 求和,同时用 COALESCE 处理可能出现的 NULL 值。因为要对每个学生求和,所以要用 GROUP BY 把学生相关的字段都列出来。

代码解释:

  • 去掉了原来的子查询。
  • LEFT JOIN 直接连接 tblpay 表。
  • COALESCE(SUM(tblpay.amount_payment), 0) AS total_payment 计算总支付金额。
  • GROUP BY 子句列出了所有非聚合字段。

安全建议: 同样,要注意 SQL 注入风险。

方案四: 排除特定年份无付款记录的学生。

如果需求变更了,不需要显示2024年注册,但是没有任何付款记录的学生。

SELECT tblstud.id_student, 
       tblstud.name_student, 
       tblstud.date_insc_student, 
       SUM(tblpay.amount_payment) AS total_payment
FROM tblstud
INNER JOIN tblpay ON tblpay.idstud_payment = tblstud.id_student
WHERE year(tblstud.date_insc_student) = 2024
  AND year(tblpay.date_payment) = 2024 -- 假设有一个date_payment字段
GROUP BY tblstud.id_student, tblstud.name_student, tblstud.date_insc_student
ORDER BY tblstud.date_insc_student, tblstud.name_student;

原理
LEFT JOIN 更改为 INNER JOIN. INNER JOIN 只会返回两个表中都存在的匹配行。在本例中,这表示只返回在tblstudtblpay表中都有记录的学生。并增加了一个假设tblpay.date_payment的年份筛选.

代码解释:

  • INNER JOIN 替换 LEFT JOIN.
  • WHERE子句中增加 AND year(tblpay.date_payment) = 2024

方案五:关于 "payment_student" 字段的处理(补充)

原查询语句中有一个 payment_student 字段,但没有提供它的定义,根据上下文推断和通用做法,给出几个处理的建议:

  1. 如果 payment_student 是来自 tblstud 表的单个值 (例如,表示首选付款方式):
    只需要像其他 tblstud 的列一样选取它即可,不需要进行特别处理.

  2. 如果payment_student是指学生选择的每次付款的付款方式(例如,现金,卡,支票),并存储在tblpay表中:

    • 如果需要显示所有付款方式:不需要显示付款方式.
    • 如果只需要显示一种主要的付款方式: 可以用聚合函数 (如 MAX),或根据业务逻辑选择合适的付款方式。
  3. 如果payment_student不是以上情况,并且不需要:
    SELECT列表中移除

例子

如果payment_studenttblpay中,想展示每笔付款的方式,但SELECT不需要:

SELECT tblstud.id_student, 
       tblstud.name_student, 
       tblstud.date_insc_student,        
       COALESCE(SUM(tblpay.amount_payment), 0) AS total_payment
FROM tblstud
LEFT JOIN tblpay ON tblpay.idstud_payment = tblstud.id_student
WHERE year(tblstud.date_insc_student) = 2024
GROUP BY tblstud.id_student, tblstud.name_student, tblstud.date_insc_student
ORDER BY tblstud.date_insc_student, tblstud.name_student;

如果 payment_student 含义不明,且不需要,可以直接从 SELECT 中移除它.

总结一下

解决这个问题, 最主要的就是记住把计算结果(totalpay)放到 SELECT 语句里。 根据具体需要,可以用 COALESCE 处理 NULL 值,或者简化查询。选择哪个方案,主要看具体的需求和数据情况。 不同的表结构, 可能存在多种更优解。