PAYATHON 2026

从依赖 JOIN 表的子查询中获取 SUM 值

支付阿杰

结论

先按 customer_id 分别汇总销售额和付款额,再把两份汇总结果连接到 customers。不要在同一层同时连接销售明细和付款明细。两边都是一对多关系,直接连接会产生重复行,导致 SUM() 重复计算。

SELECT
    c.id,
    c.company,
    COALESCE(s.total_sales, 0) AS total_sales,
    COALESCE(p.total_payments, 0) AS total_payments
FROM customers AS c
LEFT JOIN (
    SELECT
        i.customer_id,
        SUM(t.amount) AS total_sales
    FROM invoices AS i
    JOIN invoice_transactions AS t
        ON t.invoice_id = i.id
    GROUP BY i.customer_id
) AS s
    ON s.customer_id = c.id
LEFT JOIN (
    SELECT
        ap.customer_id,
        SUM(t.amount) AS total_payments
    FROM account_payments AS ap
    JOIN invoice_transactions AS t
        ON t.id = ap.transaction_id
    GROUP BY ap.customer_id
) AS p
    ON p.customer_id = c.id;

错误原因

下面这段写法:

(SELECT SUM(amount) FROM sales)

不会引用外层查询中名为 sales 的表别名。SQL 会把 FROM sales 解释为查询一张实际存在且名为 sales 的表,因此 MySQL 会报错:

#1146 - Table 'db.sales' doesn't exist

这和子查询是否先于 JOIN 执行无关。表别名只在其所属的查询块中有效,不能当作临时表写入另一个查询块的 FROM 子句。

相关子查询可以引用外层别名的列,例如 sales.amount,但不能写成 FROM sales。

为什么不能直接在外层求和

即使把查询改成:

SUM(sales.amount),
SUM(payments.amount)

并补上 GROUP BY,结果仍可能不正确。

假设某个客户有:

  • 3 条销售交易
  • 2 条付款交易

同时连接这两组明细后,会得到 3 × 2 = 6 行。每笔销售出现两次,每笔付款出现三次,两个总额都会被放大。

所以,两类数据都要先汇总成“每位客户一行”,再进行连接。

关联关系

销售额的关联路径是:

customers.id
    ← invoices.customer_id
    ← invoice_transactions.invoice_id

对应的连接条件是:

t.invoice_id = i.id

付款额的关联路径是:

customers.id
    ← account_payments.customer_id
    → account_payments.transaction_id
    → invoice_transactions.id

对应的连接条件是:

t.id = ap.transaction_id

原查询使用了:

ON i.invoice_id = sales.id

但这与给出的表结构不一致,因为 invoices 中列出的是 id,不是 invoice_id。如果实际数据库使用了不同的字段名,应以真实字段为准进行调整。

注意事项

使用 LEFT JOIN 可以保留没有销售记录或付款记录的客户。没有匹配记录时,聚合结果是 NULL,可以通过:

COALESCE(total, 0)

将其转换为 0。

为提高连接和聚合效率,通常应为以下列建立索引:

invoices.customer_id
invoice_transactions.invoice_id
account_payments.customer_id
account_payments.transaction_id

如果 invoices.id 和 invoice_transactions.id 是主键,通常已经有索引。

这段查询还假定每条销售交易只通过一个 invoice_id 归属客户,并且每条付款交易不会被 account_payments 重复引用。如果数据中允许重复关联,需要先确定业务上的去重规则。不能直接使用 SUM(DISTINCT amount),因为两笔金额相同的合法交易也会被错误地合并。

备注:内容仅供参考。