如何在借记SQL Server之前对任何贷项进行总计?

时间:2018-08-31 19:31:42

标签: sql-server tsql transactions running-total

我正在尝试对发生在借方之前的所有贷方进行总计,然后对4天之内贷方之后的所有借方进行总计。

表格

ACCT |Date      | Amount  | Credit or debit
-----+----------+---------+----------------
 152 |8/14/2017 |   48    |   C
 152 |8/12/2017 |   22.5  |   D
 152 |8/12/2017 |   40    |   D
 152 |8/11/2017 |  226.03 |   C
 152 |8/10/2017 |  143    |   D
 152 |8/10/2017 |  107.23 |   C
 152 |8/10/2017 |   20    |   D
 152 |8/10/2017 |   49.41 |   C

我的查询仅应在借方之前有贷方的情况下求和。结果将包含上面数据的3行。

所需的输出:

acct            DateRange                   credit_amount    debit_amount  
--------------------------------------------------------------------------
152            2017-10-14 to 2017-10-18       49.41            20
152            2017-10-14 to 2017-10-18      107.23            143
152            2017-10-14 to 2017-10-18      226.03            62.5

最后一个是对两个借方求和,直到有贷方为止。

  1. 首先找到第一笔信用。
  2. 如果在借方之前有多于1个,则
  3. 求和。
  4. 然后找到借方并加总直到下一个贷方。

我只需要信贷日期在借方日期之前的情况。 8/14上的48被忽略,因为其后没有借方。

逻辑是查看帐户是否已贷记,然后再扣除。

我的尝试

    DECLARE @StartDate DATE
    DECLARE @EndDate DATE
    DECLARE @OverallEndDate DATE

    SET @OverallEndDate = '2017-08-14'
    SET @StartDate = '2017-08-10'
    SET @EndDate = dateadd(dd, 4, @startDate);

    WITH Dates
    AS (
     SELECT @StartDate AS sd, @EndDate AS ed, @OverallEndDate AS od
     UNION ALL
     SELECT dateadd(dd, 1, sd), DATEADD(dd, 1, ed), od
     FROM Dates
     WHERE od > sd
     ), credits
    AS (
     SELECT DISTINCT A.Acct, LEFT(CONVERT(VARCHAR, @StartDate, 120), 10) + 'to' + LEFT(CONVERT(VARCHAR, @EndDate, 120), 10) AS DateRange, credit_amount, debit_amount
     FROM (
       SELECT t1.acct, sum(amount) AS credit_amount, MAX(t1.datestart) AS c_datestart
       FROM [Transactions] T1
       WHERE Credit_or_debit = 'C' AND T1.Datestart BETWEEN @StartDate AND @EndDate AND T1.[acct] = '152' AND T1.Datestart <= (
           SELECT MIN(D1.Datestart)
           FROM [Transactions] D1
           WHERE T1.acct = D1.acct AND D1.Credit_or_debit = 'D' AND D1.Datestart BETWEEN @StartDate AND @EndDate
           )
       GROUP BY T1.acct
       ) AS A
     CROSS JOIN (
       SELECT t2.acct, sum(amount) AS debit_amount, MAX(t2.datestart) AS c_datestart
       FROM [Transactions] T2 AND T2.DBCR = 'D' AND T2.Datestart BETWEEN @StartDate AND @EndDate AND T2.[acct] = '152' AND T2.Datestart <= (
           SELECT MAX(D1.Datestart)
           FROM [Transactions] D1
           WHERE T2.acct = D1.acct AND D1.Credit_or_debit = 'D' AND D1.Datestart BETWEEN @StartDate AND @EndDate
           )
       GROUP BY T2.acct
       ) AS B
     WHERE A.acct = B.acct AND A.c_datestart <= B.d_datestart
     )
    SELECT *
    FROM credits
    OPTION (MAXRECURSION 0)

更新:

存储的日期实际上是带有时间戳的日期。这就是我验证借方是否>贷方的方式。

1 个答案:

答案 0 :(得分:-1)

现在应该清楚地知道,您肯定需要指定一列交易顺序的列,因为否则,当它们都具有相同的datestart时,您将无法决定是在借方之前还是之后记入借方。假设您有这样一列(在我的查询中,我将其命名为ID),则可以采用以下解决方案,而无需递归并且也不需要自连接。可以使用SQL Server 2008以来提供的一些 window函数来解决该问题。

我的解决方案按照几个步骤来处理数据,这些步骤由2个CTE和最终的PIVOT查询序列实现:

DECLARE @StartDate DATE = '20170810';
DECLARE @EndDate DATE = dateadd(dd, 4, @StartDate);
DECLARE @DateRange nvarchar(24);

SET @DateRange = 
  CONVERT(nvarchar(10), @StartDate, 120) + ' to '
  + CONVERT(nvarchar(10), @EndDate, 120);

WITH 
  blocks (acct, CD, amount, blockno, r_blockno) AS (
    SELECT acct, Credit_or_debit, amount
    ,  ROW_NUMBER() OVER (PARTITION BY acct ORDER BY ID ASC)
     - ROW_NUMBER() OVER (PARTITION BY acct, Credit_or_debit ORDER BY ID ASC)
    ,  ROW_NUMBER() OVER (PARTITION BY acct ORDER BY ID DESC)
     - ROW_NUMBER() OVER (PARTITION BY acct, Credit_or_debit ORDER BY ID DESC)
    FROM Transactions
    WHERE datestart BETWEEN @StartDate AND @EndDate
      AND Credit_or_debit IN ('C','D') -- not needed, if always true
  ),
  blockpairs (acct, CD, amount, pairno) AS (
    SELECT acct, CD, amount
    , DENSE_RANK() OVER (PARTITION BY acct, CD ORDER BY blockno)
    FROM blocks
    WHERE (blockno > 0 OR CD = 'C') -- remove leading debits
      AND (r_blockno > 0 OR CD = 'D') -- remove trailing credits
  )
SELECT acct, @DateRange AS DateRange
, amt.C AS credit_amount, amt.D AS debit_amount
FROM blockpairs PIVOT (SUM(amount) FOR CD IN (C, D)) amt
ORDER BY acct, pairno;

这是它的工作方式:

阻止

此处,从表中检索了相关数据,这意味着应用了日期范围过滤器,并且Credit_or_debit列上的另一个过滤器确保结果中仅包含值C和D(如果表格中的情况就是这样,那么WHERE子句的那一部分可以省略)。此CTE的必要部分是两个行号(blockno)之差。贷方和借方分别编号,并从总行号中减去它们各自的行号。在连续的借方或贷方块中,每个记录的这些数字将相同,而在以后的同一类型的块中,这些数字将不同(较高)。此编号的主要用途是识别第一个块(编号0),以便能够将其排除在外 如果是借方冻结,则在下一步中进行进一步处理。为了能够识别最后一块(如果是贷方块,则将其过滤掉),以相反的顺序进行编号(r_blockno)。结果(我订购的只是为了使用您的示例数据进行可视化)将如下所示:

Result of the blocks CTE

块对

如前所述,在此CTE中,如果第一个块是借方块,则将其过滤掉,如果是贷方块,则将最后一个块滤除。这样做,剩余的块数必须是偶数,并且块的逻辑顺序必须是一系列贷方和借方块,每对从贷方块开始,然后是其相关的借方块。每对贷方/借方块最后都会导致一行。为了在查询中正确关联贷方和借方冻结,我通过使用每种类型的单独编号(第 n 个贷方冻结和 n -th借记卡块的编号相同( n )来关联它们。对于此编号,我使用DENSE_RANK函数,使块中的所有记录都获得相同的编号(pairno),并使编号无间隙。为了对相同类型的块进行编号,我将上述的blockno字段重新用于排序。示例中的结果(再次进行可视化排序):

Result of the blockpairs CTE

最终的PIVOT查询

最后,将credit_amountdebit_amount汇总到按acctpairno分组的各个块上,然后使用{{1}并排显示}查询。

Final result

尽管PIVOT列不可见,但它用于对结果记录进行排序。