有没有办法使用窗口函数来简化此查询?

时间:2016-05-26 18:59:30

标签: tsql ssms-2014

我有一些数据,现实世界的例子。为了以特定方式显示数据,数据以这样的方式存储,即我必须操纵它以获得我想要的结果。基本上,有两种不同类型的发票:预发票(P),其中金额在订单处理之前开帐单;和标准发票(I),其余部分在发货后开帐单。在一般情况下,您可以预期标准发票会从其中减去预发票金额,例如,如果预发票是2美元而订单是10美元,那么标准发票将是8美元。相反,标准发票存储为10美元。

以下是一个完全可操作的查询(该查询可操作!它是一个陷阱!),它们填充数据并返回我想要的结果。目标是获取发票金额,如果是发票,则退还该金额;但如果它是标准发票,请使用"用完"预发票的价值并返回一个新的金额,最小零。我已经包含了六种方案,因为预发票可以是任何金额,并且在技术上可以随时要求。

对此的任何帮助将不胜感激。我尝试了一些窗口函数,包括UNBOUNDED PRECEDING类型的东西,但它似乎总是需要递归并进入无限循环。因此,我的蛮力方法,下面。

DECLARE @PData TABLE (
     CustNum        INT
    ,TransNum       INT
    ,InvType        NVARCHAR(1)
    ,InvAmt         DECIMAL(5,2)
    ,CRank          INT
    ,TRank          INT
    ,ModInvAmt      DECIMAL(5,2)
    )

INSERT INTO @PData (
     CustNum
    ,TransNum
    ,InvType
    ,InvAmt
    )

    VALUES
     (124, 1,'P',2)
    ,(124, 2,'I',10)
    ,(124, 3,'I',10)
    ,(153, 4,'I',10)
    ,(153, 5,'P',2)
    ,(153, 6,'I',10)
    ,(324, 7,'I',10)
    ,(324, 8,'I',10)
    ,(324, 9,'P',2)
    ,(441,10,'P',12)
    ,(441,11,'I',10)
    ,(441,12,'I',10)
    ,(455,13,'I',10)
    ,(455,14,'P',12)
    ,(455,15,'I',10)
    ,(667,16,'I',10)
    ,(667,17,'I',10)
    ,(667,18,'P',12)

UPDATE pd1
    SET CRank = pd2.CDR
        FROM @PData pd1
            JOIN (SELECT CustNum, TransNum, DENSE_RANK() OVER (ORDER BY CustNum) AS CDR
                    FROM @PData) pd2
                ON pd1.TransNum = pd2.TransNum

UPDATE pd1
    SET TRank = pd2.TDR
        FROM @PData pd1
            JOIN (SELECT CustNum, TransNum, DENSE_RANK() OVER (PARTITION BY CustNum ORDER BY TransNum) AS TDR
                    FROM @PData) pd2
                ON pd1.TransNum = pd2.TransNum

DECLARE  @Counter1      INT
        ,@Counter2      INT
        ,@CBal          DECIMAL(5,2)
        ,@TNum          INT
        ,@IAmt          DECIMAL(5,2)

SET @Counter1 = 0

WHILE @Counter1 < (SELECT MAX(CRank) FROM @PData)
    BEGIN
        SET @Counter1 += 1
        SET @CBal = 0
        SET @Counter2 = 0
            WHILE @Counter2 < (SELECT MAX(TRank) FROM @PData WHERE CRank = @Counter1)
                BEGIN
                    SET @Counter2 += 1
                    SET @TNum = (SELECT TransNum FROM @PData WHERE CRank = @Counter1 AND TRank = @Counter2)
                    SET @IAmt = (SELECT InvAmt FROM @PData WHERE TransNum = @TNum)
                    IF (SELECT InvType FROM @PData WHERE TransNum = @TNum) = 'P'
                        BEGIN
                            UPDATE @PData SET ModInvAmt = @IAmt WHERE TransNum = @TNum
                            SET @CBal += -@IAmt 
                        END
                    ELSE
                        BEGIN
                            UPDATE @PData SET ModInvAmt = (ABS(@IAmt+@CBal)+(@IAmt+@CBal))/2 -- MINIMUM = 0
                                 WHERE TransNum = @TNum
                            SET @CBal += (@IAmt - (ABS(@IAmt+@CBal)+(@IAmt+@CBal))/2)
                        END                 
                END
    END

SELECT   CustNum
        ,TransNum
        ,InvType
        ,InvAmt
        ,ModInvAmt
 FROM @PData

以下是我得到的结果: enter image description here

我通常不会报告原始发票金额 - 只是新发票金额 - 但我已将其包含在此处,以便更清楚地了解它的变化。

 CustNum TransNum InvType InvAmt  ModInvAmt
 124      1         P     2.00      2.00
 124      2         I    10.00      8.00
 124      3         I    10.00     10.00
 153      4         I    10.00     10.00
 153      5         P     2.00      2.00
 153      6         I    10.00      8.00
 324      7         I    10.00     10.00
 324      8         I    10.00     10.00
 324      9         P     2.00      2.00
 441     10         P    12.00     12.00
 441     11         I    10.00      0.00
 441     12         I    10.00      8.00
 455     13         I    10.00     10.00
 455     14         P    12.00     12.00
 455     15         I    10.00      0.00
 667     16         I    10.00     10.00
 667     17         I    10.00     10.00
 667     18         P    12.00     12.00

1 个答案:

答案 0 :(得分:1)

我会使用常见的表表达式进行递归。

祝你好运 彼得

DECLARE @PData TABLE (
     CustNum        INT
    ,TransNum       INT
    ,InvType        NVARCHAR(1)
    ,InvAmt         DECIMAL(5,2)
    )

INSERT INTO @PData (
     CustNum
    ,TransNum
    ,InvType
    ,InvAmt
    )

    VALUES
 (124, 1,'P',2)
,(124, 2,'I',10)
,(124, 3,'I',10)
,(153, 4,'I',10)
,(153, 5,'P',2)
,(153, 6,'I',10)
,(324, 7,'I',10)
,(324, 8,'I',10)
,(324, 9,'P',2)
,(441,10,'P',12)
,(441,11,'I',10)
,(441,12,'I',10)
,(455,13,'I',10)
,(455,14,'P',12)
,(455,15,'I',10)
,(455,19,'I',10)
,(667,16,'I',10)
,(667,17,'I',10)
,(667,18,'P',12)

;WITH Data as (
    SELECT
        CustNum,
        TransNum,
        InvType,
        InvAmt, 
        ROW_NUMBER() OVER (PARTITION BY custNum ORDER BY Transnum ASC) row,
        CASE WHEN InvType = 'P' THEN cast(-1*InvAmt AS DECIMAL(5,2)) ELSE 0 END prepaidAmt
    FROM 
        @PData
), modified as(
    SELECT 
        CustNum,
        TransNum,
        InvType,
        InvAmt,
        prepaidAmt,
        row, 
        InvAmt  total
    FROM Data d1 
        WHERE row = 1
    UNION ALL
    SELECT      
        d2.CustNum,
        d2.TransNum,
        d2.InvType,
        d2.InvAmt,
        CASE 
        WHEN 
            d2.InvAmt+m.prepaidAmt  < 0 
        THEN  
            CAST (d2.InvAmt+m.prepaidAmt AS DECIMAL(5,2))  
        ELSE 
            CASE 
            WHEN 
                d2.invtype = 'P' 
            THEN 
                CAST(-1*d2.invamt AS DECIMAL(5,2)) 
            ELSE 
                0 
            END   
        END ,
        d2.row, 
        CASE 
        WHEN 
            d2.InvAmt+m.prepaidAmt <0 
        THEN 
            0 
        ELSE  
            CAST( d2.InvAmt+m.prepaidAmt AS DECIMAL(5,2))  
        END
    FROM Data d2
    JOIN modified m 
    ON 
        m.CustNum = d2.CustNum and 
        m.row = d2.row-1
)
SELECT 
    m.CustNum,
    m.TransNum,
    m.InvType,
    m.InvAmt,
    m.total
FROM modified m
ORDER BY 
    custnum, 
    transnum