如何选择Amounts不相互取消的行组中的行?

时间:2013-01-25 10:54:26

标签: sql sql-server-2008

给定一些类似于以下临时表中的行:

TransactionId | AccountsDocumentLineId | Amount
-------------   ----------------------   ------
52345           12345                    -15.79
52346           12345                    15.79
52347           12345                    -15.79
52348           22222                    -6.34
52349           22222                    6.34
52350           22222                    6.34
52351           22222                    -6.34
52352           22222                    -8.76
52353           22222                    10.49

如何确保删除与相同 AccountsDocumentLineId相互取消(总和为零)的任何行,并且只删除那些没有更多匹配行的行,以便它们可以配对还剩下吗?

因此,在上面的示例中,TransactionId = 52345 52347)的行将被保留(因为-15.79并且15.79会互相取消。)

同样,TransactionId = 5235252353的行仍将保留(因为两对-6.346.34会相互抵消)

因此,我们会得到以下最终结果:

TransactionId | AccountsDocumentLineId | Amount
-------------   ----------------------   ------
52347           12345                    -15.79
52352           22222                    -8.76
52353           22222                    10.49

注意:我已经删除了所有不必要的细节(更多列和更多行)以简单地显示手头的问题。

我玩过的一些选项是: -

  • 总结一组Amount内的所有AccountDocumentLineId并查看哪一行与余额匹配,但这只会处理第一个示例(仅限于那里)剩下一行)和第二个有两行需要保留(所以不容易拆分一个值来给两行)

  • 浏览AccountDocumentLineId组中的每个条目,如果找到匹配项,请删除两个对应项; 我认为这个可行吗虽然不确定如何在SQL中执行此操作?

SQLFiddle Demo

更新:根据complete answer添加了Bulat's answer

3 个答案:

答案 0 :(得分:1)

第一步很简单

DELETE FROM MYTABLE WHERE AccountsDocumentLineId IN
  (SELECT AccountsDocumentLineId from MYTABLE 
   GROUP BY AccountsDocumentLineId 
   HAVING SUM(Amount) <> 0)

但是你可能会发现你有一些需要做的事情来应对精确错误。

这应该只留给你不平衡的账户。从那里你必须(我认为)创建一个存储过程来“分配”到信用的借记。从您的数据示例中可能看出事务ID很有条理,因此应该有所帮助。

答案 1 :(得分:1)

您可以多次运行此代码(例如,当它影响超过0条记录时):

WITH Matches AS
(
SELECT t1 = t1. TransactionId, t2 = t2.TransactionId, 
  a1 = t1.Amount, a2 = t2.Amount, t1.AccountsDocumentLineId
FROM  Transactions t1 
    INNER JOIN Transactions t2 
        ON t1.AccountsDocumentLineId = t2.AccountsDocumentLineId
           AND t1.Amount = -t2.Amount 
           AND t1.TransactionId < t2.TransactionId 
)
DELETE FROM Transactions
WHERE EXISTS (
SELECT * FROM Matches m1
WHERE Transactions.TransactionId IN(m1.t1,m1.t2)
  AND NOT EXISTS
 (SELECT * FROM Matches m2
  WHERE  abs(m1.a1) = abs(m2.a1) 
     AND m1.AccountsDocumentLineId = m2.AccountsDocumentLineId
     AND (
         (m2.t1 > m1.t1 AND m2.t2 <= m1.t2)  
      OR (m2.t2 < m1.t2 AND (m2.t1 >= m1.t1))
      OR m2.t1 = m1.t2
     ) 
 )
);

答案 2 :(得分:0)

为了回应Bulat对我的包裹代码的请求,我在这里提交(并为了其他人的利益):

DECLARE 
      @SequenceStart INT = 0,
      @SequenceEnd INT = 10 -- Just to be on the safe side, though unlikely for there to be 10 matching pairs for the same AccountsDocumentLineId

STARTLOOP:
    SET
        @SequenceStart = @SequenceStart+1   
    ;
    WITH Matches AS
    (
        SELECT
            L1 = L1.TransactionId, L2 = L2.TransactionId,
            A1 = L1.Amount, A2 = L2.Amount, L1.AccountsDocumentLineId
        FROM
            #RemainingLedgerEntries L1
            INNER JOIN #RemainingLedgerEntries L2
                ON L1.AccountsDocumentLineId = L2.AccountsDocumentLineId
                AND L1.Amount*-1 = L2.Amount
                AND L1.TransactionId < L2.TransactionId
    )
    DELETE FROM
        #RemainingLedgerEntries
    WHERE
        EXISTS
        (
            SELECT
                *
            FROM
                Matches M1
            WHERE
                #RemainingLedgerEntries.TransactionId IN (M1.L1, M1.L2)
            AND NOT EXISTS
            (
                SELECT
                    *
                FROM
                    Matches M2
                WHERE
                    ABS(M1.A1) = ABS(M2.A1) 
                AND M1.AccountsDocumentLineId = M2.AccountsDocumentLineId
                AND
                (
                    (M2.L1 > M1.L1 AND M2.L2 <= M1.L2)  
                    OR (M2.L2 < M1.L2 AND (M2.L1 >= M1.L1))
                    OR M2.L1 = M1.L2
                ) 
            )
        )

    IF @SequenceStart >= @SequenceEnd
        GOTO ENDTASK

    GOTO STARTLOOP

ENDTASK: