Я пытаюсь создать повторно используемый запрос (хранимую процедуру), который будет вводить номер счета и дату и возвращать остаток единицы набора транзакций на основе количества транзакций, переведенных в эту дату или ранее:
Пример транзакции:
Параметры:
@Account int = 200
@Date date = 'Feb 1, 2020'
Счет 200
Account, trxid, transacted_units, transactiontype, transferfrom, transferto, date
200, 3, 40, buy, NULL, NULL, 12/1/2019
200, 4, 30, buy, NULL, NULL, 12/2/2019
200, 5, 7, sell, NULL, NULL, 12/3/2019
200, 6, 50, **transfer out**, NULL, 100, 1/2/2020
В приведенном выше примере я ожидаю, что мой результаты должны быть: (Новый столбец units_bal)
Account, trxid, transacted_units, **units_bal**, transactiontype, transferfrom, transferto, date
200, 3, 40, **33**, buy, NULL, NULL, 12/1/2019
200, 4, 30, **17**, buy, NULL, NULL, 12/2/2019
200, 5, 7, **0**, sell, NULL, NULL, 12/3/2019
200, 6, **50**, NULL, **transfer out**, NULL, 100, 1/2/2020
Шаг 1: Выполнить расчеты транзакций на основе FIFO для покупки и продажи - в порядке, есть две сделки покупки, затем продажа до передача происходит. Таким образом, продажа 7 единиц будет применяться к первой покупке 50 единиц, оставляя приемлемые единицы равными 43 для первой транзакции, а вторая покупка - нетронутой на уровне 30 единиц.
Шаг 2: из оставшихся отвечающих критериям единиц рассчитайте распределение 50 переданных единиц в порядке FIFO. Первая транзакция имеет 33 из 50 подходящих единиц, а вторая транзакция требует только 17 из 30 единиц для переноса.
Правило, согласно которому значение строки переноса всегда будет равно сумме всех баланс единиц в счете.
У меня есть код, который частично работает в этом примере, но две основные проблемы:
- Это очень неэффективно (мне нужно запустить его для записей 300k )
- Когда у меня несколько уровней входных и выходных переходов, происходит сбой
- При переводе из меньших единиц, чем имеет счет (частичный перевод), unit_bal неверен.
- см. Примеры счетов 300, 400 и 900
SELECT *
INTO #Transactions
FROM (
SELECT 200 account, 3 trxid, 40 transacted_units, 'buy' transactiontype, '' transferto, '' transferfrom, cast('2019-12-01' as date) [date]
UNION ALL
SELECT 200 account, 4 trxid, 30 transacted_units, 'buy' transactiontype, '' transferto, '' transferfrom, '2019-12-02' [date]
UNION ALL
SELECT 200 account, 5 trxid, 7 transacted_units, 'sell' transactiontype, '' transferto, '' transferfrom, '2019-12-03' [date]
UNION ALL
SELECT 200 account, 6 trxid, 63 transacted_units, 'tro' transactiontype, 100 transferto, '' transferfrom, '2020-01-01' [date]
UNION ALL
SELECT 300 account, 4 trxid, 250 transacted_units, 'buy' transactiontype, '' transferto, '' transferfrom, '2019-12-02' [date]
UNION ALL
SELECT 300 account, 4 trxid, 1000 transacted_units, 'buy' transactiontype, '' transferto, '' transferfrom, '2019-12-05' [date]
UNION ALL
SELECT 300 account, 5 trxid, 510 transacted_units, 'sell' transactiontype, '' transferto, '' transferfrom, '2019-12-10' [date]
UNION ALL
SELECT 300 account, 6 trxid, 100 transacted_units, 'tro' transactiontype, 500 transferto, '' transferfrom, '2020-01-05' [date]
UNION ALL
SELECT 400 account, 7 trxid, 2500 transacted_units, 'buy' transactiontype, '' transferto, '' transferfrom, '2019-12-10' [date]
UNION ALL
SELECT 400 account, 8 trxid, 2490 transacted_units, 'tro' transactiontype, 600 transferto, '' transferfrom, '2019-12-20' [date]
UNION ALL
SELECT 900 account, 9 trxid, 1200 transacted_units, 'buy' transactiontype, '' transferto, '' transferfrom, '2019-12-10' [date]
UNION ALL
SELECT 900 account, 10 trxid, 150 transacted_units, 'sell' transactiontype, '' transferto, '' transferfrom, '2019-12-11' [date]
UNION ALL
SELECT 900 account, 10 trxid, 300 transacted_units, 'tro' transactiontype, 1000 transferto, '' transferfrom, '2019-12-12' [date]
) a
/* Get Last record "Transfer Out" */
;with cte_last_record as (
select *, last_value(trxid) over (partition by account order by [date] ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) last_trxid
from #Transactions
)
/* Get rank */
, cte_rnk as (
select *,
CASE
WHEN transacted_units*IIF(transactiontype='buy', 1, -1) < 0 THEN 0 --change sign of units out to negative
ELSE ROW_NUMBER() OVER (PARTITION BY account ORDER BY transacted_units*IIF(transactiontype='buy', 1, -1) ASC, [date] ASC)
END rnk
from cte_last_record
)
/* Get cumulative */
, cte_cumulative as (
select *,
case
when trxid=last_trxid then 0
else sum(iif(trxid=last_trxid, 0, transacted_units*IIF(transactiontype='buy', 1, -1))) over(partition by account order by rnk, [date])
end cumulative
from cte_rnk
)
select *,
case
when cumulative>0 and transacted_units>=cumulative then cumulative
when cumulative>0 and transacted_units<cumulative then transacted_units
else 0
end units_bal
from cte_cumulative
order by account, [date]
drop table #Transactions