SQL Server - 根据复杂的匹配条件在另一个表中插入行(包括FIFO)

时间:2015-12-05 23:01:03

标签: sql sql-server sql-server-2008-r2

我正在研究一个复杂的问题 - Bill Marking for Aging Analysis。数据是这样的:

来源表:

string S, K, generated;
cout << "Enter the message: ";
cin >> S;
cout << "Enter the key: ";
cin >> K;
cout << "The message is: " << S << endl; // output the string
int seed = 0;
for(int i = 0; i < (int) K.length(); i++)
    seed += K[i]; // used to generate a certain sequence of numbers
srand(seed); // new seed per new key
cout << "The cipher is: ";
for(int i = 0; i < (int) S.length(); i++) {
    int R = rand() % K.length();
    char L = 65 + (S[i] - 65 + K[R] - 65) % 26;
    cout << L;
    generated += L; // to actually have something to use for decryption
}
// FINALLY, to reach to this step and do something like this took me over 2 hours of continuous debugging
cout << endl;
cout << "The message again is: ";
for(int i = 0; i < (int) generated.length(); i++) {
    int R = rand() % K.length();
    char L = 65 + (generated[i] - 65 + K[R] - 65) % 26;
    cout << L;
}
cout << endl;

因此,总借记:13,000和总信用额:11,000和总余额为2,000借方。

因此,目标是根据日期(FIFO)使用相应的借记记录标记所有信用记录,并将每个记录的标记方案存储在单独的表中,如下所示。

目标表具有每条记录的标记详细信息

目标表

TrxnID, Date, CustomerID, DebitCredit, Amount 
-----------------------------------------------
D1      01-Apr, RAS12,        D,        2000 
D2      01-Apr, RAS12,        D,        3000
C3      02-Apr, RAS12,        C,        4000
D4      03-Apr, RAS12,        D,        5000
C5      04-Apr, RAS12,        C,        1000
C6      10-Apr, RAS12,        C,        6000
D7      25-Apr, RAS12,        D,        3000

我一直在使用老式的基于游标的方法,这也变得相当复杂,但相信必须有一套基于机制的机制来处理这个问题。

非常感谢任何帮助。我们正在使用SQL Server 2008 R2。

由于

2 个答案:

答案 0 :(得分:0)

我有一个解决方案,但可能需要使用比7个给定行更多的数据进行测试。

  1. 首先将源表与自身交叉连接。这将给出债务和信用行的所有组合(7 * 7 = 49)。然后仅选择第一个源表中的debet行和第二个源表中的信用行。我们有4个debet行和3个credit行,因此这会产生12行的输出表。每个债务行将链接到3个信用行。添加每个债务交易ID的计入金额的运行总计(我的演示代码中的列&#34;可用&#34;)。添加一个列,其中包含所有先前债务金额的总和(将所有行的债务金额与较低的债务交易ID相加)。这一栏我命名为#34; UsedBefore&#34;。减去&#34; UsedBefore&#34;来自&#34;可用&#34;给予&#34; Remains&#34;。然后从&#34; Remains&#34;中减去当前的债务金额。给予&#34; RemainsAfter&#34;

  2. 使用此表作为第二个选择的输入。现在只选择&#34; Remains&#34;大于0.添加金额为&#34;付费&#34;的列。如果&#34;仍然是&#34;大于债务金额这将是债务金额,否则&#34; Remains&#34;量。添加一个row_number,对每个debet transacton id组中的行进行编号。

  3. 使用结果表作为第三个选择的输入。现在只选择row_number等于1或者数量为&#34; RemainsAfter&#34;小于或等于0.您现在拥有所需的行但尚未记入正确的金额。

  4. 使用上一个选择的结果作为第四个选择的输入。添加一列&#34; PaidNow&#34;。这是&#34;付费&#34;减去在同一债务交易ID组内支付的先前金额。

  5. 试试这个:

    select tab3.DebetTrnxID
    , tab3.CreditTrnxID
    , paid - sum(paid) over (partition by debetTrnxID order by rn) + paid as PaidNow
    from (
        select * from (
            select * 
            , row_number() over (partition by tab.DebetTrnxID order by tab.CreditDate, tab.CreditTrnxID) as rn
            , case when remains >= debetamount then 
                DebetAmount
            else    
                Remains
            end as Paid
            from (
                select a.TrnxID as DebetTrnxID
                , a.Date as DebetDate
                , a.DebitCredit as DebetDebetCredit
                , a.Amount as DebetAmount
                , b.TrnxID as CreditTrnxID 
                , b.Date as CreditDate
                , b.DebitCredit as CreditDebetCredit
                , b.Amount as CreditAmount
                , sum(b.amount) over (partition by a.trnxid order by a.Date, a.TrnxId, b.Date, b.TrnxID) as Available
                , isnull((select sum(c.amount) from source c where c.DebitCredit='D' and c.TrnxID<a.TrnxID),0) as UsedBefore
                ,  sum(b.amount) over (partition by a.trnxid order by a.Date, a.TrnxId, b.Date, b.TrnxID) 
                - isnull((select sum(c.amount) from source c where c.DebitCredit='D' and c.TrnxID<a.TrnxID),0) as Remains
                ,  sum(b.amount) over (partition by a.trnxid order by a.Date, a.TrnxId, b.Date, b.TrnxID) 
                - isnull((select sum(c.amount) from source c where c.DebitCredit='D' and c.TrnxID<a.TrnxID),0) 
                - a.amount as RemainsAfter
    
                from source a
                cross join source b
    
                where a.DebitCredit='D'
                and b.DebitCredit='C'
                ) tab
            where remains>0
            ) tab2
        where tab2.rn=1
        or tab2.RemainsAfter<=0
        ) tab3
    order by tab3.DebetTrnxID
    , tab3.CreditTrnxID
    

    阿尔伯特

答案 1 :(得分:0)

我只能使用游标解决问题。以下是以下步骤:

  1. 创建一个带有借方和贷方总和的光标,按客户分组 - 这样就会给我一个客户列表,为其调整运行

  2. 启动循环

  3. 为Debit&amp;创建单独的游标。该客户的信用个人记录

  4. 按顺序运行循环,输入正确的逻辑并确保借记和贷记记录正确匹配 - 任何匹配的记录都会插入到单独的表中。这是一个逻辑流程问题,然后是技术问题,以确保只有所需的光标(借方或贷方或两者)移动到下一条记录,只有匹配的余额。

  5. 对于超过600,000条记录,整个过程在不到4分钟的时间内完成,这是非常可接受的

  6. 结论:有时基于光标的操作比基于操作的操作更快

    --step 1 
    select cv.customercode,sum(case when cv.drcr='D' then cv.Amount else 0
     end) as PendAmountDr, 
        sum(case when cv.drcr='C' then cv.Amount else 0 end) as PendAmountCr               
       From SourceTable cv              
    Group by cv.CustomerCode 
    having sum(case when cv.drcr='D' then cv.Amount else 0 end) <> 0 
        and sum(case when cv.drcr='C' then cv.Amount else 0 end) <> 0 
    --Use Fetch Commands & While Loop  
    --STEP 2 : Create Cursor for Debit Records for that customer order by date
    While @@Fetch_Status = 0
       DECLARE CVDR CURSOR READ_ONLY FORWARD_ONLY STATIC FOR                            
                select TRANSACTIONID, cv.Amount                                    
                From Cashvchr cv              
                where  cv.CompanyID=@CompanyID and isnull(cv.deleted,0) = 0 
                AND isnull(cv.CustomerCode,'') = @Cust 
                AND CV.DRCR = 'D' 
                ORDER BY VCHRDATE,TRANSACTIONID
    --STEP 3 : Create Cursor for Credit Records for that customer order by date
    DECLARE CVCR CURSOR READ_ONLY FORWARD_ONLY STATIC FOR                            
                select TRANSACTIONID, cv.Amount                                    
                From Cashvchr cv              
                where  cv.CompanyID=@CompanyID and isnull(cv.deleted,0) = 0 
                AND isnull(cv.CustomerCode,'') = @Cust 
                AND CV.DRCR = 'C' 
                ORDER BY VCHRDATE,TRANSACTIONID
    
            set @Balamtdr = @amtdr 
            set @balamtcr = @amtcr
    
            WHILE @AMTTOMARK > 0 
                BEGIN                   
    
                    SET @CVID = @CVIDDR 
                    SET @MCVID = @CVIDCR
                    SET @CURRMARKAMT=0
                    set @skipdr = 0
                    set @skipcr = 0
                    IF @BALAMTDR > @BALAMTCR  -- i.e. balance debit amount to be marked is bigger from bal.credit amt THEN skip CREDIT
                        begin 
                            SET @CURRMARKAMT = @balAMTCR                                    
                            set @skipCr = 1
                            set @balamtdr = @balamtdr - @balamtcr 
                        end
                    ELSE IF @balAMTDR < @balAMTCR -- i.e. balance Credit amount is bigger THEN skip Debit
                        begin 
                            SET @CURRMARKAMT = @balAMTDR    
                            set @skipdr  = 1
                            set @balamtcr = @balamtCr - @balamtdr 
                        end
                    ELSE -- i.e. balance Credit & Debit amount is same, mark and skip both 
                        begin 
                            SET @CURRMARKAMT = @balamtdr
                            set @balamtcr = @balamtCr - @balamtdr 
                            set @skipdr = 1
                            set @skipcr = 1
                        end
                    ----
                    INSERT INTO TargetTable 
                        (CASHVCHRID,AMOUNT,MARKEDCASHVCHRID)                        VALUES 
                        (@CVIDDR,@CURRMARKAMT,@CVIDCR) 
    
                    set @amttomark = @amttomark - @currmarkamt 
    
                    if @skipdr = 1 and @amttomark > 0 
                        begin 
                            FETCH next from CVDR INTO @CVIDDR, @AMTDR
                            if @@fetch_status <> 0
                                begin
                                    set @amttomark = 0 -- no further marking possible some problem, should exit
                                end
                            else
                                begin
                                    set @balamtdr = @amtdr
                                end
                            -- end if 
                        end
                    -- end if @skipdr = 1 
                    if @skipcr = 1 and @amttomark > 0 
                        begin 
                            FETCH next from CVCR INTO @CVIDCR, @AMTCR   
                            if @@fetch_status <> 0
                                begin
                                    set @amttomark = 0 -- no further marking possible some problem, should exit
                                end
                            else
                                begin
                                    set @balamtcr = @amtcr
                                end
                            -- end if 
    
                        end
                    -- end if @skipcr = 1 
                END
            -- END WHILE @AMTTOMARK > 0 FOR DR/CR skip FOR ONE CUSTOMER