我坐着两张桌子(虽然他们是临时桌子)看起来像这样:
CREATE TABLE [dbo].[Invoice]
(
[InvoiceId] [int] NOT NULL,
[ReceiverId] [int] NOT NULL,
[Amount] [numeric](19, 2) NOT NULL,
[Priority] [int] NOT NULL
);
GO
CREATE TABLE [dbo].[Payment]
(
[PaymentId] [int] NOT NULL,
[SenderId] [int] NOT NULL,
[Amount] [numeric](19, 2) NOT NULL
);
GO
数据看起来像这样:
发票
InvoiceId ReceiverId Amount Priority
1 1 100.00 1
2 1 100.00 2
3 2 100.00 1
4 2 100.00 2
5 1 200.00 3
付款
PaymentId SenderId Amount
1 1 50.00
2 1 45.00
3 2 95.00
4 2 105.00
收款存储在Payment
中。我的代码的任务是在发件人的发票之间分发Payment.Amount
。
两者之间的关系密钥为ReceiverId
和SenderId
。
Priority
列对于ReceiverId
是唯一的,值“1”的优先级高于“2”。
Payment
“1”的SenderId
行可用于ReceiverId
“1”的无数张发票 - 如果Payment.Amount
中的数量不够所有这些专栏都会根据他们的Priority
付费。
我正在尝试一种不使用循环或游标来编程的方法。有什么建议? (我坐在SQL Server 2014上)。
我的预期输出是:
1) Payment 1 and 2 would be used to partially pay Invoice 1.
2) Payment 3 would be used to partially pay Invoice 3.
3) Payment 4 would then complete invoice 3.
4) Payment 4 would then completely pay invoice 4.
5) Invoice 2 and 5 would be left completely unpaid.
答案 0 :(得分:3)
主要想法
将您的美元金额视为数字行上的间隔。将您的发票和付款按正确的顺序放在彼此相邻的行上。
发票,收件人/发件人ID = 1
|----100---|----100---|--------200--------|----------->
0 100 200 400
ID 1 2 5
付款,收件人/发件人ID = 1
|-50-|-45|-------------------------------------------->
0 50 95
ID 1 2
将两组间隔放在一起(与它们相交):
|----|---|-|----------|-------------------|----------->
0 50 95 100 200 400
现在你有间隔:
From To InvoiceID PaymentID
------------------------------------
0 50 1 1
50 95 1 2
95 100 1
100 200 2
200 400 5
发票,收件人/发件人ID = 2
|----100---|----100---|------------------------------->
0 100 200
ID 3 4
付款,收件人/发件人ID = 2
|--95----|-----105----|------------------------------->
0 95 200
ID 3 4
将两组间隔放在一起(与它们相交):
|--------|-|----------|------------------------------->
0 95 100 200
现在你有间隔:
From To InvoiceID PaymentID
------------------------------------
0 95 3 3
95 100 3 4
100 200 4 4
对于这些时间间隔中的每一个,最多只能有一张发票,最多只能有一笔付款(也可以没有)。查找与每个间隔相对应的发票和付款,并在发票和付款之间建立映射。汇总每张发票的所有付款间隔,您就会知道发票是全部还是部分支付。
为发票和付款单独建立间隔的初始列表是通过运行总计来完成的。
SUM(Amount) OVER (PARTITION BY ReceiverId ORDER BY Priority
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS InvoiceInterval
SUM(Amount) OVER (PARTITION BY SenderId ORDER BY PaymentID
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS PaymentInterval
将这两组相交是一个简单的UNION
。
对于每个间隔,找到相应的发票和付款。一种简单的方法是OUTER APPLY
中的子查询。
让我们把所有这些放在一起。
示例数据
DECLARE @Invoice TABLE
(
[InvoiceId] [int] NOT NULL,
[ReceiverId] [int] NOT NULL,
[Amount] [numeric](19, 2) NOT NULL,
[Priority] [int] NOT NULL
);
DECLARE @Payment TABLE
(
[PaymentId] [int] NOT NULL,
[SenderId] [int] NOT NULL,
[Amount] [numeric](19, 2) NOT NULL
);
INSERT INTO @Invoice(InvoiceId,ReceiverId,Amount,Priority) VALUES
(1, 1, 100.00, 1),
(2, 1, 100.00, 2),
(3, 2, 100.00, 1),
(4, 2, 100.00, 2),
(5, 1, 200.00, 3);
INSERT INTO @Payment(PaymentId, SenderId, Amount) VALUES
(1, 1, 50.00),
(2, 1, 45.00),
(3, 2, 95.00),
(4, 2, 105.00);
<强>查询强>
WITH
CTE_InvoiceIntervals
AS
(
SELECT
I.InvoiceId
,I.ReceiverId AS ClientID
,I.Priority
,SUM(I.Amount) OVER (PARTITION BY I.ReceiverId ORDER BY I.Priority
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS InvoiceInterval
FROM @Invoice AS I
)
,CTE_PaymentIntervals
AS
(
SELECT
P.PaymentId
,P.SenderId AS ClientID
,P.PaymentId AS Priority
,SUM(P.Amount) OVER (PARTITION BY P.SenderId ORDER BY P.PaymentID
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS PaymentInterval
FROM @Payment AS P
)
,CTE_AllIntervals
AS
(
SELECT
ClientID
,InvoiceInterval AS Interval
FROM CTE_InvoiceIntervals
UNION
SELECT
ClientID
,PaymentInterval AS Interval
FROM CTE_PaymentIntervals
)
SELECT *
FROM
CTE_AllIntervals
OUTER APPLY
(
SELECT TOP(1) CTE_InvoiceIntervals.InvoiceId
FROM CTE_InvoiceIntervals
WHERE
CTE_InvoiceIntervals.ClientID = CTE_AllIntervals.ClientID
AND CTE_InvoiceIntervals.InvoiceInterval >= CTE_AllIntervals.Interval
ORDER BY
CTE_InvoiceIntervals.InvoiceInterval
) AS A_Invoices
OUTER APPLY
(
SELECT TOP(1) CTE_PaymentIntervals.PaymentId
FROM CTE_PaymentIntervals
WHERE
CTE_PaymentIntervals.ClientID = CTE_AllIntervals.ClientID
AND CTE_PaymentIntervals.PaymentInterval >= CTE_AllIntervals.Interval
ORDER BY
CTE_PaymentIntervals.PaymentInterval
) AS A_Payments
ORDER BY
ClientID
,Interval;
<强>结果强>
+----------+----------+-----------+-----------+
| ClientID | Interval | InvoiceId | PaymentId |
+----------+----------+-----------+-----------+
| 1 | 50.00 | 1 | 1 |
| 1 | 95.00 | 1 | 2 |
| 1 | 100.00 | 1 | NULL |
| 1 | 200.00 | 2 | NULL |
| 1 | 400.00 | 5 | NULL |
| 2 | 95.00 | 3 | 3 |
| 2 | 100.00 | 3 | 4 |
| 2 | 200.00 | 4 | 4 |
+----------+----------+-----------+-----------+