如何在 SQL Server 中连接 3 个表并根据 2 个键列计算值

问题描述 投票:0回答:2

我有 3 个表需要连接,其中 2 个对于计算值并在第三个表中提供它很重要,问题是在这 2 个表中我有 2 列需要分组以进行计算。

这些是表格 - 第一个表格:

SELECT customerId, baseCurrencyId, SUM(amount) AS totalRecovered
FROM dbo.RecoveredDebt
WHERE (isDeleted = 0)
GROUP BY customerId, baseCurrencyId;

首次查询结果:

客户ID 基础货币ID 已恢复总数
2 1 950
4 1 10
1 2 100
2 2 500

第二张表:

SELECT customerId, baseCurrencyId, SUM(amount) AS totalOld
FROM dbo.OldDebt
WHERE (isDeleted = 0)
GROUP BY customerId, baseCurrencyId;

第二次查询结果:

客户ID 基础货币ID 总旧
2 1 2000
1 2 100
2 2 300

现在技巧来了,我想从

Customer
表中进行选择并获取这些列:

id (customer id), 
baseCurrencyId, 
totalOldDebt (from first query), 
totalRecoveredDebt (from second query), 
totalDebt (totalOldDebt - totalRecoveredDebt)

但我希望它按

baseCurrencyId
进行分组,例如,如果客户有超过一种货币的债务,则必须对每种货币进行计算,如果一个表中存在包含一种货币的行,但另一个表中没有那么该值必须为零并且计算仍然会发生。

这是我根据提供的表格计算后的预期结果:

id 基础货币ID 旧债总额 已收回债务总额 债务总额
1 2 100 100 0
2 1 2000 950 1050
2 2 300 500 -200
4 1 0 10 -10

我尝试过使用 CTE,但我不知道该怎么做:

WITH OldCTE AS 
(
    SELECT    
        customerId, baseCurrencyId, SUM(amount) AS totalOld
    FROM
        dbo.OldDebt
    WHERE     
        (isDeleted = 0)
    GROUP BY  
        customerId, baseCurrencyId
), 
RecoveredCTE AS 
(
    SELECT    
        customerId, baseCurrencyId, SUM(amount) AS totalRecovered
    FROM
        dbo.RecoveredDebt
    WHERE     
        (isDeleted = 0)
    GROUP BY  
        customerId, baseCurrencyId
)
SELECT C.id
FROM Customer AS C
LEFT JOIN OldCTE AS OCTE ON OCTE.customerId = C.id
LEFT JOIN RecoveredCTE AS RCTE ON RCTE.customerId = C.id
sql sql-server
2个回答
1
投票

如果您想使用 CTE 作为变体:

WITH OldCTE AS (
    SELECT    customerId, baseCurrencyId, SUM(amount) AS totalOld
    FROM      dbo.OldDebt
    WHERE     (isDeleted = 0)
    GROUP BY  customerId, baseCurrencyId
), RecoveredCTE AS (
    SELECT    customerId, baseCurrencyId, SUM(amount) AS totalRecovered
    FROM      dbo.RecoveredDebt
    WHERE     (isDeleted = 0)
    GROUP BY  customerId, baseCurrencyId
)
SELECT C.id,
coalesce(OCTE.totalOld, 0) as totalOldDebt,
coalesce(RCTE.totalRecovered, 0) as totalRecoveredDebt,
coalesce(OCTE.totalOld, 0) - coalesce(RCTE.totalRecovered, 0) as totalDebt
FROM OldCTE AS OCTE
FULL JOIN  RecoveredCTE AS RCTE ON RCTE.customerId = OCTE.customerId and OCTE.baseCurrencyId = RCTE.baseCurrencyId
JOIN Customer as C on C.id = coalesce(OCTE.customerId, RCTE.customerId)

附注我对 sql-server 不太熟悉,但你可能可以在没有 CTE 的情况下编写它:

select 
C.id,
coalesce(OD.baseCurrencyId, RD.baseCurrencyId) as currency,
coalesce(sum(OD.amount), 0) as totalOldDebt,
coalesce(sum(RD.amount), 0) as totalRecoveredDebt,
coalesce(sum(OD.amount), 0) - coalesce(sum(RD.amount), 0) as totalDebt
from dbo.OldDebt as OD
full join dbo.RecoveredDebt as RD on OD.customerId = RD.customerId and OD.baseCurrencyId = RD.baseCurrencyId and RD.isDeleted = 0
join Customer as C on C.id = coalesce(OD.customerId, RD.customerId)
where OD.customerId is null or OD.isDeleted = 0
group by C.id, coalesce(OD.baseCurrencyId, RD.baseCurrencyId)

我也不确定这是否更优化,因为它需要检查查询计划。


0
投票

您需要首先在旧债务和已收回债务之间执行

FULL OUTER JOIN
,收集值,然后与您的客户结合起来。这可以在子选择或另一个 CTE 中完成。 (...或者,正如 Gwaeren 在我之前的另一个答案中发布的那样,只需重新排序连接即可。)

WITH OldCTE AS (
    SELECT    customerId, baseCurrencyId, SUM(amount) AS totalOldDebt
    FROM      dbo.OldDebt
    WHERE     (isDeleted = 0)
    GROUP BY  customerId, baseCurrencyId
), RecoveredCTE AS (
    SELECT    customerId, baseCurrencyId, SUM(amount) AS totalRecoveredDebt
    FROM      dbo.RecoveredDebt
    WHERE     (isDeleted = 0)
    GROUP BY  customerId, baseCurrencyId
), MergedCTE AS (
    SELECT
        COALESCE(R.customerId, O.customerId) AS customerId,
        COALESCE(R.baseCurrencyId, O.baseCurrencyId) AS baseCurrencyId,
        ISNULL(O.totalOldDebt, 0) AS totalOldDebt,
        ISNULL(R.totalRecoveredDebt, 0) AS totalRecoveredDebt
    FROM OldCTE O
    FULL OUTER JOIN RecoveredCTE R
        ON R.customerId = O.customerId
        AND R.baseCurrencyId = O.baseCurrencyId
)
SELECT
    C.Id,
    M.baseCurrencyId,
    M.totalOldDebt,
    M.totalRecoveredDebt,
    M.totalOldDebt - M.totalRecoveredDebt AS totalDebt
FROM Customer AS C
JOIN MergedCTE M
    ON M.customerId = C.Id
ORDER BY
    C.id,
    M.baseCurrencyId

结果:

身份证 基础货币ID 旧债总额 已收回债务总额 债务总额
1 2 100 100 0
2 1 2000 950 1050
2 2 300 500 -200
4 1 0 10 -10

请参阅 this db<>fiddle 进行演示。

附录:您询问了另一条评论,是的,

UNION
是另一种方法:

WITH UnionCTE AS (
    SELECT
        customerId,
        baseCurrencyId,
        SUM(old) AS totalOldDebt,
        SUM(recovered) AS totalRecoveredDebt
    FROM (
        SELECT    customerId, baseCurrencyId, amount AS old, 0 as recovered
        FROM      dbo.OldDebt
        WHERE     (isDeleted = 0)
        UNION ALL
        SELECT    customerId, baseCurrencyId, 0 AS old, amount as recovered
        FROM      dbo.RecoveredDebt
        WHERE     (isDeleted = 0)
    ) U
    GROUP BY  customerId, baseCurrencyId
)
SELECT     C.id,
           UCTE.baseCurrencyId, UCTE.totalOldDebt, UCTE.totalRecoveredDebt,
           UCTE.totalOldDebt - UCTE.totalRecoveredDebt AS totalDebt
FROM       Customer AS C
JOIN       UnionCTE AS UCTE ON UCTE.customerId = C.id
ORDER BY   C.Id, UCTE.baseCurrencyId

参见这个数据库<>小提琴

还有其他方法。如果您的实际查询只需要访问一小部分客户(或者可能一次只访问一个客户),那么使用仅访问与所选客户关联的债务行的

CROSS APPLY
来计算债务可能是有意义的(s)。将您的客户 ID 预选到临时表(或表变量)中并使用它来预过滤债务数据也是一种选择。

© www.soinside.com 2019 - 2024. All rights reserved.