汇总 SQL 数据库中具有多对多关系的两列

Summarize two columns that have a many-to-many relationship in an SQL database

我有以下数据库 tables TransactionsPayments。数据的结构使得交易和付款都具有多对多关系,它们在查询中连接在一起:

SELECT Transactions.Id, Payments.Id, Transactions.Amount
FROM Transactions 
LEFT JOIN Payments ON Transactions.Id = Payments.TransactionId 

| Transactions.Id | Payments.PaymentId | Transactions.Amount |
| ----------------| -------------------| --------------------|
| 3492            | 123456             | 123                 |
| 3492            | 123457             | 123                 |
| 3493            | 123458             | 300                 |
| 3494            | 123459             | 10                  |
| 3495            | 123459             | 25                  |

我希望能够将 table 简化为如下所示(如果这在 SQL 中完全可行的话)

| Transactions.Id | Payments.PaymentId | Transactions.Amount |
| ----------------| -------------------| --------------------|
| 3492            | 123456, 123457     | 123                 |
| 3493            | 123458             | 300                 |
| 3494, 3495      | 123459             | 35                  |

...甚至没有第三列(如果它降低了复杂性)

| Transactions.Id | Payments.PaymentId |
| ----------------| -------------------|
| 3492            | 123456, 123457     |
| 3493            | 123458             |
| 3494, 3495      | 123459             |

我目前拥有的:

SELECT
Transactions.Id, 
   STUFF((SELECT '; ' + CAST(Payments.Id as varchar)
    FROM Payments 
    WHERE Payments.TransactionId = Transactions.Id
    FOR XML PATH('')), 1, 1, '') [PaymentIds]
FROM Transactions

| Transactions.Id | PaymentIds         |
| ----------------| -------------------|
| 3492            | 123456, 123457     |
| 3493            | 123458             |
| 3494            | 123459             |
| 3495            | 123459             |

如何将 Transactions.Id 添加到逗号分隔列表中? 我正在使用 SQL 服务器 12.0.2000.8

SQLFiddle here

基本上,您必须通过两次才能汇总每笔交易的付款,然后汇总每笔付款的交易。这假设(基于示例数据和所需的输出)当付款应用于多个交易时,您需要总和,但是当交易有多个付款时,您需要最大值(或者在这种情况下它们总是相同的)最大工作正常吗?):

;WITH FirstPass AS 
(
  SELECT t.Id, pId = STUFF(
    (SELECT ', ' + CONVERT(varchar(11), p.Id)
      FROM dbo.Payments AS p
      WHERE p.TransactionId = t.Id
      ORDER BY p.Id
      FOR XML PATH(''), TYPE).value(N'./text()[1]', 
        N'varchar(max)'), 1, 2, ''),
    Amount = MAX(t.Amount)
  FROM dbo.Transactions AS t
  GROUP BY t.Id
)
SELECT [Transactions.Id] = STUFF(
  (SELECT ', ' + CONVERT(varchar(11), fp.Id)
    FROM FirstPass AS fp
    WHERE fp.pId = FirstPass.pId
    ORDER BY fp.pId
    FOR XML PATH(''), TYPE).value(N'./text()[1]', 
        N'varchar(max)'), 1, 2, ''),
  [Payments.PaymentId] = FirstPass.pId,
  Amount = SUM(FirstPass.Amount)
FROM FirstPass 
GROUP BY FirstPass.pId;

输出:

Transactions.Id Payments.PaymentId Amount
3492 123456, 123457 123.00
3493 123458 300.00
3494, 3495 123459 35.00

在 SQL Server 2017+(或 Azure SQL 数据库)上,这变得更加简单,但对于这个特定的用例来说仍然有点复杂。这目前对您没有帮助,但可以帮助今天的其他读者、未来的读者,甚至未来的您:

;WITH FirstPass AS 
(
  SELECT t.Id, ca.pId, Amount = MAX(t.Amount)
  FROM dbo.Transactions AS t
  CROSS APPLY
  (
    SELECT pId = STRING_AGG(p.Id, ', ')
      FROM dbo.Payments AS p
      WHERE p.TransactionId = t.Id
  ) AS ca GROUP BY t.Id, ca.pId
)
SELECT [Transactions.Id] = STRING_AGG(Id, ', '),
  [Payments.PaymentId] = pId,
  Amount = SUM(Amount)
FROM FirstPass 
GROUP BY pId;

相同的输出:

下面的代码生成你想要的输出

WITH cte1 AS(
   SELECT 
       Transactions.Id AS TransactionID, 
       Payments.Id AS PaymentID,
       max(Transactions.amount) Amount
   FROM Transactions 
   LEFT JOIN Payments ON Transactions.Id = Payments.TransactionId 
   GROUP BY Transactions.Id,Payments.Id),
   
   
cte2 AS(
  SELECT

       STUFF((SELECT ',' + cast(t2.TransactionID as varchar(100)) 
       FROM cte1 t2
       WHERE t2.PaymentID = t1.PaymentID
       FOR XML PATH(''),TYPE).value('(./text())[1]','varchar(MAX)'),1,1,'')TransactionID,
     
       STUFF((SELECT ',' + cast(t2.PaymentID as varchar(100)) 
       FROM cte1 t2
       WHERE t2.TransactionID = t1.TransactionID
       FOR XML PATH(''),TYPE).value('(./text())[1]','varchar(MAX)'),1,1,'')PaymentID,
     
       (SELECT sum(Amount)
       FROM cte1 t2 
       WHERE t2.PaymentID = t1.PaymentID
       GROUP BY PaymentID)Amount
     
    
     
   FROM cte1 t1
)

SELECT TransactionID,PaymentID,Amount
FROM cte2
GROUP BY TransactionID,PaymentID,Amount

result