Как суммировать кредиты до дебетования SQL Server? - PullRequest
0 голосов
/ 31 августа 2018

Я пытаюсь суммировать все кредиты, полученные до дебета, а затем суммировать все дебеты за кредит в течение 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. затем найдите дебет и сумму вместе до следующего кредита.

Мне нужен только тот случай, когда дата кредита предшествует дате списания. 48 на 8/14 игнорируется, потому что после него нет дебета.

Логика состоит в том, чтобы увидеть, была ли учетная запись зачислена, а затем дебетирована после нее.

Моя попытка

    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 голосов
/ 02 сентября 2018

Теперь должно быть ясно, что вам определенно нужен столбец, в котором указан последовательный порядок транзакций, потому что в противном случае вы не сможете решить, будет ли дебет размещен до или после кредита, когда они оба имеют одинаковый datestart. Предполагая, что у вас есть такой столбец (в моем запросе я назвал его ID), решение может быть следующим, без рекурсии и без самосоединения. Эту проблему можно решить с помощью некоторых оконных функций , доступных с SQL Server 2008.

Мое решение обрабатывает данные в несколько этапов, которые я реализовал в виде последовательности из 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

blockpairs

В этом CTE, как описано выше, самый первый блок отфильтровывается, если это дебетовый блок, и самый последний блок отфильтровывается, если это кредитный блок. При этом количество оставшихся блоков должно быть четным, а логический порядок блоков должен быть последовательностью пар кредитных и дебетовых блоков, каждая из которых начинается с кредитного блока и сопровождается соответствующим дебетовым блоком. Каждая пара кредитных / дебетовых блоков в итоге будет иметь одну строку. Чтобы правильно связать кредитные и дебетовые блоки в запросе, я присваиваю им одинаковые номера, используя отдельные нумерации для каждого типа ( n -й кредитный блок и n -ый дебетовый блок связаны, давая им тот же номер n ). Для этой нумерации я использую функцию DENSE_RANK, чтобы все записи в блоке получали одинаковое число (pairno) и делали нумерацию без пробелов. Для нумерации блоков одного типа я повторно использую описанное выше поле blockno для упорядочения. Результат в вашем примере (снова отсортирован для визуализации):

Result of the blockpairs CTE

Финальный запрос PIVOT

Наконец, credit_amount и debit_amount агрегируются по соответствующим блокам, группирующимся по acct и pairno, а затем отображаются рядом друг с другом, используя запрос PIVOT.

Final result

Хотя столбец pairno не виден, он используется для сортировки полученных записей.

...