SQL Server: Calculate amortization - sql

Currently i'm struggling to solve following problem:
I have two tables (contract & amortization).
Contract: contract informations with an outstanding amount
Amortization: date of payment & amortization amount
My desired result is a table with the contract and the decreasing
oustanding amount.
;WITH CTE AS
(
SELECT con.ID
,con.outstanding
,amo.amortization
,amo.amo_date
,ROW_NUMBER() OVER ( PARTITION BY con.ID
ORDER BY amo.amo_date asc
) as rn
FROM contract con
INNER JOIN amort amo
ON amo.contractID = con.ID
)
SELECT ID
,outstanding
,outstanding - amortization as new_outstanding
,amo_date
FROM CTE
Currently i am getting this result - which is of course wrong, as just one amortization is calculated for the new_outstanding:
ID outstanding new_outstanding amo_date
1 100000 90000 01.08.2017 00:00:00
1 100000 80000 01.09.2017 00:00:00
1 100000 50000 01.10.2017 00:00:00
1 100000 90000 01.11.2017 00:00:00
1 100000 90000 01.12.2017 00:00:00
My desired result would be:
ID outstanding new_outstanding amo_date
1 100000 90000 01.08.2017 00:00:00
1 100000 70000 01.09.2017 00:00:00
1 100000 20000 01.10.2017 00:00:00
1 100000 10000 01.11.2017 00:00:00
1 100000 0 01.12.2017 00:00:00
Any simple idea to solve this in an easy way?
Rextester: http://rextester.com/SBLT77863
Thanks in advance!

I think you just need a cumulative sum:
SELECT con.ID,
con.outstanding,
amo.amortization,
amo.amo_date,
(con.outstanding -
sum(amo.amortization) over (partition by amo.contractId
order by amo.amo_date)
) as new_outstanding
FROM contract con INNER JOIN
amort amo
ON amo.contractID = con.ID;

Related

Bigquery - aggregate while filtering out values

I'm sure this question has been answered elsewhere but I can't find it.
I have a table of invoices like
id
company
index
date_sent
amount
1
Com1
1
2022-01-01
100
2
Com1
2
2022-02-01
100
3
Com1
3
2022-03-01
100
4
Com1
4
2022-04-01
100
5
Com2
1
2022-02-01
100
6
Com2
2
2022-03-01
100
7
Com2
3
2022-04-01
100
8
Com3
1
2022-01-01
100
9
Com3
2
2022-02-01
100
10
Com4
1
2022-01-01
100
(Index here is added by basically doing RANK() OVER (PARTITION BY co ORDER BY date_sent) as index)
I'd like to return companies who have more than 3 invoices, the aggregate sum of those 3 invoices and the date sent of the 3rd invoice.
For example, for the data above, the returned data should be:
company
date_3rd
amount_sum_3
Com1
2022-03-01
300
Com2
2022-04-01
300
So far I've got:
select company,
(select sum(amount) from grouped_invs.amount_sum_3 amount) as amount_sum_3,
from (
select company,
array_agg(invoices.amount order by invoices.index limit 3) amount_sum_3,
from `data` invoices
group by invoices.company
having count(*) => 3
) grouped_invs
which gives me
company
amount_sum_3
Com1
300
Com2
300
But I can't figure out how to get the 3rd date sent out from there.
Thanks in advance
You might consider below
SELECT (SELECT AS STRUCT
ANY_VALUE(company) AS company,
MAX(date_sent) date_3rd,
SUM(amount) amount_sum_3
FROM grouped_invs.amount_sum_3).*
FROM (
SELECT ARRAY_AGG(invoices ORDER BY index LIMIT 3) amount_sum_3
FROM `data` invoices
GROUP BY invoices.company HAVING COUNT(*) >= 3
) grouped_invs;
Assuming that your data already has an index, below will return same results.
SELECT company, MAX(date_sent) date_3rd, SUM(amount) amount_sum_3
FROM (
SELECT * FROM `data` invoices
WHERE index <= 3
QUALIFY COUNT(*) OVER (PARTITION BY company) >= 3
)
GROUP BY 1;
Query results

How to select the most recent records in a dataset in sql

I have a large dataset where we keep track of all revenue collected by client for each month. So each client gets a row for each month they were charged, resulting in a lot of rows. I am trying to find a way to pull the most recent record for each client.
Here is what i have:
merchant name
Merchant id
revenue date
revenue amount
fish
1234
2022-03-01
200
fish
1234
2022-04-01
200
fish
1234
2022-05-01
200
fish
1234
2022-06-01
200
dog
5678
2022-01-01
200
dog
5678
2022-02-01
200
dog
5678
2022-03-01
200
dog
5678
2022-04-01
200
cat
1011
2022-10-01
200
cat
1011
2022-11-01
200
My desired result is:
merchant name
Merchant id
revenue date
revenue amount
fish
1234
2022-06-01
200
dog
5678
2022-04-01
200
cat
1011
2022-11-01
200
I have tried this:
Select distinct
merchant_name,
merchant_id,
revenue_date,
revenue_amount
from table
where revenue_date=(select max(revenue_date) from table)
but that is only returning rows that match the maximum date listed (2022-11-01).
Any help is much appreciated!
The general solution takes the form:
select *
from (
select t.*, row_number() over(partition by merchant_name
order by revenue_date desc) as rn
from t
) x
where rn = 1
Or... in PostgreSQL you can just do:
select distinct on (merchant_name) *
from t
order by merchant_name, revenue_date desc
Alternate answer (formatted for SQL Server):
--For each merchantID, find the latest date
WITH maxDates AS (
SELECT
merchantID,
MAX(revenueDate) revenueDate
FROM #input
GROUP BY merchantID
)
--Use the defined CTE to retrieve the rest of the columns
SELECT i.*
FROM #input i
INNER JOIN maxDates m ON i.merchantID=m.merchantID AND i.revenueDate=m.revenueDate

How to get the date when accumulation (sum) in a condition in postgresql

I have table like this:
id
date
total
123
2021-02-03
200000
123
2021-03-03
1650000
123
2021-02-04
1500000
123
2021-02-21
200000
123
2021-03-03
200000
How's the sql query if I want to get the date when sum of total reach 3000000 or when the date on 2021-02-04?
the expected output is
id
date
total
123
2021-02-04
3350000
or if not possible maybe like this:
id
date
123
2021-02-04
use this
select top 1 * from (
select id,date,
SUM(total) OVER (ORDER BY date ROWS UNBOUNDED PRECEDING) as Total
from tablename
)
where total >= 3000000

Wrong Opening Balance for some rows

I want to calculate Opening and Closing Balance of a business application. But for some rows wrong Opening Balance is producing. I have following Data Tables:
SupplierPayments
DateOfPayment Bill
2018-06-01 4000
2018-06-01 9000
2018-06-19 2000
2018-06-19 6000
2019-03-28 3000
2019-03-29 5000
Expensis
DateOfExpense Expense
2018-08-14 2,000
2019-02-26 8,000
2019-03-28 2000
2019-03-29 2000
Income
DateSold Income
2018-09-27 24,000
2018-10-17 8,000
2019-01-01 13,000
2019-03-28 10,000
SQL Server 2012 Query
with Income( DateSold, Income ) as (
select DateSold,isnull(sum(TotalBill),0)
from SalesInvoice group by DateSold
), SupplierPayments( DateOfPayment,Bill ) as(
select DateOfPayment,isnull(sum(BillPaidAmount),0)
from SupplyInvoicePaymentHistory group by DateOfPayment
), Expensis( DateOfExpense, Expense ) as(
select Date ,isnull(sum(Amount),0)
from GeneralExpense group by Date
), t as (
select i.DateSold
,e.DateOfExpense
,sp.DateOfPayment
,i.income
, e.Expense
,sp.Bill
, sum(isnull(i.income,0)-(isnull(e.Expense,0)+isnull(sp.Bill,0))) over (order by i.DateSold,e.DateOfExpense,sp.DateOfPayment) as closing_balance
from income i
full outer join expensis e on e.DateOfExpense = i.DateSold
full outer join SupplierPayments sp on sp.DateOfPayment=e.DateOfExpense
)
select m.EventDate, m.DateSold
,m.DateOfExpense
,m.DateOfPayment
,isnull(m.opening_balance,0) as Opening_Balance
,isnull(m.Income,0) as Income
,isnull(m.Expense,0) as Expensis
,isnull(m.Bill,0) as SupplierPayments
,isnull(m.closing_balance,0) as Closing_Balance
from (
select coalesce(coalesce(DateOfPayment, DateOfExpense), DateSold) EventDate, DateSold
,DateOfExpense
,DateOfPayment
,lag(closing_balance,1,0) over (order by DateSold, DateOfExpense,DateOfPayment) as opening_balance
,Income
,Expense
,closing_balance
,Bill
from t
) as m order by m.EventDate ASC
Output
EventDate DateSold ExpenseDate PaymentDate Opening Income Expense Bill Closing
2018-06-01 NULL NULL 2018-06-01 0 0 0 13000 -13000
2018-06-19 NULL NULL 2018-06-19 -13000 0 0 8000 -21000
2018-08-14 NULL 2018-08-14 NULL -21000 0 2000 0 -23000
2018-09-27 2018-09-27 NULL NULL -30000 24000 0 0 -6000
2019-01-01 2019-01-01 NULL NULL -6000 13000 0 0 7000
2019-03-28 2019-03-28 2019-03-28 2019-03-28 7000 10000 2000 3000 12000
2019-03-29 NULL 2019-03-29 2019-03-29 -23000 0 2000 5000 -30000
Formula to calculate closing balance is as:
Closing = Opening + Income - Expense - Bill
As we can notice that opening balance for date 2018-09-27 is -30,000 which is wrong. It should be -23,000. similarly opening balance for date 2019-03-29 is also wrong.
Required Result
EventDate DateSold ExpenseDate PaymentDate Opening Income Expense Bill Closing
2018-06-01 NULL NULL 2018-06-01 0 0 0 13000 -13000
2018-06-19 NULL NULL 2018-06-19 -13000 0 0 8000 -21000
2018-08-14 NULL 2018-08-14 NULL -21000 0 2000 0 -23000
2018-09-27 2018-09-27 NULL NULL -23000 24000 0 0 1000
2019-01-01 2019-01-01 NULL NULL 1000 13000 0 0 14000
2019-03-28 2019-03-28 2019-03-28 2019-03-28 14000 10000 2000 3000 19000
2019-03-29 NULL 2019-03-29 2019-03-29 19000 0 2000 5000 12000
It is possible that in any day there is No item sold but there is expense or bill paid to supplier and vice versa.
Also it is possible that any tables has two entries on the same date.
Your issue lies within the order by columns for both SUM and LAG. If you selected from t CTE alone, you'd see that you're not ordering by whatever is in any of the three dates (what's available), you're ordering by them in the order you specified. So you'll get NULLs for the first column first, NULLs for the second column first, etc. What you need to do is introduce the EventDate sooner, in t, and order everything by it.
;with xIncome( DateSold, Income ) as (
select DateSold,isnull(sum(income),0)
from income group by DateSold
), xSupplierPayments( DateOfPayment,Bill ) as(
select DateOfPayment,isnull(sum(bill),0)
from supplierpayments group by DateOfPayment
), xExpensis( DateOfExpense, Expense ) as(
select DateOfExpense Date ,isnull(sum(expense),0)
from expensis group by Dateofexpense
), t as (
select i.DateSold
,e.DateOfExpense
,sp.DateOfPayment
,consolidated.date consolidatedDate
,i.income
, e.Expense
,sp.Bill
, sum(isnull(i.income,0)-(isnull(e.Expense,0)+isnull(sp.Bill,0))) over (order by consolidated.date) as closing_balance
from xincome i
full outer join xexpensis e on e.DateOfExpense = i.DateSold
full outer join xSupplierPayments sp on sp.DateOfPayment=e.DateOfExpense
cross apply (select coalesce(i.DateSold,e.DateOfExpense,sp.DateOfPayment) as date) consolidated
)
select consolidatedDate, m.DateSold
,m.DateOfExpense
,m.DateOfPayment
,isnull(m.opening_balance,0) as Opening_Balance
,isnull(m.Income,0) as Income
,isnull(m.Expense,0) as Expensis
,isnull(m.Bill,0) as SupplierPayments
,isnull(m.closing_balance,0) as Closing_Balance
from (
select consolidatedDate
,DateSold
,DateOfExpense
,DateOfPayment
,lag(closing_balance,1,0) over (order by consolidatedDate) as opening_balance
,Income
,Expense
,closing_balance
,Bill
from t
) as m order by m.consolidatedDate ASC
Notice the CROSS APPLY in t CTE, where I COALESCE all the dates into a single cosnolidated date. I had to rename the first CTEs to match your sample data table names, but you get the gist of it.

SQL select specific group from table

I have a table named trades like this:
id trade_date trade_price trade_status seller_name
1 2015-01-02 150 open Alex
2 2015-03-04 500 close John
3 2015-04-02 850 close Otabek
4 2015-05-02 150 close Alex
5 2015-06-02 100 open Otabek
6 2015-07-02 200 open John
I want to sum up trade_price grouped by seller_name when last (by trade_date) trade_status was 'open'. That is:
sum_trade_price seller_name
700 John
950 Otabek
The rows where seller_name is Alex are skipped because the last trade_status was 'close'.
Although I can get desirable output result with the help of nested select
SELECT SUM(t1.trade_price), t1.seller_name
WHERE t1.seller_name NOT IN
(SELECT t2.seller_name FROM trades t2
WHERE t2.seller_name = t1.seller_name AND t2.trade_status = 'close'
ORDER BY t2.trade_date DESC LIMIT 1)
from trades t1
group by t1.seller_name
But it takes more than 1 minute to execute above query (I have approximately 100K rows).
Is there another way to handle it?
I am using PostgreSQL.
I would approach this with window functions:
SELECT SUM(t.trade_price), t.seller_name
FROM (SELECT t.*,
FIRST_VALUE(trade_status) OVER (PARTITION BY seller_name ORDER BY trade_date desc) as last_trade_status
FROM trades t
) t
WHERE last_trade_status <> 'close;
GROUP BY t.seller_name;
This should perform reasonably with an index on seller_name
select
sum(trade_price) as sum_trade_price,
seller_name
from
trades
inner join
(
select distinct on (seller_name) seller_name, trade_status
from trades
order by seller_name, trade_date desc
) s using (seller_name)
where s.trade_status = 'open'
group by seller_name