SQL - How to calculate opening and closing balance - sql

I'm trying to figure out a way to calculate two new columns for the following model:
Link: http://sqlfiddle.com/#!7/0a6ce9/1
A) first column: should be the "OPENING_BALANCE". It needs to be the sum of the "AMOUNT" column starting off when transaction date started. The transaction date could be any.
B) Second column: Should be the "CLOSING_BALANCE". This one will always be the sum of the "opening balance" from previous day + "amount" of the current day. So, by the second TRANSACTION_DATE forward, the "opening balance" will always be the "closing balance" from the previous day.
Here's an example:
Can anyone share any examples of how I could achieve this?

You can use SUM() window function and a CASE expression to check for the 1st transaction:
SELECT *,
CASE
WHEN ROW_NUMBER() OVER (ORDER BY TRANSACTION_DATE, TRANSACTION_ID) = 1 THEN AMOUNT
ELSE SUM(AMOUNT) OVER (ORDER BY TRANSACTION_DATE, TRANSACTION_ID) - AMOUNT
END OPENING_BALANCE,
SUM(AMOUNT) OVER (ORDER BY TRANSACTION_DATE, TRANSACTION_ID) CLOSING_BALANCE
FROM TRANSATION_TABLE;
See the demo.

select f.TRANSACTION_ID, f.TRANSACTION_DATE, f.AMOUNT,CASE WHEN sum(s.AMOUNT)-f.AMOUNT >0
THEN sum(s.AMOUNT)-f.AMOUNT ELSE s.AMOUNT END AS OPENING_BALANCE,sum(s.AMOUNT) as CLOSING_BALANCE
from TRANSATION_TABLE f inner join TRANSATION_TABLE s on f.TRANSACTION_ID >= s.TRANSACTION_ID
group by f.amount order by f.TRANSACTION_ID asc

Related

Retain function in SQL

Have scenario where need to retain values based on condition.
Assign "Change date" on "AA start Date" then check for "Change in Duration".
If "change in Duration" < 60 then 1st Change date will be assigned till next Change in duration > 60
then retain new change date in "AA start date". Sample is given below.
"AA_START_DATE" is final column which I am looking for.
I think this is a type of gap and islands problem, where you want to remember the first date in sequence that is 60+ days from the previous date.
You can handle this by using lag() to get the previous date. Then use a cumulative conditional maximum to get the time when the most recent change occurred:
select t.*,
max(case when change_date > date_add(change_date, interval -60 day) then null else change_date
end) over (partition by cn, aa_code
order by change_date
) as aa_start_date
from (select t.*,
lag(change_date) over (partition by cn, aa_code order by change_date) as prev_change_date
from t
) t
I have achieved this as follows: Any alternate way is appreciable
(select CIN,AA_CODE,EXT_AA_MESSAGE,CHG_DATE,EXP_DATE,PREV_CHG_DATE,CHG_DATE_DUR,AA_START_DTE,
sum(case when AA_START_DTE is null then 0 else 1 end) over (partition by CIN,AA_CODE order by CHG_DATE) as value_partition from (select CIN,AA_CODE,EXT_AA_MESSAGE,CHG_DATE,EXP_DATE,
case when CIN_AA_CODE_FIRST = 1 then NULL else PREV_CHG_DATE end as PREV_CHG_DATE,
case when CIN_AA_CODE_FIRST <> 1 then date_diff(CHG_DATE,PREV_CHG_DATE,DAY) end as CHG_DATE_DUR,
case when CIN_AA_CODE_FIRST = 1 or (case when CIN_AA_CODE_FIRST <> 1 then date_diff(CHG_DATE,PREV_CHG_DATE,DAY) end ) > 60 then CHG_DATE
end as AA_START_DTE,
from
( select CIN,AA_CODE,EXT_AA_MESSAGE,CHG_DATE,EXP_DATE, LAG(CHG_DATE) OVER (PARTITION BY CIN,AA_CODE ORDER BY CHG_DATE ASC) as PREV_CHG_DATE,
rank() OVER (PARTITION BY CIN,AA_CODE ORDER BY CHG_DATE ASC) AS CIN_AA_CODE_FIRST from TABLE )))`

SQL sum over(partition) not subtracting negative values in SUM

I have the following query which outputs a list of transactions per user - units spent and units earned - column 'Amount'.
I have managed to group this per user and do a running total - column 'Running_Total_Spend'.
However it is ADDING the negative 'Amount' values rather than subtracting them. Sp pretty sure it is the SUM part of query not working.
WITH cohort AS(
SELECT DISTINCT userID FROM events_live WHERE startDate = '2018-07-26' LIMIT 50),
my_events AS (
SELET events_live.* FROM events_live WHERE eventDate >= '2018-07-26')
SELECT cohort.userID,
my_events.eventDate,
my_events.eventTimestamp,
CASE
--spent resource outputs a negative value ---working
WHEN transactionVector = 'SPENT' THEN -abs(my_events.productAmount)
--earned resource outputs a positive value ---working
WHEN transactionVector = 'RECEIVED' THEN my_events.productAmount END AS Amount,
ROW_NUMBER() OVER (PARTITION BY cohort.userID ORDER BY cohort.userID, eventTimestamp asc) AS row,
--sum the values in column 'Amount' for this partition
--should sum positive and negative values ---NOT WORKING--converting negatives into positive
--------------------------------------------------
SUM(CASE WHEN my_events.productAmount >= 0 THEN my_events.productAmount
WHEN my_events.productAmount <0 THEN -abs(my_events.productAmount) end) OVER(PARTITION BY cohort.userID ORDER BY cohort.userID, eventTimestamp asc) AS Running_Total_Spend
---------------------------------------------------
FROM cohort
INNER JOIN my_events ON cohort.userID=my_events.userID
WHERE productName = 'COINS' AND transactionVector IN ('SPENT','RECEIVED')
I suspect you want that logic around transactionvector for the sum too as my_events.productamount seems to be always positive.
...
sum(CASE
WHEN transactionvector = 'SPENT' THEN
-my_events.productamount
WHEN transactionvector = 'RECEIVED' THEN
my_events.productamount
END) OVER (PARTITION BY cohort.userid
ORDER BY cohort.userid,
eventTimestamp) running_total_spend
...
Update your sum function to -
SUM(my_events.productAmount) OVER(PARTITION BY cohort.userID ORDER BY cohort.userID, eventTimestamp asc) AS Running_Total_Spend

Loop in Oracle SQL, comparing one month to another

I have to draft a SQL query which does the following:
Compare current week (e.g. week 10) amount to the average amount over previous 4 weeks (Week# 9,8,7,6).
Now I need to run the query on a monthly basis so say for weeks (10,11,12,13).
As of now I am running it four times giving the week parameter on each run.
For example my current query is something like this :
select account_id, curr.amount,hist.AVG_Amt
from
(
select
to_char(run_date,'IW') Week_ID,
sum(amount) Amount,
account_id
from Transaction t
where to_char(run_date,'IW') = '10'
group by account_id,to_char(run_date,'IW')
) curr,
(
select account_id,
sum(amount) / count(to_char(run_date,'IW')) as AVG_Amt
from Transactions
where to_char(run_date,'IW') in ('6','7','8','9')
group by account_id
) hist
where
hist.account_id = curr.account_id
and curr.amount > 2*hist.AVG_Amt;
As you can see, if I have to run the above query for week 11,12,13 I have to run it three separate times. Is there a way to consolidate or structure the query such that I only run once and I get the comparison data all together?
Just an additional info, I need to export the data to Excel (which I do after running query on the PL/SQL developer) and export to Excel.
Thanks!
-Abhi
You can use a correlated sub-query to get the sum of amounts for the last 4 weeks for a given week.
select
to_char(run_date,'IW') Week_ID,
sum(amount) curAmount,
(select sum(amount)/4.0 from transaction
where account_id = t.account_id
and to_char(run_date,'IW') between to_char(t.run_date,'IW')-4
and to_char(t.run_date,'IW')-1
) hist_amount,
account_id
from Transaction t
where to_char(run_date,'IW') in ('10','11','12','13')
group by account_id,to_char(run_date,'IW')
Edit: Based on OP's comment on the performance of the query above, this can also be accomplished using lag to get the previous row's value. Count of number of records present in the last 4 weeks can be achieved using a case expression.
with sum_amounts as
(select to_char(run_date,'IW') wk, sum(amount) amount, account_id
from Transaction
group by account_id, to_char(run_date,'IW')
)
select wk, account_id, amount,
1.0 * (lag(amount,1,0) over (order by wk) + lag(amount,2,0) over (order by wk) +
lag(amount,3,0) over (order by wk) + lag(amount,4,0) over (order by wk))
/ case when lag(amount,1,0) over (order by wk) <> 0 then 1 else 0 end +
case when lag(amount,2,0) over (order by wk) <> 0 then 1 else 0 end +
case when lag(amount,3,0) over (order by wk) <> 0 then 1 else 0 end +
case when lag(amount,4,0) over (order by wk) <> 0 then 1 else 0 end
as hist_avg_amount
from sum_amounts
I think that is what you are looking for:
with lagt as (select to_char(run_date,'IW') Week_ID, sum(amount) Amount, account_id
from Transaction t
group by account_id, to_char(run_date,'IW'))
select Week_ID, account_id, amount,
(lag(amount,1,0) over (order by week) + lag(amount,2,0) over (order by week) +
lag(amount,3,0) over (order by week) + lag(amount,4,0) over (order by week)) / 4 as average
from lagt;

How can I add cumulative sum column?

I use SqlExpress
Following is the query using which I get the attached result.
SELECT ReceiptId, Date, Amount, Fine, [Transaction]
FROM (
SELECT ReceiptId, Date, Amount, 'DR' AS [Transaction]
FROM ReceiptCRDR
WHERE (Amount > 0)
UNION ALL
SELECT ReceiptId, Date, Amount, 'CR' AS [Transaction]
FROM ReceiptCR
WHERE (Amount > 0)
UNION ALL
SELECT strInvoiceNo AS ReceiptId, CONVERT(datetime, dtInvoiceDt, 103) AS Date, floatTotal AS Amount, 'DR' AS [Transaction]
FROM tblSellDetails
) AS t
ORDER BY Date
Result
want a new column which would show balance amount.
For example. 1 Row should show -2500, 2nd should -3900, 3rd should -700 and so on.
basically, it requires previous row' Account column's data and carry out calculation based on transaction type.
Sample Result
Well, that looks like SQL-Server , if you are using 2012+ , then use SUM() OVER() :
SELECT t.*,
SUM(CASE WHEN t.transactionType = 'DR'
THEN t.amount*-1
ELSE t.amount END)
OVER(PARTITION BY t.date ORDER BY t.receiptId,t.TransactionType DESC) as Cumulative_Col
FROM (YourQuery Here) t
This will SUM the value when its CR and the value*-1 when its DR
Right now I grouped by date, meaning each day will recalculate this column, if you want it for all time, replace the OVER() with this:
OVER(ORDER BY t.date,t.receiptId,t.TransactionType DESC) as Cumulative_Col
Also, I didn't understand why in the same date, for the same ReceiptId DR is calculated before CR , I've add it to the order by but if thats not what you want then explain the logic better.

Sum Until Value Reached - Teradata

In Teradata, I need a query to first identify all members in the MEM TABLE that currently have a negative balance, let's call that CUR_BAL. Then, for all of those members only, sum all transactions from the TRAN TABLE in order by date until the sum of those transactions is equal to the CUR_BAL.
Editing to add a third ADJ table that contains MEM_NBR, ADJ_DT and ADJ_AMT that need to be included in the running total in order to capture all of the records.
I would like the outcome to include the MEM.MEM_NBR, MEM.CUR_BAL, TRAN.TRAN_DATE OR ADJ.ADJ_DT (date associated with the transaction that resulted in the running total to equal CUR_BAL), MEM.LST_UPD_DT. I don't need to know if the balance is negative as a result of a transaction or adjustment, just the date that it went negative.
Thank you!
select
mem_nbr,
cur_bal,
tran_date,
tran_type
from (
select
a.mem_nbr,
a.cur_bal,
b.tran_date,
b.tran_type,
a.lst_upd_dt,
sum(b.tran_amt) over (partition by b.mem_nbr order by b.tran_date rows between unbounded preceding and current row) as cumulative_bal
from mem a
inner join (
select
mem_nbr,
tran_date,
tran_amt,
'Tran' as tran_type
from tran
union all
select
mem_nbr,
adj_date,
adj_amt,
'Adj' as tran_type
from adj
) b
on a.mem_nbr = b.mem_nbr
where a.cur_bal < 0
qualify cumulative_bal < 0
) z
qualify rank() over (partition by mem_nbr order by tran_date) = 1
The subquery picks up all instances where the cumulative balance is negative, then the outer query picks up the earliest instance of it. If you want the latest, add desc after tran_date in the final qualify line.