Sum Until Value Reached - Teradata - sql

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.

Related

How to select only decreasing delta in values with increasing delta also present in data?

I have a warehouse which returns stock. Stock diminishes (orders) and increases (supplies). I have only stock values, nothing else. Say I have this data sorted by stock quantity ascending:
time
stock
00:11
7144 <--- current
00:10
7280
00:09
7416 <--- note increase, means new supply arrived
00:08
2259
00:07
2333
00:06
2538
00:05
2999
00:04
3074
00:03
3104 <--- start
I need to derive orders excluding supply, so max(qty)-min(qty) do not work, function has to mind the sudden increase and return only diminishing delta. So I expect in given values formula to be: orders = (3104-2259)+(7416-7144)
How would you approach this task?
Thanks.
You can join two "adjacent" rows and check that the stock of the earlier row is more than the stock of the later row to find your orders. Then sum over the difference of these stocks.
Assuming that the column time is unique, you can use following query:
SELECT SUM(t1.stock - t2.stock) AS sum_of_orders
FROM my_table t1
INNER JOIN my_table t2 ON t2.time > t1.time
AND NOT EXISTS(
SELECT 1
FROM my_table t3
WHERE t3.time > t1.time AND t3.time < t2.time)
WHERE t1.stock > t2.stock;
This is a type of gaps-and-islands problem. You can identify the "islands" by when the stock increases. Then within each island you want the max and min. You can summarize the islands using:
select min(stock), max(stock)
from (select t.*,
sum(case when prev_stock > stock then 0 else 1 end) over (order by time) as grp
from (select t.*,
lag(stock) over (order by time) as prev_stock
from t
) t
) t
group by grp;
Then one more summary gives the total you want:
select sum(max_stock - min_stock)
from (select select min(stock) as min_stock, max(stock) as max_stock
from (select t.*,
sum(case when prev_stock > stock then 0 else 1 end) over (order by time) as grp
from (select t.*,
lag(stock) over (order by time) as prev_stock
from t
) t
) t
group by grp
) t;
Here is a db<>fiddle.

Sum for a rolling total

I have the following query:
select b.month_date,total_signups,active_users from
(
SELECT date_trunc('month',confirmed_at) as month_date
, count(distinct id) as total_signups
FROM follower.users
WHERE confirmed_at::date >= dateadd(day,-90,getdate())::date
and (deleted_at is null or deleted_at > date_trunc('month',confirmed_at))
group by 1
) a ,
(
SELECT date_trunc('month', inv.created_at) AS month_date
,COUNT(DISTINCT em.user_id) AS active_users
FROM follower.invitees inv
INNER JOIN follower.events
ON inv.event_id = em.event_id
where inv.created_at::date >= dateadd(day,-90,getdate())::date
GROUP BY 1
) b
where a.month_date=b.month_date
This returns three columns month date, total signups and active users, what I need is a rolling total for all users in the fourth column (rolling total of signups). I've tried over and partition functions with no luck. Could someone help? Appreciate it very much.
Try adding this column definition to your first Select:
SUM(total_signups)
OVER (ORDER BY b.month_date ASC rows between unbounded preceding and current row)
AS running_total
Here's a mini-demo

how to query a table date against a series of dates on another table

I have two tables, INVOICES and INV_PRICES. I am trying to find the Invoice table's part price from the Inv_Prices based upon the Invoice_Dt on the Invoice table; if the Invoice_Dt is between (greater than, but less than) or greater than the max EFF_DT on the Inv_Prices, then return that part's price.
I have tired variations on the following code, but no luck. I either do not get all the parts or multiple records.
SELECT DISTINCT A.INVOICE_NBR, A.INVOICE_DT, A.PART_NO,
CASE WHEN TRUNC(A.INVOICE_DT) >= TRUNC(B.EFF_DT) THEN B.DLR_NET_PRC_AM
WHEN (TRUNC(A.INVOICE_DT)||ROWNUM >= TRUNC(B.EFF_DT)||ROWNUM) AND (TRUNC(B.EFF_DT)||ROWNUM <= TRUNC(A.INVOICE_DT)||ROWNUM) THEN B.DLR_NET_PRC_AM
/*MAX(B.EFF_DT) THEN B.DLR_NET_PRC_AM*/
ELSE 0
END AS PRICE
FROM INVOICES A,
INV_PRICES B
WHERE A.PART_NO = B.PART_NO
ORDER BY A.INVOICE_NBR
Can someone assist? I have a sample of each table if needed.
Doesn't it work to put the condition in the JOIN conditions? You can calculate the period when a price is valid using LEAD():
SELECT i.INVOICE_NBR, i.INVOICE_DT, i.PART_NO,
COALESCE(ip.DLR_NET_PRC_AM, 0) as price
FROM INVOICES i LEFT JOIN
(SELECT ip.*, LEAD(eff_dt) OVER (PARTITION BY PART_NO ORDER BY eff_dt) as next_eff_dt
FROM INV_PRICES ip
) ip
ON i.PART_NO = ip.PART_NO AND
i.invoice_dt >= ip.eff_dt AND
(i.invoice_dt < ip.next_eff_dt or ip.next_eff_dt is null)
ORDER BY i.INVOICE_NBR

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

Query for getting previous date in oracle in specific scenario

I have the below data in a table A which I need to insert into table B along with one computed column.
TABLE A:
Account_No | Balance | As_on_date
1001 |-100 | 1-Jan-2013
1001 |-150 | 2-Jan-2013
1001 | 200 | 3-Jan-2013
1001 |-250 | 4-Jan-2013
1001 |-300 | 5-Jan-2013
1001 |-310 | 6-Jan-2013
Table B:
In table B, there should be no of days to be shown when balance is negative and
the date one which it has gone into negative.
So, for 6-Jan-2013, this table should show below data:
Account_No | Balance | As_on_date | Days_passed | Start_date
1001 | -310 | 6-Jan-2013 | 3 | 4-Jan-2013
Here, no of days should be the days when the balance has gone negative in recent time and
not from the old entry.
I need to write a SQL query to get the no of days passed and the start date from when the
balance has gone negative.
I tried to formulate a query using Lag analytical function, but I am not succeeding.
How should I check the first instance of negative balance by traversing back using LAG function?
Even the first_value function was given a try but not getting how to partition in it based on negative value.
Any help or direction on this will be really helpful.
Here's a way to achive this using analytical functions.
INSERT INTO tableb
WITH tablea_grouped1
AS (SELECT account_no,
balance,
as_on_date,
SUM (CASE WHEN balance >= 0 THEN 1 ELSE 0 END)
OVER (PARTITION BY account_no ORDER BY as_on_date)
grp
FROM tablea),
tablea_grouped2
AS (SELECT account_no,
balance,
as_on_date,
grp,
LAST_VALUE (
balance)
OVER (
PARTITION BY account_no, grp
ORDER BY as_on_date
ROWS BETWEEN UNBOUNDED PRECEDING
AND UNBOUNDED FOLLOWING)
closing_balance
FROM tablea_grouped1
WHERE balance < 0
AND grp != 0 --keep this, if starting negative balance is to be ignored
)
SELECT account_no,
closing_balance,
MAX (as_on_date),
MAX (as_on_date) - MIN (as_on_date) + 1,
MIN (as_on_date)
FROM tablea_grouped2
GROUP BY account_no, grp, closing_balance
ORDER BY account_no, MIN (as_on_date);
First, SUM is used as analytical function to assign group number to consecutive balances less than 0.
LAST_VALUE function is then used to find the last -ve balance in each group
Finally, the result is aggregated based on each group. MAX(date) gives the last date, MIN(date) gives the starting date, and the difference of the two gives number of days.
Demo at sqlfiddle.
Try this and use gone_negative to computing specified column value for insert into another table:
select temp.account_no,
temp.balance,
temp.prev_balance,
temp.on_date,
temp.prev_on_date,
case
WHEN (temp.balance < 0 and temp.prev_balance >= 0) THEN
1
else
0
end as gone_negative
from (select account_no,
balance,
on_date,
lag(balance, 1, 0) OVER(partition by account_no ORDER BY account_no) prev_balance,
lag(on_date, 1) OVER(partition by account_no ORDER BY account_no) prev_on_date
from tblA
order by account_no) temp;
Hope this helps pal.
Here's on way to do it.
Select all records from my_table where the balance is positive.
Do a self-join and get all the records that have a as_on_date is greater than the current row, but the amounts are in negative
Once we get these, we cut-off the rows WHERE the date difference between the current and the previous row for as_on_date is > 1. We then filter the results a outer sub query
The Final select just groups the rows and gets the min, max values for the filtered rows which are grouped.
Query:
SELECT
account_no,
min(case when row_number = 1 then balance end) as balance,
max(mt2_date) as As_on_date,
max(mt2_date) - mt1_date as Days_passed,
min(mt2_date) as Start_date
FROM
(
SELECT
*,
MIN(break_date) OVER( PARTITION BY mt1_date ) AS min_break_date,
ROW_NUMBER() OVER( PARTITION BY mt1_date ORDER BY mt2_date desc ) AS row_number
FROM
(
SELECT
mt1.account_no,
mt2.balance,
mt1.as_on_date as mt1_date,
mt2.as_on_date as mt2_date,
case when mt2.as_on_date - lag(mt2.as_on_date,1) over () > 1 then mt2.as_on_date end as break_date
FROM
my_table mt1
JOIN my_table mt2 ON ( mt2.balance < mt1.balance AND mt2.as_on_date > mt1.as_on_date )
WHERE
MT1.balance > 0
order by
mt1.as_on_date,
mt2.as_on_date ) sub_query
) T
WHERE
min_break_date is null
OR mt2_date < min_break_date
GROUP BY
mt1_date,
account_no
SQLFIDDLE
I have a added a few more rows in the FIDDLE, just to test it out