在雪花中创建摊销计划
Creating an amortization schedule in snowflake
我在 snowflake 中有一个视图,它提供了以下内容:
- 贷款日期
- 贷款金额
- 到期日
- 付款频率(每周、每两周、每半个月、每月)
- 支付金额
如果你愿意的话,我想从中生成一种摊销时间表。因此,如果我有一笔贷款,日期为 2022 年 1 月 1 日,到期日为 2022 年 3 月 9 日,支付频率为每两周支付一次,每次支付 50 美元,我希望看到如下输出:
LoanID
Payment Date
Payment Amount
Payment Frequency
abc123
1/15/2022
.00
biweekly
abc123
1/29/2022
.00
biweekly
abc123
2/12/2022
.00
biweekly
abc123
2/26/2022
.00
biweekly
abc123
3/09/2022
.00
biweekly
我假设我需要某种循环,同时付款日期 < 到期日和总和(付款金额) < 贷款金额,但我不确定如何正确设置它以查看数千笔贷款.你们能提供的任何帮助都将是不可思议的,我非常感激!
你可以通过写一个Recursive CTE, just remember that the default is limited to 100 iterations, if you need more loops then check this MAX_RECURSIONS参数来得到它。
这只是一个代码示例,您应该扩展它以包括一些极端的数据保护;
示例数据:
CREATE OR REPLACE TABLE LoanTable (
LoanID STRING,
Loan_date DATE,
Loan_amount NUMERIC(12,2),
Maturity_date DATE,
Payment_frequency STRING,
Payment_amount NUMERIC(12,2)
);
INSERT INTO LoanTable
VALUES ('abc123', '1/1/2022', 250, '3/9/2022', 'biweekly', 50);
查询:
WITH Recursive_CTE AS (
SELECT LoanID,
CASE Payment_frequency WHEN 'weekly' THEN DATEADD(WEEK, 1, Loan_date)
WHEN 'biweekly' THEN DATEADD(WEEK, 2, Loan_date)
WHEN 'semimonthly' THEN DATEADD(DAY, 15, Loan_date) -- I don't know how the semimonthly value is determined??
WHEN 'monthly' THEN DATEADD(MONTH, 1, Loan_date) END AS Payment_Date,
Payment_amount,
Loan_amount - Payment_amount AS Left_to_pay,
Payment_frequency,
Maturity_date
FROM LoanTable
UNION ALL
SELECT LoanID,
CASE Payment_frequency WHEN 'weekly' THEN DATEADD(WEEK, 1, Payment_Date)
WHEN 'biweekly' THEN DATEADD(WEEK, 2, Payment_Date)
WHEN 'semimonthly' THEN DATEADD(DAY, 15, Payment_Date) -- I don't know how the semimonthly value is determined??
WHEN 'monthly' THEN DATEADD(MONTH, 1, Payment_Date) END AS Payment_Date,
Payment_amount,
IFF(Left_to_pay - Payment_amount < 0, Left_to_pay, Left_to_pay - Payment_amount) AS Left_to_pay,
Payment_frequency,
Maturity_date
FROM Recursive_CTE
WHERE Left_to_pay > 0
)
SELECT LoanID, IFF(Payment_Date > Maturity_date, Maturity_date, Payment_Date) AS Payment_Date, Payment_amount, Left_to_pay, Payment_frequency
FROM Recursive_CTE
ORDER BY LoanID, Payment_Date;
Table生成器是另一种方法。
感谢 Simon 改进了这个解决方案。尊重!
WITH CTE_MY_DATE AS
(SELECT DATEADD(DAY, row_number() over (order by null)-1, '1900-01-01')::date AS MY_DATE FROM table(generator(rowcount => 18000)))
SELECT
date(MY_DATE) CALENDAR_DATE,
concat( decode(extract ('dayofweek_iso', date(MY_DATE)),1,'Monday',2, 'Tuesday',3, 'Wednesday',4, 'Thursday',5, 'Friday',6, 'Saturday',7, 'Sunday'),TO_CHAR(date(MY_DATE), ', MMMM DD, YYYY')) FULL_DATE_DESC
,row_number() over (partition by 1 order by calendar_date ) MOD_IS_COOL
FROM
CTE_MY_DATE
where
CALENDAR_DATE
between '2022-01-02' and '2022-09-03'
qualify
mod(MOD_IS_COOL, 14) = 0
下面是如何通过 JavaScript UDF 进行摊销,以及如何调用它的示例。我在从函数中获取 JSON 时遇到了一些麻烦,因此 return 将其编辑为文本字符串,去除双引号,将其展平,然后转换为 Table。也许更擅长 JavaScript 的人可以将其修改为 return table 预清理。
CREATE OR REPLACE FUNCTION AMORTIZATIONTABLE("AMOUNTFINANCED" FLOAT, "INTEREST" FLOAT, "PERIODS" FLOAT)
RETURNS STRING
LANGUAGE javascript
AS $$
const annuity = (AMOUNTFINANCED, INTEREST, PERIODS) => AMOUNTFINANCED * (INTEREST / (1 - (1 + INTEREST)**(-PERIODS)));
const balance_t = (AMOUNTFINANCED, INTEREST, P) => {
const period_movements = {
base: AMOUNTFINANCED
}
period_movements.interest = AMOUNTFINANCED * INTEREST;
period_movements.amortization = P - (AMOUNTFINANCED * INTEREST);
period_movements.annuity = P;
period_movements.final_value = Math.round((AMOUNTFINANCED - period_movements.amortization) * 100) / 100;
return period_movements;
}
const display_mortgage = (AMOUNTFINANCED, INTEREST, PERIODS) => {
var data = [];
const payements = annuity(AMOUNTFINANCED, INTEREST, PERIODS);
let movements = balance_t(AMOUNTFINANCED, INTEREST, payements);
while (movements.final_value > -.01) {
data.push(movements);
movements = balance_t(movements.final_value, INTEREST, payements);
}
return data;
}
data2 = display_mortgage(AMOUNTFINANCED, INTEREST, PERIODS);
return JSON.stringify(data2);
$$;
SELECT
INDEX + 1 AS Period,
a.VALUE:base AS CurrPrincipalBal,
a.VALUE:annuity AS TotalPayment,
a.VALUE:amortization AS PrincipalPmt,
a.VALUE:interest AS InterestPmt,
a.VALUE:final_value AS NewPrincipalBal
FROM
(SELECT * FROM TABLE(flatten(INPUT => SELECT parse_json(REPLACE(AMORTIZATIONTABLE(20000.00, 0.04, 12.00),'"',''))))) AS a;
所以我想我可以使用 table 生成器来编写这个“清洁器”。
公平地说,我觉得这比递归 CTE 更清晰。
需要注意的是,您需要为我拥有的1000
插入“可能的最大贷款期限”。
双月刊是通过计算每月选项之间的天数来完成的,取“一半”,奇数天使用 15 是正常的。
但像那样:
WITH loans_table(loanid, loan_date, loan_amount,
maturity_date, payment_frequency,
payment_amount) as (
SELECT * FROM VALUES
('abc123', '2022-01-01'::date, 250, '2022-03-09'::date, 'biweekly', 50)
), table_of_numbers as (
SELECT row_number() over(order by null) as rn
FROM TABLE(generator(ROWCOUNT => 1000))
/* that 1000 should be larger than any loan perdiod length you have */
), loan_enrich as (
SELECT
*
,CASE Payment_frequency
WHEN 'weekly' THEN 7
WHEN 'biweekly' THEN 14
WHEN 'semimonthly' THEN 14
WHEN 'monthly' THEN 28
END as period_lo_days
,datediff('day', loan_date, maturity_date) as loan_days
,CEIL(loan_days / period_lo_days) as loan_periods
FROM loans_table
)
SELECT
l.loanid,
CASE payment_frequency
WHEN 'weekly' THEN dateadd('week', r.rn, l.loan_date)
WHEN 'biweekly' THEN dateadd('week', r.rn * 2, l.loan_date)
WHEN 'semimonthly' THEN
case r.rn%2
when 0 then dateadd('month', r.rn/2, l.loan_date)
when 1 then dateadd('days', floor(datediff('days', dateadd('month', (r.rn-1)/2, l.loan_date), dateadd('month', (r.rn+1)/2, l.loan_date))/2), dateadd('month', (r.rn-1)/2, l.loan_date))
end
WHEN 'monthly' THEN dateadd('month', r.rn, l.loan_date)
END as payment_date,
l.payment_amount,
l.payment_frequency
FROM loan_enrich AS l
JOIN table_of_numbers AS r
ON l.loan_periods >= r.rn
ORDER BY 1, r.rn;
给出:
LOANID
PAYMENT_DATE
PAYMENT_AMOUNT
PAYMENT_FREQUENCY
abc123
2022-01-15
50
biweekly
abc123
2022-01-29
50
biweekly
abc123
2022-02-12
50
biweekly
abc123
2022-02-26
50
biweekly
abc123
2022-03-12
50
biweekly
所以这可以提升,让 semimonthly15 总是 15 天后,我们可以做一些过滤,以防行数超过我们需要的,我们可以显示处理最终付款的逻辑比以前的付款少:
WITH loans_table(loanid, loan_date, loan_amount,
maturity_date, payment_frequency,
payment_amount) as (
SELECT * FROM VALUES
('abc123', '2022-01-01'::date, 250, '2022-03-09'::date, 'biweekly', 50),
('abc124', '2022-01-01'::date, 249, '2022-03-09'::date, 'semimonthly', 50),
('abc125', '2022-01-01'::date, 249, '2022-03-09'::date, 'semimonthly15', 50)
), table_of_numbers as (
SELECT row_number() over(order by null) as rn
FROM TABLE(generator(ROWCOUNT => 1000))
/* that 1000 should be larger than any loan perdiod length you have */
), loan_enrich as (
SELECT
*
,CASE Payment_frequency
WHEN 'weekly' THEN 7
WHEN 'biweekly' THEN 14
WHEN 'semimonthly' THEN 14
WHEN 'semimonthly15' THEN 14
WHEN 'monthly' THEN 28
END as period_lo_days
,datediff('day', loan_date, maturity_date) as loan_days
,CEIL(loan_days / period_lo_days) as loan_periods
FROM loans_table
)
SELECT
l.loanid,
CASE payment_frequency
WHEN 'weekly' THEN dateadd('week', r.rn, l.loan_date)
WHEN 'biweekly' THEN dateadd('week', r.rn * 2, l.loan_date)
WHEN 'semimonthly' THEN
case r.rn%2
when 0 then dateadd('month', r.rn/2, l.loan_date)
when 1 then dateadd('days', floor(datediff('days', dateadd('month', (r.rn-1)/2, l.loan_date), dateadd('month', (r.rn+1)/2, l.loan_date))/2), dateadd('month', (r.rn-1)/2, l.loan_date))
end
WHEN 'semimonthly15' THEN
case r.rn%2
when 0 then dateadd('month', r.rn/2, l.loan_date)
when 1 then dateadd('days', 15, dateadd('month', (r.rn-1)/2, l.loan_date))
end
WHEN 'monthly' THEN dateadd('month', r.rn, l.loan_date)
END as payment_date,
l.payment_amount,
l.payment_frequency,
l.loan_amount,
l.loan_amount - least(l.loan_amount, l.payment_amount * r.rn) as still_to_pay,
least(l.loan_amount - least(l.loan_amount, l.payment_amount * (r.rn-1)), l.payment_amount) as this_payment
FROM loan_enrich AS l
JOIN table_of_numbers AS r
ON l.loan_periods >= r.rn
WHERE this_payment > 0
ORDER BY 1, r.rn
LOANID
PAYMENT_DATE
PAYMENT_AMOUNT
PAYMENT_FREQUENCY
LOAN_AMOUNT
STILL_TO_PAY
THIS_PAYMENT
abc123
2022-01-15
50
biweekly
250
200
50
abc123
2022-01-29
50
biweekly
250
150
50
abc123
2022-02-12
50
biweekly
250
100
50
abc123
2022-02-26
50
biweekly
250
50
50
abc123
2022-03-12
50
biweekly
250
0
50
abc124
2022-01-16
50
semimonthly
249
199
50
abc124
2022-02-01
50
semimonthly
249
149
50
abc124
2022-02-15
50
semimonthly
249
99
50
abc124
2022-03-01
50
semimonthly
249
49
50
abc124
2022-03-16
50
semimonthly
249
0
49
abc125
2022-01-16
50
semimonthly15
249
199
50
abc125
2022-02-01
50
semimonthly15
249
149
50
abc125
2022-02-16
50
semimonthly15
249
99
50
abc125
2022-03-01
50
semimonthly15
249
49
50
abc125
2022-03-16
50
semimonthly15
249
0
49
我在 snowflake 中有一个视图,它提供了以下内容:
- 贷款日期
- 贷款金额
- 到期日
- 付款频率(每周、每两周、每半个月、每月)
- 支付金额
如果你愿意的话,我想从中生成一种摊销时间表。因此,如果我有一笔贷款,日期为 2022 年 1 月 1 日,到期日为 2022 年 3 月 9 日,支付频率为每两周支付一次,每次支付 50 美元,我希望看到如下输出:
LoanID | Payment Date | Payment Amount | Payment Frequency |
---|---|---|---|
abc123 | 1/15/2022 | .00 | biweekly |
abc123 | 1/29/2022 | .00 | biweekly |
abc123 | 2/12/2022 | .00 | biweekly |
abc123 | 2/26/2022 | .00 | biweekly |
abc123 | 3/09/2022 | .00 | biweekly |
我假设我需要某种循环,同时付款日期 < 到期日和总和(付款金额) < 贷款金额,但我不确定如何正确设置它以查看数千笔贷款.你们能提供的任何帮助都将是不可思议的,我非常感激!
你可以通过写一个Recursive CTE, just remember that the default is limited to 100 iterations, if you need more loops then check this MAX_RECURSIONS参数来得到它。
这只是一个代码示例,您应该扩展它以包括一些极端的数据保护;
示例数据:
CREATE OR REPLACE TABLE LoanTable (
LoanID STRING,
Loan_date DATE,
Loan_amount NUMERIC(12,2),
Maturity_date DATE,
Payment_frequency STRING,
Payment_amount NUMERIC(12,2)
);
INSERT INTO LoanTable
VALUES ('abc123', '1/1/2022', 250, '3/9/2022', 'biweekly', 50);
查询:
WITH Recursive_CTE AS (
SELECT LoanID,
CASE Payment_frequency WHEN 'weekly' THEN DATEADD(WEEK, 1, Loan_date)
WHEN 'biweekly' THEN DATEADD(WEEK, 2, Loan_date)
WHEN 'semimonthly' THEN DATEADD(DAY, 15, Loan_date) -- I don't know how the semimonthly value is determined??
WHEN 'monthly' THEN DATEADD(MONTH, 1, Loan_date) END AS Payment_Date,
Payment_amount,
Loan_amount - Payment_amount AS Left_to_pay,
Payment_frequency,
Maturity_date
FROM LoanTable
UNION ALL
SELECT LoanID,
CASE Payment_frequency WHEN 'weekly' THEN DATEADD(WEEK, 1, Payment_Date)
WHEN 'biweekly' THEN DATEADD(WEEK, 2, Payment_Date)
WHEN 'semimonthly' THEN DATEADD(DAY, 15, Payment_Date) -- I don't know how the semimonthly value is determined??
WHEN 'monthly' THEN DATEADD(MONTH, 1, Payment_Date) END AS Payment_Date,
Payment_amount,
IFF(Left_to_pay - Payment_amount < 0, Left_to_pay, Left_to_pay - Payment_amount) AS Left_to_pay,
Payment_frequency,
Maturity_date
FROM Recursive_CTE
WHERE Left_to_pay > 0
)
SELECT LoanID, IFF(Payment_Date > Maturity_date, Maturity_date, Payment_Date) AS Payment_Date, Payment_amount, Left_to_pay, Payment_frequency
FROM Recursive_CTE
ORDER BY LoanID, Payment_Date;
Table生成器是另一种方法。
感谢 Simon 改进了这个解决方案。尊重!
WITH CTE_MY_DATE AS
(SELECT DATEADD(DAY, row_number() over (order by null)-1, '1900-01-01')::date AS MY_DATE FROM table(generator(rowcount => 18000)))
SELECT
date(MY_DATE) CALENDAR_DATE,
concat( decode(extract ('dayofweek_iso', date(MY_DATE)),1,'Monday',2, 'Tuesday',3, 'Wednesday',4, 'Thursday',5, 'Friday',6, 'Saturday',7, 'Sunday'),TO_CHAR(date(MY_DATE), ', MMMM DD, YYYY')) FULL_DATE_DESC
,row_number() over (partition by 1 order by calendar_date ) MOD_IS_COOL
FROM
CTE_MY_DATE
where
CALENDAR_DATE
between '2022-01-02' and '2022-09-03'
qualify
mod(MOD_IS_COOL, 14) = 0
下面是如何通过 JavaScript UDF 进行摊销,以及如何调用它的示例。我在从函数中获取 JSON 时遇到了一些麻烦,因此 return 将其编辑为文本字符串,去除双引号,将其展平,然后转换为 Table。也许更擅长 JavaScript 的人可以将其修改为 return table 预清理。
CREATE OR REPLACE FUNCTION AMORTIZATIONTABLE("AMOUNTFINANCED" FLOAT, "INTEREST" FLOAT, "PERIODS" FLOAT)
RETURNS STRING
LANGUAGE javascript
AS $$
const annuity = (AMOUNTFINANCED, INTEREST, PERIODS) => AMOUNTFINANCED * (INTEREST / (1 - (1 + INTEREST)**(-PERIODS)));
const balance_t = (AMOUNTFINANCED, INTEREST, P) => {
const period_movements = {
base: AMOUNTFINANCED
}
period_movements.interest = AMOUNTFINANCED * INTEREST;
period_movements.amortization = P - (AMOUNTFINANCED * INTEREST);
period_movements.annuity = P;
period_movements.final_value = Math.round((AMOUNTFINANCED - period_movements.amortization) * 100) / 100;
return period_movements;
}
const display_mortgage = (AMOUNTFINANCED, INTEREST, PERIODS) => {
var data = [];
const payements = annuity(AMOUNTFINANCED, INTEREST, PERIODS);
let movements = balance_t(AMOUNTFINANCED, INTEREST, payements);
while (movements.final_value > -.01) {
data.push(movements);
movements = balance_t(movements.final_value, INTEREST, payements);
}
return data;
}
data2 = display_mortgage(AMOUNTFINANCED, INTEREST, PERIODS);
return JSON.stringify(data2);
$$;
SELECT
INDEX + 1 AS Period,
a.VALUE:base AS CurrPrincipalBal,
a.VALUE:annuity AS TotalPayment,
a.VALUE:amortization AS PrincipalPmt,
a.VALUE:interest AS InterestPmt,
a.VALUE:final_value AS NewPrincipalBal
FROM
(SELECT * FROM TABLE(flatten(INPUT => SELECT parse_json(REPLACE(AMORTIZATIONTABLE(20000.00, 0.04, 12.00),'"',''))))) AS a;
所以我想我可以使用 table 生成器来编写这个“清洁器”。
公平地说,我觉得这比递归 CTE 更清晰。
需要注意的是,您需要为我拥有的1000
插入“可能的最大贷款期限”。
双月刊是通过计算每月选项之间的天数来完成的,取“一半”,奇数天使用 15 是正常的。
但像那样:
WITH loans_table(loanid, loan_date, loan_amount,
maturity_date, payment_frequency,
payment_amount) as (
SELECT * FROM VALUES
('abc123', '2022-01-01'::date, 250, '2022-03-09'::date, 'biweekly', 50)
), table_of_numbers as (
SELECT row_number() over(order by null) as rn
FROM TABLE(generator(ROWCOUNT => 1000))
/* that 1000 should be larger than any loan perdiod length you have */
), loan_enrich as (
SELECT
*
,CASE Payment_frequency
WHEN 'weekly' THEN 7
WHEN 'biweekly' THEN 14
WHEN 'semimonthly' THEN 14
WHEN 'monthly' THEN 28
END as period_lo_days
,datediff('day', loan_date, maturity_date) as loan_days
,CEIL(loan_days / period_lo_days) as loan_periods
FROM loans_table
)
SELECT
l.loanid,
CASE payment_frequency
WHEN 'weekly' THEN dateadd('week', r.rn, l.loan_date)
WHEN 'biweekly' THEN dateadd('week', r.rn * 2, l.loan_date)
WHEN 'semimonthly' THEN
case r.rn%2
when 0 then dateadd('month', r.rn/2, l.loan_date)
when 1 then dateadd('days', floor(datediff('days', dateadd('month', (r.rn-1)/2, l.loan_date), dateadd('month', (r.rn+1)/2, l.loan_date))/2), dateadd('month', (r.rn-1)/2, l.loan_date))
end
WHEN 'monthly' THEN dateadd('month', r.rn, l.loan_date)
END as payment_date,
l.payment_amount,
l.payment_frequency
FROM loan_enrich AS l
JOIN table_of_numbers AS r
ON l.loan_periods >= r.rn
ORDER BY 1, r.rn;
给出:
LOANID | PAYMENT_DATE | PAYMENT_AMOUNT | PAYMENT_FREQUENCY |
---|---|---|---|
abc123 | 2022-01-15 | 50 | biweekly |
abc123 | 2022-01-29 | 50 | biweekly |
abc123 | 2022-02-12 | 50 | biweekly |
abc123 | 2022-02-26 | 50 | biweekly |
abc123 | 2022-03-12 | 50 | biweekly |
所以这可以提升,让 semimonthly15 总是 15 天后,我们可以做一些过滤,以防行数超过我们需要的,我们可以显示处理最终付款的逻辑比以前的付款少:
WITH loans_table(loanid, loan_date, loan_amount,
maturity_date, payment_frequency,
payment_amount) as (
SELECT * FROM VALUES
('abc123', '2022-01-01'::date, 250, '2022-03-09'::date, 'biweekly', 50),
('abc124', '2022-01-01'::date, 249, '2022-03-09'::date, 'semimonthly', 50),
('abc125', '2022-01-01'::date, 249, '2022-03-09'::date, 'semimonthly15', 50)
), table_of_numbers as (
SELECT row_number() over(order by null) as rn
FROM TABLE(generator(ROWCOUNT => 1000))
/* that 1000 should be larger than any loan perdiod length you have */
), loan_enrich as (
SELECT
*
,CASE Payment_frequency
WHEN 'weekly' THEN 7
WHEN 'biweekly' THEN 14
WHEN 'semimonthly' THEN 14
WHEN 'semimonthly15' THEN 14
WHEN 'monthly' THEN 28
END as period_lo_days
,datediff('day', loan_date, maturity_date) as loan_days
,CEIL(loan_days / period_lo_days) as loan_periods
FROM loans_table
)
SELECT
l.loanid,
CASE payment_frequency
WHEN 'weekly' THEN dateadd('week', r.rn, l.loan_date)
WHEN 'biweekly' THEN dateadd('week', r.rn * 2, l.loan_date)
WHEN 'semimonthly' THEN
case r.rn%2
when 0 then dateadd('month', r.rn/2, l.loan_date)
when 1 then dateadd('days', floor(datediff('days', dateadd('month', (r.rn-1)/2, l.loan_date), dateadd('month', (r.rn+1)/2, l.loan_date))/2), dateadd('month', (r.rn-1)/2, l.loan_date))
end
WHEN 'semimonthly15' THEN
case r.rn%2
when 0 then dateadd('month', r.rn/2, l.loan_date)
when 1 then dateadd('days', 15, dateadd('month', (r.rn-1)/2, l.loan_date))
end
WHEN 'monthly' THEN dateadd('month', r.rn, l.loan_date)
END as payment_date,
l.payment_amount,
l.payment_frequency,
l.loan_amount,
l.loan_amount - least(l.loan_amount, l.payment_amount * r.rn) as still_to_pay,
least(l.loan_amount - least(l.loan_amount, l.payment_amount * (r.rn-1)), l.payment_amount) as this_payment
FROM loan_enrich AS l
JOIN table_of_numbers AS r
ON l.loan_periods >= r.rn
WHERE this_payment > 0
ORDER BY 1, r.rn
LOANID | PAYMENT_DATE | PAYMENT_AMOUNT | PAYMENT_FREQUENCY | LOAN_AMOUNT | STILL_TO_PAY | THIS_PAYMENT |
---|---|---|---|---|---|---|
abc123 | 2022-01-15 | 50 | biweekly | 250 | 200 | 50 |
abc123 | 2022-01-29 | 50 | biweekly | 250 | 150 | 50 |
abc123 | 2022-02-12 | 50 | biweekly | 250 | 100 | 50 |
abc123 | 2022-02-26 | 50 | biweekly | 250 | 50 | 50 |
abc123 | 2022-03-12 | 50 | biweekly | 250 | 0 | 50 |
abc124 | 2022-01-16 | 50 | semimonthly | 249 | 199 | 50 |
abc124 | 2022-02-01 | 50 | semimonthly | 249 | 149 | 50 |
abc124 | 2022-02-15 | 50 | semimonthly | 249 | 99 | 50 |
abc124 | 2022-03-01 | 50 | semimonthly | 249 | 49 | 50 |
abc124 | 2022-03-16 | 50 | semimonthly | 249 | 0 | 49 |
abc125 | 2022-01-16 | 50 | semimonthly15 | 249 | 199 | 50 |
abc125 | 2022-02-01 | 50 | semimonthly15 | 249 | 149 | 50 |
abc125 | 2022-02-16 | 50 | semimonthly15 | 249 | 99 | 50 |
abc125 | 2022-03-01 | 50 | semimonthly15 | 249 | 49 | 50 |
abc125 | 2022-03-16 | 50 | semimonthly15 | 249 | 0 | 49 |