MySQL query for date range transactions statement - mysql

I have a transactions table.
id account_id trx_date trx_type amount remarks
------ ---------- ---------- -------- ------ ---------
1 1 2017-12-10 DEPOSIT 500 test
2 1 2017-12-11 DEPOSIT 500 test
3 1 2017-12-12 DEPOSIT 6000 test
4 1 2017-12-13 WITHDRAW 300 test
5 1 2017-12-13 DEPOSIT 200 test
I want to result in this format but I can't figure out how will be the query for fetching data like as below. Here all fields are same as transactions table. op_bal, cl_bal are dynamic field.
date trx_type op_bal amount cl_bal
2017-12-12 DEPOSIT 1000 6000 7000
2017-12-13 WITHDRAW 7000 300 6700
2017-12-13 DEPOSIT 6700 200 6900

hello for the given output I have created this sqlfiddle please check for the answer
CREATE TABLE transactions (
id bigint(12) PRIMARY KEY auto_increment,
account_id bigint(12),
trx_date date,
trx_type enum('DEPOSIT','WITHDRAW'),
amount float(10,2),
remarks varchar(50)
);
INSERT INTO transactions (account_id, trx_date, trx_type, amount, remarks)
VALUES ('1','2017-12-10','DEPOSIT',500,'test'),
('1','2017-12-11','DEPOSIT',500,'test'),
('1','2017-12-12','DEPOSIT',6000,'test'),
('1','2017-12-13','WITHDRAW',300,'test'),
('1','2017-12-13','DEPOSIT',200,'test');
and query to achieve result is
SELECT
trx_date,
trx_type,
(SELECT SUM(CASE tr.trx_type WHEN 'DEPOSIT' THEN CONCAT('+',tr.amount) ELSE CONCAT('-', tr.amount) END) as amount FROM transactions as tr WHERE tr.id < (SELECT MAX(t.id) FROM transactions as t WHERE t.id <= transactions.id LIMIT 1) ) as op_bal,
amount AS trx_amount,
(SELECT SUM(CASE tr.trx_type WHEN 'DEPOSIT' THEN CONCAT('+',tr.amount) ELSE CONCAT('-', tr.amount) END) as amount FROM transactions as tr WHERE tr.id <= (SELECT MAX(t.id) FROM transactions as t WHERE t.id <= transactions.id LIMIT 1) ) as cl_bal
FROM transactions
WHERE DATE(trx_date) BETWEEN '2017-12-12' AND '2017-12-13'
Hope it will work for you.

Related

Creating an overdraft statement

I'm currently stuck on how to create a statement that shows daily overdraft statements for a particular council.
I have the following, councils, users, markets, market_transactions, user_deposits.
market_transaction run daily reducing user's account balance. When the account_balance is 0 the users go into overdraft (negative). When users make a deposit their account balance increases.
I Have put the following tables to show how transactions and deposits are stored.
if I reverse today's transactions I'm able to get what account balance a user had yesterday but to formulate a query to get the daily OD amount is where the problem is.
USERS
user_id
name
account_bal
1
Wells
-5
2
James
100
3
Joy
10
4
Mumbi
-300
DEPOSITS
id
user_id
amount
date
1
1
5
2021-04-26
2
3
10
2021-04-26
3
3
5
2021-04-25
4
4
5
2021-04-25
TRANSACTIONS
id
user_id
amount_tendered
date
1
1
5
2021-04-27
2
2
10
2021-04-26
3
3
15
2021-04-26
4
4
50
2021-04-25
The Relationships are as follows,
COUNCILS
council_id
name
1
a
2
b
3
c
MARKETS
market_id
name
council_id
1
x
3
2
y
1
3
z
2
MARTKET_USER_LINK
id
market_id
user_id
1
1
3
2
2
2
3
3
1
I'm running this SQL query to get the total amount users have spent and subtracting with the current user account balance.
Don't know If I can use this to figure out the account_balance for each day.
SELECT u.user_id, total_spent, total_deposits,m.council_id
FROM users u
JOIN market_user_link ul ON ul.user_id= u.user_id
LEFT JOIN markets m ON ul.market_id =m.market_id
LEFT JOIN councils c ON m.council_id =c.council_id
LEFT JOIN (
SELECT user_id, SUM(amount_tendered) AS total_spent
FROM transactions
WHERE DATE(date) BETWEEN DATE('2021-02-01') AND DATE(NOW())
GROUP BY user_id
) t ON t.user_id= u.user_id
ORDER BY user_id, total_spent ASC
// looks like this when run
| user_id | total_spent | council_id |
|-------------|----------------|------------|
| 1 | 50.00 | 1 |
| 2 | 2.00 | 3 |
I was hoping to reverse transactions and deposits done to get the account balance for a day then get the sum of users with an account balance < 0... But this has just failed to work.
The goal is to produce a query that shows daily overdraft (Only SUM the total account balance of users with account balance below 0 ) for a particular council.
Expected Result
date
council_id
o_d_amount
2021-04-24
1
-300.00
2021-04-24
2
-60.00
2021-04-24
3
-900.00
2021-04-25
1
-600.00
2021-04-25
2
-100.00
2021-04-25
3
-1200.00
This is actually not that hard, but the way you asked makes it hard to follow.
Also, your expected result should match the data you provided.
Edited: Previous solution was wrong - It counted withdraws and deposits more than once if you have more than one event for each user/date.
Start by having the total exchanged on each day, like
select user_id, date, sum(amount) exchanged_on_day from (
select user_id, date, amount amount from deposits
union all select user_id, date, -amount_tendered amount from transactions
) d
group by user_id, date
order by user_id, date;
What follows gets the state of the account only on days that had any deposits or withdraws.
To get the results of all days (and not just those with account movement) you just have to change the cross join part to get a table with all dates you want (like Get all dates between two dates in SQL Server) but I digress...
select dates.date, c.council_id, u.name username
, u.account_bal - sum(case when e.date >= dates.date then e.exchanged_on_day else 0 end) as amount_on_start_of_day
, u.account_bal - sum(case when e.date > dates.date then e.exchanged_on_day else 0 end) as amount_on_end_of_day
from councils c
inner join markets m on c.council_id=m.council_id
inner join market_user_link mul on m.market_id=mul.market_id
inner join users u on mul.user_id=u.user_id
left join (
select user_id, date, sum(amount) exchanged_on_day from (
select user_id, date, amount amount from deposits
union all select user_id, date, -amount_tendered amount from transactions
) d group by user_id, date
) e on u.user_id=e.user_id --exchange on each Day
cross join (select distinct date from (select date from deposits union select date from transactions) datesInternal) dates --all days that had a transaction
group by dates.date, c.council_id, u.name, u.account_bal
order by dates.date desc, c.council_id, u.name;
From there you can rearrange to get the result you want.
select date, council_id
, sum(case when amount_on_start_of_day<0 then amount_on_start_of_day else 0 end) o_d_amount_start
, sum(case when amount_on_end_of_day<0 then amount_on_end_of_day else 0 end) o_d_amount_end
from (
select dates.date, c.council_id, u.name username
, u.account_bal - sum(case when e.date >= dates.date then e.exchanged_on_day else 0 end) as amount_on_start_of_day
, u.account_bal - sum(case when e.date > dates.date then e.exchanged_on_day else 0 end) as amount_on_end_of_day
from councils c
inner join markets m on c.council_id=m.council_id
inner join market_user_link mul on m.market_id=mul.market_id
inner join users u on mul.user_id=u.user_id
left join (
select user_id, date, sum(amount) exchanged_on_day from (
select user_id, date, amount amount from deposits
union all select user_id, date, -amount_tendered amount from transactions
) d group by user_id, date
) e on u.user_id=e.user_id --exchange on each Day
cross join (select distinct date from (select date from deposits union select date from transactions) datesInternal) dates --all days that had a transaction
group by dates.date, c.council_id, u.name, u.account_bal
) result
group by date, council_id
order by date;
You can check it on https://www.db-fiddle.com/f/msScT6B5F7FjU2aQXVr2da/6
Basically the query maps users to councils, caculates periods of overdrafts for users, them aggregates over councils. I assume that starting balance is dated start of the month '2021-04-01' (it could be ending balance as well, see below), change it as needed. Also that negative starting balance counts as an overdraft. For simplicity and debugging the query is divided into a number of steps.
with uc as (
select distinct m.council_id, mul.user_id
from markets m
join market_user_link mul on m.market_id = mul.market_id
),
user_running_total as (
select user_id, date,
coalesce(lead(date) over(partition by user_id order by date) - interval 1 day, date) nxt,
sum(sum(s)) over(partition by user_id order by date) rt
from (
select user_id, date, -amount_tendered s
from transactions
union all
select user_id, date, amount
from deposits
union all
select user_id, se.d, se.s
from users
cross join lateral (
select date(NOW() + interval 1 day) d, 0 s
union all
select '2021-04-01' d, account_bal
) se
) t
group by user_id, date
),
user_overdraft as (
select user_id, date, nxt, least(rt, 0) ovd
from user_running_total
where date <= date(NOW())
),
dates as (
select date
from user_overdraft
union
select nxt
from user_overdraft
),
council__overdraft as (
select uc.council_id, d.date, sum(uo.ovd) total_overdraft, lag(sum(uo.ovd), 1, sum(uo.ovd) - 1) over(partition by uc.council_id order by d.date) prev_ovd
from uc
cross join dates d
join user_overdraft uo on uc.user_id = uo.user_id and d.date between uo.date and uo.nxt
group by uc.council_id, d.date
)
select council_id, date, total_overdraft
from council__overdraft
where total_overdraft <> prev_ovd
order by date, council_id
Really council__overdraft is quite usable, the last step just compacts output excluding intermidiate dates when overdraft is not changed.
With following sample data:
users
user_id name account_bal
1 Wells -5
2 James 100
3 Joy 10
4 Mumbi -300
deposits, odered by date, extra row added for the last date
id user_id amount date
3 3 5 2021-04-25
4 4 5 2021-04-25
1 1 5 2021-04-26
2 3 10 2021-04-26
5 3 73 2021-05-06
transactions, odered by date (note the added row, to illustrate running total in action)
id user_id amount_tendered date
5 4 50 2021-04-25
2 2 10 2021-04-26
3 3 15 2021-04-26
1 1 5 2021-04-27
4 3 17 2021-04-27
councils
council_id name
1 a
2 b
3 c
markets
market_id name council_id
1 x 3
2 y 1
3 z 2
market_user_link
id market_id user_id
1 1 3
2 2 2
3 3 1
4 3 4
the query ouput is
council_id
date
overdraft
1
2021-04-01
0
2
2021-04-01
-305
3
2021-04-01
0
2
2021-04-25
-350
2
2021-04-26
-345
2
2021-04-27
-350
3
2021-04-27
-7
3
2021-05-06
0
Alternatively, provided the users table is holding a closing (NOW()) balance, replace user_running_total CTE with the following code
user_running_total as (
select user_id, date,
coalesce(lead(date) over(partition by user_id order by date) - interval 1 day, date) nxt,
coalesce(sum(sum(s)) over(partition by user_id order by date desc
rows between unbounded preceding and 1 preceding), sum(s)) rt
from (
select user_id, date, amount_tendered s
from transactions
union all
select user_id, date, -amount
from deposits
union all
select user_id, se.d, se.s
from users
cross join lateral (
select date(NOW() + interval 1 day) d, account_bal s
union all
select '2021-04-01' d, 0
) se
) t
where DATE(date) between date '2021-04-01' and date(NOW() + interval 1 day)
group by user_id, date
),
This way the query starts with closing balance dated next date after now and rollouts a running total in the reverse order till '2021-04-01' as a starting date.
Output
council_id
date
overdraft
1
2021-04-01
0
2
2021-04-01
-260
3
2021-04-01
-46
2
2021-04-25
-305
3
2021-04-25
-41
2
2021-04-26
-300
3
2021-04-26
-46
2
2021-04-27
-305
3
2021-04-27
-63
3
2021-05-06
0
db-fiddle both versions

How to get SUM of a column of a child table when query parent table?

I have two tables
Account Table (account)
id name
1 Account 1
2 Account 2
3 Account 3
4 Account 2
Transaction Table (transaction)
id account_id type amount datetime
1 1 credit 500 2020-04-01 06:00:00
2 1 credit 300 2020-04-01 06:00:00
3 2 credit 100 2020-04-01 06:00:00
4 2 debit 50 2020-04-01 06:00:00
5 1 debit 600 2020-04-01 06:00:00
6 3 credit 1000 2020-04-01 06:00:00
7 1 credit 100
My target is to get account id, name, balance in one query. The balance will be calculated from the transaction table as SUM of Credit Amount - SUM of Debit Amount for a given account.
Target Output
id name balance
1 Account 1 300
2 Account 2 50
3 Account 3 1000
4 Account 4 0
Possible Query
SELECT id, name, ((SELECT SUM(amount) FROM transaction WHERE type = 'credit' AND account_id = {ACCOUNT_ID} ) - (SELECT SUM(amount) FROM transaction WHERE type = 'debit' AND account_id = {ACCOUNT_ID} )) as balance
Is it possible to do this in one query and If yes How to do it.
You could do the aggregation in a derived table to avoid the issues of having to group by a lot of fields in the top level. For example:
SELECT a.id, a.name, COALESCE(b.balance, 0) AS balance
FROM account a
LEFT JOIN (
SELECT account_id,
SUM(CASE WHEN type='credit' THEN amount ELSE 0 END) -
SUM(CASE WHEN type='debit' THEN amount ELSE 0 END) AS balance
FROM transaction
GROUP BY account_id
) b ON b.account_id = a.id
Output:
id name balance
1 Account 1 300
2 Account 2 50
3 Account 3 1000
4 Account 2 0
Demo on SQLFiddle
You need a left join of account to transaction so you can group by each account and conditionally sum the amount depending on the type:
select
a.id, a.name,
sum(case when type = 'credit' then 1 else -1 end * coalesce(t.amount, 0)) balance
from account a left join transaction t
on t.account_id = a.id
group by a.id, a.name

How to update the value of the least value row with the value of the maximum value row using MySQL?

I have a table which has a combination of item_id and payment_id columns as a key.
Every two rows have the same item_id value.
This is how the table looks.
item_id payment_id amount
1 140 1000
1 141 3000
2 141 500
2 145 600
3 4 4000
3 735 9000
How to subtract the amount value of the least payment_id row from the amount value of the maximum payment_id row (of the two rows with the same item_id) using MySQL?
To clarify, this is how the table I want.
item_id payment_id amount
1 140 1000
1 141 2000 : 3000 - 1000
2 141 500
2 145 100 : 600 - 500
3 4 4000
3 735 5000 : 9000 - 4000
Cheer!
You can get the new amount with this query:
select p1.item_id, p1.payment_id, p1.amount - (
select p0.amount
from payments p0
where p0.item_id = p1.item_id
and p0.payment_id < p1.payment_id
order by p0.payment_id
limit 1
) as new_amount
from payments p1
having new_amount is not null;
It will subtract the amount of the "last" row with the same item_id (if present).
You can then use that query in the UPDATE statement as derived table joined to your original table:
update payments p
join (
select p1.item_id, p1.payment_id, p1.amount - (
select p0.amount
from payments p0
where p0.item_id = p1.item_id
and p0.payment_id < p1.payment_id
order by p0.payment_id
limit 1
) as new_amount
from payments p1
having new_amount is not null
) p1 using (item_id, payment_id)
set p.amount = p1.new_amount;
Demo: http://rextester.com/DJD86481
UPDATE tt JOIN (SELECT item_id, MAX(payment_id) mp , (SUBSTRING_INDEX(GROUP_CONCAT(amount ORDER BY payment_id DESC),',',1) - SUBSTRING_INDEX(GROUP_CONCAT(amount ORDER BY payment_id ),',',1)) maxdif FROM tt GROUP BY item_id) s
ON tt.item_id=s.item_id
SET tt.amount =s.maxdif
WHERE tt.payment_id =s.mp AND tt.item_id=s.item_id;
SELECT * FROM tt;
See it working

SQL Query - How to get difference of sum of multiple of cells of rows

I need to get the difference of the sums of two fields which are in single table (really sorry if this is confusing), please read on for an example
Id type account_id stock_id volume price value
==========================================================
1 BUY 1 1 5 500 2500
2 BUY 1 4 30 200 6000
6 BUY 1 1 10 500 5000
7 SELL 1 1 3 500 1500
8 SELL 1 1 2 500 1000
9 SELL 1 4 20 120 2400
Above is my sample data and I would my SQL query result to be something like,
account_id stock_id volume totalAmount
============================================
1 1 10 5000
1 4 10 3600
basically here I am trying to get the total buy value of unique account & stock combination and subtract with the total sell value
Any help here would be highly appreciated.
Thanks in advance
Fiddle Test:
http://sqlfiddle.com/#!2/53035/1/0
select account_id,
stock_id,
sum(case when type = 'BUY' then volume else -volume end) as volume,
sum(case when type = 'BUY' then value else -value end) as totalamount
from tbl
group by account_id,
stock_id
having sum(case when type = 'BUY' then volume else -volume end) <> 0
I added the HAVING clause based on your comment.
Just to reduce duplication I would change Brian's code to this:
SELECT
account_id,
stock_id,
SUM(volume * type_sign) as total_volume,
SUM(value * type_sign) as total_value
FROM
(select t.*, case when type = 'BUY' then 1 else -1 end as type_sign
from tbl) t
GROUP BY account_id,
stock_id
select buy.account_id,buy.stock_id,(buy.volume-sell.volume) volume,(buy.totalAmount-sell.totalAmount) totalAmount from
(select account_id,stock_id,sum(volume) volume,sum(value) totalAmount from stock
where type = 'BUY'
group by account_id,stock_id) buy
inner join
(select account_id,stock_id,sum(volume) volume,sum(value) totalAmount from stock
where type = 'SELL'
group by account_id,stock_id) sell
on buy.account_id = sell.account_id and buy.stock_id = sell.stock_id

mysql - filter IN statement

I need to refine this sql select:
SELECT ch_num, payment_type, Total FROM transactions WHERE ( ch_num )
IN (
SELECT ch_num FROM transactions GROUP BY ch_num HAVING count('ch_num') > 1
)
ORDER BY `ch_num`
Result i get is:
ch_num payment_type Total
20001 visa 36.60
20001 visa 36.60
20001 mc 30.60
50019 cash 9.00
50019 mc 18.95
50023 cash 2.70
50023 visa 7.00
But i need results rows only where there is 'cash' payment_type so 'ch_no' 20001 should be omited.
Correct result would be then:
ch_num payment_type Total
50019 cash 9.00
50019 mc 18.95
50023 cash 2.70
50023 visa 7.00
SELECT ch_num, payment_type, Total
FROM transactions
WHERE ch_num IN
(
SELECT ch_num
FROM transactions
GROUP BY ch_no
HAVING count('ch_num') > 1
and sum(payment_type='cash') >= 1
)
ORDER BY `ch_num`
Here's a complete, proven code example, with test results at the end. I used Oracle, but syntax should be the same for the SQL SELECT.
create table transactions (ch_num int, payment_type varchar2(100), total float);
insert into transactions values(20001,'visa',36.60);
insert into transactions values(20001,'mc',30.60);
insert into transactions values(50019,'cash',9.00);
insert into transactions values(50019,'mc',18.95);
insert into transactions values(50023,'cash',2.70);
insert into transactions values(50023,'visa',7.00);
SELECT ch_num, payment_type, Total FROM transactions a WHERE ( ch_num )
IN (
SELECT ch_num FROM transactions GROUP BY ch_num HAVING count(ch_num) > 1
)
AND EXISTS
(SELECT ch_num FROM transactions b where payment_type = 'cash' and a.ch_num = b.ch_num)
ORDER BY ch_num
Results:
CH_NUM PAYMENT_TYPE TOTAL
1 50019 cash 9
2 50019 mc 18.95
3 50023 cash 2.7
4 50023 visa 7