【问题标题】:Performing a running subtraction in T-SQL在 T-SQL 中执行运行减法
【发布时间】:2016-10-06 19:56:12
【问题描述】:

我坐在两张桌子旁(虽然它们是临时表),看起来像这样:

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

两者之间的关系键是ReceiverIdSenderId

Priority 列对于 ReceiverId 来说是唯一的,并且值“1”的优先级高于“2”。

带有SenderId“1”的Payment 行可以用于无限数量的带有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.

【问题讨论】:

  • 在等待答案时,您可能需要仔细阅读this 问题。
  • SQL Server 2014,将其添加到问题中。
  • 仅使用您的样本数据,您的预期结果应该是什么?
  • 我希望这样:1) 付款 1 和 2 将用于部分支付发票 1。2) 付款 3 将用于部分支付发票 3。3) 然后付款 4 将完成发票 3。4) 付款 4 将完全支付发票 4。5) 发票 2 和 5 将完全未支付。
  • 将此添加到问题中,因为换行符在评论中不起作用...

标签: sql sql-server tsql sql-server-2014


【解决方案1】:

主要思想

将您的美元金额视为数轴上的间隔。将您的发票和付款以正确的顺序放在彼此相邻的行上。

发票,收件人/发件人 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         |
+----------+----------+-----------+-----------+

【讨论】:

  • 您不仅发布了一个出色的解决方案,而且您还很好地解释了它!谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-01-28
  • 2014-07-04
  • 2016-01-06
  • 1970-01-01
  • 1970-01-01
  • 2019-01-31
  • 2012-03-31
相关资源
最近更新 更多