Calculate Balance from Transactions - sql

I have a table that looks like this
ID Type Amount Created
10 4 30,00 2019-11-29 11:34:54.417
1 1 10,50 2019-11-19 11:34:54.417
3 2 16,50 2019-11-17 11:34:54.417
2 4 11,50 2019-11-15 11:34:54.417
4 6 10,00 2019-11-11 11:34:54.417
5 3 8,60 2019-10-19 11:34:54.417
7 1 21,50 2019-05-19 11:34:54.417
8 4 9,00 2019-04-19 11:34:54.417
9 1 8,00 2019-02-19 11:34:54.417
6 1 1,50 2019-01-19 11:34:54.417
Imagine this table keeps an e-wallet and these are Transactions with ID , Type(withdrawals , reversals , deposits etc..) ,Amount and datetime Created.
Lets say that all these 10 transactions refer to a specific Customer. Thus if i run
SELECT SUM(Amount) AS Balance
FROM transactions
WHERE Created <= '20191120'
this query will return the Balance of this customer until 2019/11/20.
What i want is to run a select query to this table and keep only the Transactions with Type=4.
E.g.
SELECT ID
, Type
, Amount
, Created
FROM transactions
WHERE type=4
This query returns the following
ID Type Amount Created
2 4 11,50 2019-11-15 11:34:54.417
8 4 9,00 2019-04-19 11:34:54.417
10 4 30,00 2019-11-29 11:34:54.417
What i really want though is an extra column in this result set that shows the balance of the customer at the point of each transaction(with Type=4). For example when he did the Transaction with ID = 2 His balance before this(not counting the current(id=2) was (1,50+8,00+9,00+21,50+8,60+10,00) , when he did the Transaction with ID = 8 his balance was (1,50+8,00) and so on..
A desired Result set would be
ID Type Amount Created Balance
2 4 11,50 2019-11-15 11:34:54.417 58,60
8 4 9,00 2019-04-19 11:34:54.417 9,50
10 4 30,00 2019-11-29 11:34:54.417 97,1
I want to do this in one Select Query. I have some thoughts of doing it in two steps but that's not my intention, i Just need to run it once and have all five desired columns.

Looking carefully at your desired output, assuming your DBMS supports window functions, you can do this using a pseudo-cumulative sum:
SELECT ID, Type, Amount, Created, Balance
FROM (
SELECT ID, Type, Amount, Created,
SUM(Amount) OVER(-- Sum "amount" of all rows before current row (exclude current row)
ORDER BY Created ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
) AS Balance
FROM transactions
) src
WHERE type = 4
ORDER BY id
;
SQL Fiddle
Also, shouldn't the balance for id=2 be 58,6 and the balance for id=10 be 97.1?

You want a cumulative sum and filtering:
SELECT t.*
FROM (SELECT t.*, SUM(Amount) OVER (ORDER BY Created) - Amount AS Balance
FROM transactions t
WHERE Created <= '20191120'
) t
WHERE type = 4;

Related

Snowflake SQL - Forward / Back fill a column with multiple values

I am trying to forward and back fill a column that has multiple values. My end result should be a filled column with duplicated values in the index.
My current dataset has a report month and a category. The category column could have a dynamic amount of category numbers with null values in between. There is 1 column for report month that is a distinct value.
reportmonth
category
2020-01
null
2021-02
5
2021-03
null
2021-04
null
2021-05
10
2021-05
5
2021-06
null
2021-07
null
Here is the dataset that I am expecting:
reportmonth
category
2020-01
5
2021-01
10
2021-02
5
2021-02
10
2021-03
10
2021-03
5
2021-04
5
2021-04
10
2021-05
5
2021-05
10
2021-06
5
2021-06
10
2021-07
5
2021-07
10
I've tried using
first_value(category) ignore nulls OVER (ORDER BY reportmonth ROWS BETWEEN CURRENT ROW AND UNBOUNDED following) AS forward_fill
but this seems to stop once it hits the next category number. It also does not duplicate the report months.
There are other posts / questions similar to this, however none of them need to have the category repeat by the reportmonth.
Any help would be greatly appreciated.
You need to cross join the distinct reportmonth values with the distinct category values, try the following:
Select R.reportmonth, C.category
FROM
(
Select Distinct reportmonth
From yourTbl
) R
Cross Join
(
Select Distinct category
From yourTbl Where category is not null
) C
Order By R.reportmonth
The output according to your sample data:

SQL: How to group rows with the condition that sum of fields is limited to a certain value?

This is my table:
id user_id date balance
1 1 2016-05-10 10
2 1 2016-05-10 30
3 2 2017-04-24 5
4 2 2017-04-27 10
5 3 2017-11-10 40
I want to group the rows by user_id and sum the balance, but so that the sum is equal or less than 30. Moreover, I need to display the minimum date in the group. It should look like this:
id balance date_start
1-1 10 2016-05-10
1-2 30 2016-05-10
2-1 15 2017-04-24
Excuse for my language. Thanks.
You should be able to do so by using group by & having, here is an example of what may solve your case :
SELECT id, user_id, SUM(balance) as balance, data_start
FROM your_table
GROUP BY user_id
HAVING SUM(balance) >= 30
AND MIN(date_start)
This is a good way to do it with one query, but it is a complex query and you should be careful if using it on a very large tables.

Count first occurring record per time period

In my table trips , I have two columns: created_at and user_id
Unique users take many different trips. My goal is to count the very first trip made unique per each user_ids per year-month. I understand that in this case the min() function should be applied.
In a previous query, all unique users per year-month were aggregated:
SELECT to_char(created_at, 'YYYY-MM') as yyyymm, COUNT(DISTINCT user_id)
FROM trips
GROUP BY yyyymm
ORDER BY yyyymm;
Where in the above query should min() be integrated? In other words, instead of counting all unique user id's per month, I only need to count the first occurrence of unique user id per month.
The sample input would look like:
> routes
user_id created_at
1 1 2015-08-07 07:18:21
2 2 2015-05-06 20:43:52
3 3 2015-05-06 20:53:54
4 1 2015-03-30 20:09:07
5 2 2015-10-01 18:28:32
6 3 2015-08-07 07:29:29
7 1 2015-08-28 13:45:44
8 2 2015-08-07 07:37:31
9 3 2015-03-30 20:14:04
10 1 2015-08-07 07:08:50
And the output would be:
count Y-m
1 0 2015-01
2 0 2015-02
3 2 2015-03
4 0 2015-04
5 1 2015-05
Because the first occurrences of user_id 1 and 3 were in March and the first occurrence of user_id 2 was in May
You can do this with 2 levels of aggregation. Get the min time per user_id and then count.
SELECT to_char(first_time, 'YYYY-MM'),count(*)
from (
SELECT user_id,MIN(created_at) as first_time
FROM trips
GROUP BY user_id
) t
GROUP BY to_char(first_time, 'YYYY-MM')

Get the latest price SQLITE

I have a table which contain _id, underSubheadId, wefDate, price.
Whenever a product is created or price is edited an entry is made in this table also.
What I want is if I enter a date, I get the latest price of all distinct UnderSubheadIds before the date (or on that date if no entry found)
_id underHeadId wefDate price
1 1 2016-11-01 5
2 2 2016-11-01 50
3 1 2016-11-25 500
4 3 2016-11-01 20
5 4 2016-11-11 30
6 5 2016-11-01 40
7 3 2016-11-20 25
8 5 2016-11-15 52
If I enter 2016-11-20 as date I should get
1 5
2 50
3 25
4 30
5 52
I have achieved the result using ROW NUMBER function in SQL SERVER, but I want this result in Sqlite which don't have such function.
Also if a date like 2016-10-25(which have no entries) is entered I want the price of the date which is first.
Like for 1 we will get price as 5 as the nearest and the 1st entry is 2016-11-01.
This is the query for SQL SERVER which is working fine. But I want it for Sqlite which don't have ROW_NUMBER function.
select underSubHeadId,price from(
select underSubHeadId,price, ROW_NUMBER() OVER (Partition By underSubHeadId order by wefDate desc) rn from rates
where wefDate<='2016-11-19') newTable
where newTable.rn=1
Thank You
This is a little tricky, but here is one way:
select t.*
from t
where t.wefDate = (select max(t2.wefDate)
from t t2
where t2.underSubHeadId = t.underSubHeadId and
t2.wefdate <= '2016-11-20'
);
select underHeadId, max(price)
from t
where wefDate <= "2016-11-20"
group by underHead;

Performing calculations based on dates in oracle

I have the following tables.
Accounts(account_number*,balance)
Transactions(account_number*,transaction_number*,date,amount,type)
Date is the date that the transaction happened. Amount is the amount of the transaction
and it can have a positive or a negative value, dependent of the type(Withdrawal -,Deposit +). I think the type is irrelevant here as the amount is already given in the proper way.
I need to write a query which points out the account_number of the accounts that have at least once had negative balance.
Here's some sample data from the Transactions table, ordered by account_number and date.
account_number transaction_number date amount type
--------------------------------------------------------------------
1 2 02/03/2013 -20000 withdrawal
1 3 03/15/2013 300 deposit
1 1 01/01/2013 100 deposit
2 1 04/15/2013 235236 deposit
3 1 06/15/2013 500 deposit
4 1 03/01/2013 10 deposit
4 2 04/01/2013 80 deposit
5 1 11/11/2013 10000 deposit
5 2 12/11/2013 20000 deposit
5 3 12/13/2013 -10002 withdrawal
6 1 03/15/2013 102300 deposit
7 1 03/15/2013 100 deposit
8 1 08/08/2013 133990 deposit
9 1 05/09/2013 10000 deposit
9 2 06/01/2013 300 deposit
9 3 10/11/2013 23 deposit
Something like this with an analytic to keep a running balance for an account:
SELECT DISTINCT account_number
FROM ( SELECT account_number
,SUM(amount)
OVER (PARTITION BY account_number ORDER BY date) AS running_balance
FROM transactions
) x
WHERE running_balance < 0
Explanation:
It is using an analytic function: the PARTITION BY breaks the table into groups identified by the account number. Within each group, the data is ordered by date. Then there is a walk through each element in the ordered group and the SUM function is applied (by default summing everything from the beginning of the group to the current row). This gives you a running balance. Just run the inner query on its own and take a look at the output, then read a bit about analytic queries. They are pretty cool.