PAYATHON 2026

MySQL:如何在多表查询中计算每个 Trip 的 Payments 总额?

支付老李

结论

先按 trips_tripID 汇总 payments.amount,再将汇总结果连接到 Trip。由于 seminar 是可选关系,从 people_has_trips 连接到 seminar 时应使用 LEFT JOIN。

建议先聚合 Payments。这样即使其他一对多表中有多条相关记录,付款金额也不会被重复计算:

SELECT
    pht.people_PersonID,
    pht.trips_tripID,
    s.seminarname,
    ar.agent,
    COALESCE(pay.total_amount, 0) AS Amount
FROM people_has_trips AS pht
INNER JOIN trips AS t
    ON t.tripID = pht.trips_tripID
LEFT JOIN seminar AS s
    ON s.seminarID = pht.seminar_seminarID
INNER JOIN agentref AS ar
    ON ar.agentID = t.agentref_agentID
LEFT JOIN (
    SELECT
        trips_tripID,
        SUM(amount) AS total_amount
    FROM payments
    GROUP BY trips_tripID
) AS pay
    ON pay.trips_tripID = t.tripID;

为什么要先汇总 Payments

payments 与 trips 通常是一对多关系。一条 Trip 可以对应多笔付款,直接连接这两个表时,每笔付款都会产生一行结果。

下面的子查询先按 Trip 汇总付款:

SELECT
    trips_tripID,
    SUM(amount) AS total_amount
FROM payments
GROUP BY trips_tripID

汇总后,每个 Trip 只保留一行。外层查询连接这个结果时,Amount 就是该 Trip 的付款总额。

使用 LEFT JOIN 可以保留还没有付款记录的 Trip。此时汇总金额为 NULL,因此需要使用:

COALESCE(pay.total_amount, 0)

将其显示为 0。

让没有 Seminar 的记录也出现在结果中

可选外键应使用 LEFT JOIN:

LEFT JOIN seminar AS s
    ON s.seminarID = pht.seminar_seminarID

这样会保留所有 people_has_trips 记录。没有关联 Seminar 时,seminarname 为 NULL,person 和 trip 仍会出现在结果中。

如果需要筛选 Seminar,要注意 WHERE 条件的位置。例如:

WHERE s.seminarname = 'Example'

这个条件会排除没有 Seminar 的记录,使 LEFT JOIN 的实际效果变成 INNER JOIN。如果筛选条件只用于限制参与连接的 Seminar,应将其写在连接条件中:

LEFT JOIN seminar AS s
    ON s.seminarID = pht.seminar_seminarID
   AND s.seminarname = 'Example'

也可以直接使用 GROUP BY

如果查询中没有其他可能产生重复行的一对多连接,也可以直接聚合:

SELECT
    pht.people_PersonID,
    pht.trips_tripID,
    s.seminarname,
    ar.agent,
    COALESCE(SUM(p.amount), 0) AS Amount
FROM people_has_trips AS pht
INNER JOIN trips AS t
    ON t.tripID = pht.trips_tripID
LEFT JOIN seminar AS s
    ON s.seminarID = pht.seminar_seminarID
INNER JOIN agentref AS ar
    ON ar.agentID = t.agentref_agentID
LEFT JOIN payments AS p
    ON p.trips_tripID = t.tripID
GROUP BY
    pht.people_PersonID,
    pht.trips_tripID,
    s.seminarname,
    ar.agent;

这种写法更短,但要确认其他连接不会导致付款记录成倍增加。

特别注意 agentnotes

原查询还连接了 agentnotes:

INNER JOIN agentnotes
    ON trips.tripID = agentnotes.Trips_tripID

如果一个 Trip 有多条 agentnotes,每笔 Payment 都会被复制多次。此时直接执行 SUM(payments.amount),得到的金额会偏大。

如果结果中既不需要备注字段,也不需要按备注筛选,最好删除这个连接。如果只是要求 Trip 至少有一条备注,可以改用 EXISTS,以免付款记录被复制:

WHERE EXISTS (
    SELECT 1
    FROM agentnotes AS an
    WHERE an.Trips_tripID = t.tripID
)

如果必须同时返回多条备注,就需要单独聚合 Payments,并明确结果是一行对应一个 Trip,还是一行对应一条备注。对于“一人参加了哪些 Trip,以及每个 Trip 总共花了多少钱”这一需求,先按 Trip 聚合 Payments 更稳妥。

备注:内容仅供参考。