删除给定键的总和为零的行

Remove Rows That Sum Zero For A Given Key

我有一个查询会导致在我们的 SSRS 2008 R2 服务器上创建客户账单。 SQL 服务器实例也是 2008 R2。查询很大,出于安全等原因,我不想 post 整个查询。

我需要对下面的示例数据做的是从结果集中删除 73.19 和 -73.19 的两行。因此,如果两行在 LineBalance 列中具有相同的绝对值并且它们的总和为 0 并且如果它们在 REF1 列中具有相同的值,则应从结果集中删除。结果集中仍应返回 REF1 = 14598 和行余额为 281.47 的行,不应返回下面 REF1 = 14598 的其他两行。

这里的重点是 "hide" 会计错误和客户的更正。 "hide" 我的意思是,不要在他们收到的邮件账单上显示。这里发生的事情是客户在本应在 281.47 开具账单时错误地在 73.19 开具账单。所以,我们的 AR 部门。将 73.19 退回他们的帐户并向他们收取正确金额 281.47。如您所见,它们都具有相同的 REF1 值。

它很笨重,但它可以让您识别匹配的负面违规者。您需要从结果集中排除两列中的 sysInvNum

Create table #tmp( SysInvNum int,Ref1 int, LineBalance decimal(8,2))

insert into #tmp
values(3344299,14602,558.83)
,(3344298,14598,281.47)
,(3344297,14602,-95.98)
,(3344296,14598,-73.19)
,(3341758,14598,73.19)

--Select * From #tmp

Select * 
INTO #t1
From #tmp
 Where LineBalance < 0

--Select * From #t1

Select SysInvNum1 = #tmp.SysInvNum, SysInvNum2 = #T1.SysInvNum 
INTO #T2
From #tmp 
LEFT JOIN #t1 On #tmp.Ref1 = #T1.Ref1 and #tmp.LineBalance = -#T1.LineBalance
where #t1.SysInvNum is not null


Select * From 
#tmp
Where SysInvNum not in(
Select SysInvNum1 from #t2
union Select SysInvNum2 from #t2
)

drop table #tmp
drop table #t1
drop table #t2

此解决方案仅适用于 SQL Server 2012,但如果将来有人必须做类似的事情,我决定将其保留在这里,因为它非常简单.

.....

如果总和为 0 并且它们之间没有其他交易(负交易发生在正交易之后),这应该只删除特定收件人的两笔交易。比较保守和安全。

应该有关于交易顺序的信息。类似日期列的东西。在 PARTITION BY 语句中,您应该按此列而不是 PRIMARY KEY 列进行排序。

IF OBJECT_ID('Receipts', 'U') IS NOT NULL
    DROP TABLE Receipts

CREATE TABLE Receipts
(
    SysInvNum INT PRIMARY KEY IDENTITY(1,1), 
    REF1 INT, 
    LineBalance DECIMAL(10,2)
)


INSERT INTO Receipts
values
    (14602,558.83),
    (14598,281.47),
    (14602,-95.98),
    (14598,73.19),
    (14598,-73.19),
    (14598,73.19),
    (14598,73.19),
    (14598, 215.6),
    (14598,73.19)


WITH ghosts AS
(
    SELECT SysInvNum, REF1, LineBalance, 
        LAG(LineBalance, 1, 0) OVER (PARTITION BY REF1 ORDER BY SysInvNum) PreviousLineBalance,
        LEAD(LineBalance, 1, 0) OVER (PARTITION BY REF1 ORDER BY SysInvNum) NextLineBalance
    FROM Receipts
)
SELECT r.SysInvNum, r.REF1, r.LineBalance
FROM Receipts r
JOIN ghosts g On (r.SysInvNum = g.SysInvNum)
WHERE NOT (g.LineBalance + g.PreviousLineBalance = 0 AND g.LineBalance < 0)
    AND NOT (g.LineBalance + g.NextLineBalance = 0 AND g.LineBalance > 0)
ORDER BY r.SysInvNum

我会添加一个字段,其中包含明确的标志,告诉您某笔费用是 mistake/reversal 的错误,然后过滤掉这些行就很简单了。即时执行此操作可能会使您的报告相当缓慢。

但是,要按原样解决给定的问题,我们可以这样做。该解决方案假定 SysInvNum 是唯一的。

使用示例数据创建 table

DECLARE @T TABLE (SysInvNum int, REF1 int, LineBalance money);

INSERT INTO @T (SysInvNum, REF1, LineBalance) VALUES (3344299, 14602, 558.83);
INSERT INTO @T (SysInvNum, REF1, LineBalance) VALUES (3344298, 14598, 281.47);
INSERT INTO @T (SysInvNum, REF1, LineBalance) VALUES (3344297, 14602, -95.98);
INSERT INTO @T (SysInvNum, REF1, LineBalance) VALUES (3344296, 14598, -73.19);
INSERT INTO @T (SysInvNum, REF1, LineBalance) VALUES (3341758, 14598, 73.19);

INSERT INTO @T (SysInvNum, REF1, LineBalance) VALUES (11, 100, 50.00);
INSERT INTO @T (SysInvNum, REF1, LineBalance) VALUES (12, 100, -50.00);
INSERT INTO @T (SysInvNum, REF1, LineBalance) VALUES (13, 100, 50.00);

INSERT INTO @T (SysInvNum, REF1, LineBalance) VALUES (21, 200, -50.00);
INSERT INTO @T (SysInvNum, REF1, LineBalance) VALUES (22, 200, -50.00);
INSERT INTO @T (SysInvNum, REF1, LineBalance) VALUES (23, 200, 50.00);

INSERT INTO @T (SysInvNum, REF1, LineBalance) VALUES (31, 300, -50.00);
INSERT INTO @T (SysInvNum, REF1, LineBalance) VALUES (32, 300, 50.00);
INSERT INTO @T (SysInvNum, REF1, LineBalance) VALUES (33, 300, -50.00);
INSERT INTO @T (SysInvNum, REF1, LineBalance) VALUES (34, 300, 50.00);

INSERT INTO @T (SysInvNum, REF1, LineBalance) VALUES (41, 400, 50.00);
INSERT INTO @T (SysInvNum, REF1, LineBalance) VALUES (42, 400, -50.00);
INSERT INTO @T (SysInvNum, REF1, LineBalance) VALUES (43, 400, 50.00);
INSERT INTO @T (SysInvNum, REF1, LineBalance) VALUES (44, 400, -50.00);
INSERT INTO @T (SysInvNum, REF1, LineBalance) VALUES (45, 400, 50.00);

我添加了几个有多个错误的案例。

编号并计算行数

SELECT
    SysInvNum
    , REF1
    , LineBalance
    , ROW_NUMBER() OVER(PARTITION BY REF1, LineBalance ORDER BY SysInvNum) AS rn
    , COUNT(*) OVER(PARTITION BY REF1, ABS(LineBalance)) AS cc1
FROM @T AS TT

这是结果集:

SysInvNum    REF1    LineBalance    rn    cc1
11           100      50.00         1     3
12           100     -50.00         1     3
13           100      50.00         2     3
21           200     -50.00         1     3
23           200      50.00         1     3
22           200     -50.00         2     3
31           300     -50.00         1     4
32           300      50.00         1     4
33           300     -50.00         2     4
34           300      50.00         2     4
41           400      50.00         1     5
42           400     -50.00         1     5
43           400      50.00         2     5
44           400     -50.00         2     5
45           400      50.00         3     5
3341758      14598    73.19         1     2
3344296      14598   -73.19         1     2
3344298      14598   281.47         1     1
3344297      14602   -95.98         1     1
3344299      14602   558.83         1     1

您可以看到那些有错误的行的计数 > 1。而且,成对的错误具有相同的行号。因此,我们需要 remove/hide 计数 > 1 的行和具有两个相同行号的行。

确定要删除的行

WITH
CTE_rn
AS
(
    SELECT
        SysInvNum
        , REF1
        , LineBalance
        , ROW_NUMBER() OVER(PARTITION BY REF1, LineBalance ORDER BY SysInvNum) AS rn
        , COUNT(*) OVER(PARTITION BY REF1, ABS(LineBalance)) AS cc1
    FROM @T AS TT
)
, CTE_ToRemove
AS
(
    SELECT
        SysInvNum
        , REF1
        , LineBalance
        , COUNT(*) OVER(PARTITION BY REF1, rn) AS cc2
    FROM CTE_rn
    WHERE CTE_rn.cc1 > 1
)
SELECT *
FROM CTE_ToRemove
WHERE CTE_ToRemove.cc2 = 2

这是另一个中间结果:

SysInvNum    REF1    LineBalance    cc2
12           100     -50.00         2
11           100      50.00         2
21           200     -50.00         2
23           200      50.00         2
32           300      50.00         2
31           300     -50.00         2
33           300     -50.00         2
34           300      50.00         2
42           400     -50.00         2
41           400      50.00         2
43           400      50.00         2
44           400     -50.00         2
3344296      14598   -73.19         2
3341758      14598    73.19         2

现在,我们将所有这些放在一起。

最终查询

WITH
CTE_rn
AS
(
    SELECT
        SysInvNum
        , REF1
        , LineBalance
        , ROW_NUMBER() OVER(PARTITION BY REF1, LineBalance ORDER BY SysInvNum) AS rn
        , COUNT(*) OVER(PARTITION BY REF1, ABS(LineBalance)) AS cc1
    FROM @T AS TT
)
, CTE_ToRemove
AS
(
    SELECT
        SysInvNum
        , REF1
        , LineBalance
        , COUNT(*) OVER(PARTITION BY REF1, rn) AS cc2
    FROM CTE_rn
    WHERE CTE_rn.cc1 > 1
)
SELECT *
FROM @T AS TT
WHERE
    TT.SysInvNum NOT IN 
    (
        SELECT CTE_ToRemove.SysInvNum
        FROM CTE_ToRemove
        WHERE CTE_ToRemove.cc2 = 2
    )
ORDER BY SysInvNum;

结果:

SysInvNum    REF1    LineBalance
13           100       50.00
22           200      -50.00
45           400       50.00
3344297      14602    -95.98
3344298      14598    281.47
3344299      14602    558.83

请注意,最终结果没有任何 REF = 300 的行,因为有两个更正的错误,它们完全平衡了彼此。

大多数 AR/billing 系统将 "credit memos"(负数)视为现金,在这种情况下,-73.19 将应用于 73.19 LineBalance,就像客户一样支付了该金额,导致余额为 0 美元。

选项 1:

你们在这个系统中处理现金收据和申请吗?如果是这样,您可以从这些现金申请表中提取数据以显示 SysInvNum 3344296 和 3341758 之间的关系。

选项 2:

我假设 PayAdjust 列用于在客户付款后减少余额,而 LineBalance 是一个计算列,即 Charges + PayAdjust.

大多数情况下,发生这种情况时,应收账款部门将负责将贷记凭证应用到未清发票,因此 PayAdjust 列在两行之间的净额为 0 美元,这会导致LineBalance 在 2 行的每一行上也为 $0。这可能只是正在使用的系统的培训问题。

这会导致有问题的 3 行看起来像这样,所以你没有问题,你只需在查询中添加 where LineBalance <> 0 来排除这些行,因为 AR 部门(应用信用开始,所以知道这个问题的答案)明确说明信用适用于哪个LineBalance

选项 2 首选数据结构:

SysInvNum   REF1        Charges               PayAdjust             LineBalance
----------- ----------- --------------------- --------------------- ---------------------
3344298     14598       281.47                0.00                  281.47
3344296     14598       -73.19                73.19                 0.00
3341758     14598       73.19                 -73.19                0.00

选项 3:

如果没有来自选项 1 或 2 的数据,您会做出许多假设,并且 运行 存在无意中隐藏错误行的风险。

也就是说,这是一个尝试执行您所要求的查询,但我强烈建议您与 AR 部门联系,看看他们是否可以更新这些记录 "PayAdjust"。

我添加了几个可能导致问题的场景测试用例,但这可能无法涵盖所有​​基础。

对于相同的 REF1 和相同的 DueDate,此查询将仅隐藏找到一个不同的匹配负值作为正值的行。它还确保原始收费发票 ID 在贷记之前,因为可以假设贷记不会在实际收费之前发生(测试用例 6 仍然显示两行,因为贷记具有在收费之前发生的 SysInvNum ).如果每个 REF1, DueDate, and LineBalance 找到多次匹配,则它不会隐藏相应的费用和信用额度(测试用例 2 和 4)。测试用例 3 的总和为 0,但它仍然显示所有 3 行,因为 LineBalance 值不完全匹配。这些都是我为处理边缘情况所做的假设,因此可以根据需要进行调整。

CREATE TABLE #SysInvTable (SysInvNum int not null primary key, REF1 int, Charges money, PayAdjust money, LineBalance as Charges + PayAdjust, DueDate date, REF2 int, Remark varchar(50), REM varchar(50));

INSERT INTO #SysInvTable(SysInvNum, REF1, Charges, PayAdjust, DueDate, Remark) 
VALUES
    --.....................................
    --Your test case
      (3344298, 14598, 281.47, 0, '2014-12-08','Your original test case. This one should stay.')
    , (3344296, 14598, -73.19, 0, '2014-12-08',null)
    , (3341758, 14598, 73.19, 0, '2014-12-08',null)
    --.....................................
    --Test case 2: How do you match these up? 
    , (2001, 2, 73.19, 0, '2015-01-06','Charge 2.1')
    , (2002, 2, 73.19, 0, '2015-01-06','Charge 2.2')
    , (2003, 2, 73.19, 0, '2015-01-06','Charge 2.3')
    , (2004, 2, -73.19, 0, '2015-01-06','Credit for charge 2.3')
    , (2005, 2, -73.19, 0, '2015-01-06','Credit for charge 2.1') 
    --.....................................
    --Test case 3
    , (3001, 3, 73.19, 0, '2015-01-06','Charge 3.1')
    , (3002, 3, 73.19, 0, '2015-01-06','Charge 3.2')
    , (3003, 3, -146.38, 0, '2015-01-06','Credit for charges 3.1 and 3.2') 
    --.....................................
    --Test case 4: Do you hide 4001 or 4002? 
    , (4001, 4, 73.19, 0, '2015-01-06','Cable')
    , (4002, 4, 73.19, 0, '2015-01-06','Internet')
    , (4003, 4, -73.19, 0, '2015-01-06','Misc Credit')
    --.....................................
    --Test case 5: remove all lines except the first
    , (5000, 5, 9.99, 0, '2015-01-06','Charge 5.0 (Should stay)')
    , (5001, 5, 11.11, 0, '2015-01-06','Charge 5.1')
    , (5002, 5, 22.22, 0, '2015-01-06','Charge 5.2')
    , (5003, 5, 33.33, 0, '2015-01-06','Charge 5.3')
    , (5004, 5, -11.11, 0, '2015-01-06','Credit for charge 5.1')
    , (5005, 5, -33.33, 0, '2015-01-06','Credit for charge 5.3') 
    , (5006, 5, -22.22, 0, '2015-01-06','Credit for charge 5.2') 
    --.....................................
    --Test case 6: credit occurs before charge, so keep both
    , (6000, 6, -73.19, 0, '2015-01-06','Credit occurs before charge')
    , (6001, 6, 73.19, 0, '2015-01-06','Charge 6.1')
;

SELECT i.* 
FROM #SysInvTable i
WHERE i.SysInvNum not in 
(
    SELECT IngoreInvNum = case when c.N = 1 then max(t.SysInvNum) else min(t2.SysInvNum) end
    FROM #SysInvTable t
    INNER JOIN #SysInvTable t2 
        ON t.ref1 = t2.ref1 
        AND t.DueDate = t2.DueDate
    CROSS APPLY (SELECT 1 AS N UNION ALL SELECT 2 as N) AS c --used to both both T and T2 SysInvNum's to exclude
    WHERE 1=1
        AND t.LineBalance > 0 AND t2.LineBalance < 0
        AND t.SysInvNum < t2.SysInvNum --make sure the credit came in after the positive SysInvNum 
        AND t.LineBalance = t2.LineBalance * -1
    GROUP BY t.REF1, t.DueDate, abs(t.LineBalance), c.n
    HAVING Count(*) = 1
)
;

DROP TABLE #SysInvTable;