在T-SQL

时间:2016-10-06 19:56:12

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

我坐着两张桌子(虽然他们是临时桌子)看起来像这样:

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”。

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.

1 个答案:

答案 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         |
+----------+----------+-----------+-----------+