how to create columns from unique values of a column and do a cumulative addition or subtraction.
I have a record of transactions like this
transaction_id
created_at
transaction_type
amount
124
2020-08-06 17:00:09
2
25.00
123
2020-08-06 17:00:03
1
50.00
There are various types of transactions, which in turn have different effects .Some results are withdrawals from th account, some are deposits and others don’t affect it at all. This information is summarized in another table (let’s call it ‘transaction_types’), as shown below:
id
description
effect
1
Manual Deposit
add
2
Direct Payment
subtract
id in the table transaction_type is a foreign key transactions. transaction_type = transactions_type.id
now if the initial amount is $10000 I need to create a table like this
transaction_id
initial_balance
deposit
withdrawal
final_balance
123
100000
50
100050
124
100050
25
100025
I don't know how to create new columns based on unique values in some columns also to start a cumulative sum from 10000. This is the query I tried select f.transaction_id, t.created_at, sum(case when d.effect = 'subtract' then -1 else 1 end * amount) from f inner join d on f.transaction_type = d.id
WITH RECURSIVE RowNums AS
(
SELECT
transaction_id,
created_at,
CAST(amount AS SIGNED) AS amount,
effect,
ROW_NUMBER() OVER
(
ORDER BY
created_at,
transaction_id
) AS RowNum
FROM transactions t
JOIN transaction_types tt
ON t.transaction_type = tt.id
),
Balances AS
(
SELECT
transaction_id,
created_at,
amount,
effect,
100000 AS initial_balance,
100000 +
(
CASE effect
WHEN 'add' THEN amount
WHEN 'subtract' THEN (-(amount))
END
) AS final_balance,
RowNum
FROM RowNums
WHERE RowNum = 1
UNION ALL
SELECT
rn.transaction_id,
rn.created_at,
rn.amount,
rn.effect,
b.final_balance,
b.final_balance +
(
CASE rn.effect
WHEN 'add' THEN rn.amount
WHEN 'subtract' THEN (-(rn.amount))
END
),
rn.RowNum
FROM Balances b
JOIN RowNums rn
ON (b.RowNum + 1) = rn.RowNum
)
SELECT
transaction_id,
initial_balance,
CASE effect
WHEN 'add'
THEN CAST(amount AS CHAR(20))
ELSE ''
END AS deposit,
CASE effect
WHEN 'subtract'
THEN CAST(amount AS CHAR(20))
ELSE ''
END AS withdrawal,
final_balance
FROM Balances
ORDER BY
created_at,
transaction_id
source : https://www.reddit.com/r/sqltutorial/comments/sx3ycb/comment/hxr6f9l/?utm_source=share&utm_medium=web2x&context=3
Related
I have this table called transactions, where agents can give certain amounts to other agents, we have 2 columns, one called agent_from which is the agent that put the amount and agent_to is the one reciving the amount.
An example with the id 1 would be that the agent2 is giving an amount of 300 to the agent8
The report that I would like to do is a sum and a group by agent_from and agent_to
Right now I am able to make the query separatly like this
SELECT agent_from,
SUM(amount) as from_transaccions
FROM `transactions` GROUP BY agent_from;
This would give me this result:
This return a sum of all the amounts made by agent_from.
Now I can repeat this query changing the column name from agent_from to agent_to so I can get the sum of all the amounts recived by agent_to, that will look like this:
An example would be that the agent8 recived 2 transaccions (300 + 450) = 750
Now what I want to do is make this 2 querys into one that will look like this:
Refer query below -
with data_cte as (
(select agent_from agent, amount, 'af' flag from transactions) union all
(select agent_to agent, amount, 'at' flag from transactions)
)
select agent,
sum(case when flag='af' then amount else 0 end) from_sum,
sum(case when flag='at' then amount else 0 end) to_sum
from data_cte
group by agent
union all
select 'total' as col1,
sum(case when flag='af' then amount else 0 end) from_sum,
sum(case when flag='at' then amount else 0 end) to_sum
from data_cte
group by col1
order by agent
fiddle.
Use UNION ALL to split each row of the table to 2 rows so that you separate the 2 agents and aggregate:
SELECT COALESCE(agent, 'total') agent,
SUM(`from`) `from`,
SUM(`to`) `to`
FROM (
SELECT agent_from agent, amount `from`, 0 `to` FROM `transactions`
UNION ALL
SELECT agent_to, 0 `from`, amount `to` FROM `transactions`
) t
GROUP BY agent WITH ROLLUP
ORDER BY GROUPING(agent);
See the demo.
I have a query that changes the status of some fields and rate of another field.
fiddle expample
CREATE TABLE mytable(
item_id INTEGER NOT NULL PRIMARY KEY
,rate INTEGER NOT NULL
,status VARCHAR(6) NOT NULL
);
INSERT INTO mytable(item_id,rate,status) VALUES (1,12,'credit');
INSERT INTO mytable(item_id,rate,status) VALUES (2,10,'credit');
INSERT INTO mytable(item_id,rate,status) VALUES (3,10,'credit');
INSERT INTO mytable(item_id,rate,status) VALUES (4,20,'cash');
INSERT INTO mytable(item_id,rate,status) VALUES (5,55,'credit');
select item_id, 'cash' as status,
case when sum_rate >= 23 then sum_rate - 23 else rate end as rate
from (
select t.*, sum(rate) over(order by item_id) sum_rate
from mytable t
where status = 'credit'
) t
where sum_rate - rate < 23;
update mytable t
inner join (
select item_id, sum(rate) over(order by item_id) sum_rate
from mytable t
where status = 'credit'
) t1 on t1.item_id = t.item_id
set
t.status = 'cash',
t.rate = case when t1.sum_rate >= 23 then t1.sum_rate - 23 else t.rate end
where t1.sum_rate - t.rate < 23
The logic checks the sum of all rows until a value of 23 is reached and changes the status of those rows to cash, in the example since the total of rate in top 3 rows is greater than 23, the third row is updated with the balance after adding the first two rows. I want the third row status to remain the same and only the rate to be updated.
The problem the above code is that it updates all the status of all rows having sum of rate 23.
The original question for reference
Ok I was able to get it done with the following addition to the query so posting it in case it may help anyone with a similar requirement.
t.status = case when t1.sum_rate >= 23 then 'credit' else 'cash' end
Here is my table
Which have field type which means 1 is for income and 2 is for expense
Now requirement is for example in table there is two transaction made on 2-10-2018 so i want data as following
Expected Output
id created_date total_amount
1 1-10-18 10
2 2-10-18 20(It calculates all only income transaction made on 2nd date)
3 3-10-18 10
and so on...
it will return an new field which contains only incom transaction made on perticulur day
What i had try is
SELECT * FROM `transaction`WHERE type = 1 ORDER BY created_date ASC
UNION
SELECT()
//But it wont work
SELECT created_date,amount,status FROM
(
SELECT COUNT(amount) AS totalTrans FROM transaction WHERE created_date = created_date
) x
transaction
You can Also See Schema HERE http://sqlfiddle.com/#!9/6983b9
You can Count() the total number of expense transactions using conditional function If(), on a group of created_date.
Similarly, you can Sum() the amount of expense done using If(), on a created_date.
Try the following:
SELECT
`created_date`,
SUM(IF (`type` = 2, `amount`, 0)) AS total_expense_amount,
COUNT(IF (`type` = 2, `id`, NULL)) AS expense_count
FROM
`transaction`
GROUP BY `created_date`
ORDER BY `created_date` ASC
Do you just want a WHERE clause?
SELECT t.created_date, SUM(amount) as total_amount
FROM transaction t
WHERE type = 2
GROUP BY t.created_date
ORDER BY created_date ASC ;
I have a query which works great for 1000 records or less but now I need to optimize it for 50,000+ records and when I run it on that it just stalls...
Here is my code:
SELECT
b1.account_num,b1.effective_date as ed1,b1.amount as am1,
b2.effective_date as ed2,b2.amount as am2
FROM bill b1
left join bill b2 on (b1.account_num=b2.account_num)
where b1.effective_date = (select max(effective_date) from bill where account_num = b1.account_num)
and (b2.effective_date = (select max(effective_date) from bill where account_num = b1.account_num and effective_date < (select max(effective_date) from bill where account_num = b1.account_num)) or b2.effective_date is null)
ORDER BY b1.effective_date DESC
My objective is to get the latest two effective dates and amounts from one table with many records.
Here is a working answer from your SQL-Fiddle baseline
First, the inner preQuery gets the max date per account. That is then joined to the bill table per account AND the effective date is less than the max already detected.
That is then joined to each respective bill for their amounts.
select
FB1.account_num,
FB1.effective_date as ed1,
FB1.amount as am1,
FB2.effective_date as ed2,
FB2.amount as am2
from
( select
pq1.account_num,
pq1.latestBill,
max( b2.effective_date ) as secondLastBill
from
( SELECT
b1.account_num,
max( b1.effective_date ) latestBill
from
bill b1
group by
b1.account_num ) pq1
LEFT JOIN bill b2
on pq1.account_num = b2.account_num
AND b2.effective_date < pq1.latestBill
group by
pq1.account_num ) Final
JOIN Bill FB1
on Final.Account_Num = FB1.Account_Num
AND Final.LatestBill = FB1.Effective_Date
LEFT JOIN Bill FB2
on Final.Account_Num = FB2.Account_Num
AND Final.secondLastBill = FB2.Effective_Date
ORDER BY
Final.latestBill DESC
In mysql , window analytic function like row_number is not there, so we can simulate the same using variables.
The good thing is, the table is scanned only once with this approach.
A row_number is assigned to each partition which is divided based on ( account number, effective date ) and only 2 rows are selected from each partition.
select account_num,
max(case when row_number =1 then effective_date end) as ed1,
max(case when row_number =1 then amount end) as am1,
max(case when row_number =2 then effective_date end) as ed2,
max(case when row_number =2 then amount end )as am2
from (
select account_num, effective_date, amount,
#num := if(#prevacct= account_num , #num + 1, 1) as row_number,
#prevacct := account_num as dummy
from bill, (select #num:=0, #prevacct := '' ) as var
order by account_num , effective_date desc
)T
where row_number <=2
group by account_num
I have the following query which will return the number of users in table transactions who have earned between $100 and $200
SELECT COUNT(users.id)
FROM transactions
LEFT JOIN users ON users.id = transactions.user_id
WHERE transactions.amount > 100 AND transactions.amount < 200
The above query returns the correct result below:
COUNT(users.id)
559
I would like to extend it so that the query can return data in the following format:
COUNT(users.id) : amount
1678 : 0-100
559 : 100-200
13 : 200-300
How can I do this?
You can use a CASE expression inside of your aggregate function which will get the result in columns:
SELECT
COUNT(case when amount >= 0 and amount <= 100 then users.id end) Amt0_100,
COUNT(case when amount >= 101 and amount <= 200 then users.id end) Amt101_200,
COUNT(case when amount >= 201 and amount <= 300 then users.id end) Amt201_300
FROM transactions
LEFT JOIN users
ON users.id = transactions.user_id;
See SQL Fiddle with Demo
You will notice that I altered the ranges from 0-100, 101-200, 201-300 otherwise you will have user ids being counted twice on the 100, 200 values.
If you want the values in rows, then you can use:
select count(u.id),
CASE
WHEN amount >=0 and amount <=100 THEN '0-100'
WHEN amount >=101 and amount <=200 THEN '101-200'
WHEN amount >=201 and amount <=300 THEN '101-300'
END Amount
from transactions t
left join users u
on u.id = t.user_id
group by
CASE
WHEN amount >=0 and amount <=100 THEN '0-100'
WHEN amount >=101 and amount <=200 THEN '101-200'
WHEN amount >=201 and amount <=300 THEN '101-300'
END
See SQL Fiddle with Demo
But if you have many ranges that you need to calculate the counts on, then you might want to consider creating a table with the ranges, similar to the following:
create table report_range
(
start_range int,
end_range int
);
insert into report_range values
(0, 100),
(101, 200),
(201, 300);
Then you can use this table to join to your current tables and group by the range values:
select count(u.id) Total, concat(start_range, '-', end_range) amount
from transactions t
left join users u
on u.id = t.user_id
left join report_range r
on t.amount >= r.start_range
and t.amount<= r.end_range
group by concat(start_range, '-', end_range);
See SQL Fiddle with Demo.
If you don't want to create a new table with the ranges, then you can always use a derived table to get the same result:
select count(u.id) Total, concat(start_range, '-', end_range) amount
from transactions t
left join users u
on u.id = t.user_id
left join
(
select 0 start_range, 100 end_range union all
select 101 start_range, 200 end_range union all
select 201 start_range, 300 end_range
) r
on t.amount >= r.start_range
and t.amount<= r.end_range
group by concat(start_range, '-', end_range);
See SQL Fiddle with Demo
One way to do this would be to use a case/when statement in your group by.
SELECT
-- NB this must match your group by statement exactly
-- otherwise you will get an error
CASE
WHEN amount <= 100
THEN '0-100'
WHEN amount <= 200
THEN '100-200'
ELSE '201+'
END Amount,
COUNT(*)
FROM
transactions
GROUP BY
CASE
WHEN amount <= 100
THEN '0-100'
WHEN amount <= 200
THEN '100-200'
ELSE '201+'
END
If you plan on using the grouping elsewhere, it probably makes sense to define it as a scalar function (it will also look cleaner)
e.g.
SELECT
AmountGrouping(amount),
COUNT(*)
FROM
transactions
GROUP BY
AmountGrouping(amount)
If you want to be fully generic:
SELECT
concat(((amount DIV 100) * 100),'-',(((amount DIV 100) + 1) * 100)) AmountGroup,
COUNT(*)
FROM
transactions
GROUP BY
AmountGroup
Sql Fiddle
Bilbo, I tried to be creative and found a very nice solution [ for those who love math (like me) ]
It's always surprising when MySQL integer division operator solves our problems.
DROP SCHEMA IF EXISTS `stackoverflow3`;
CREATE SCHEMA `stackoverflow3`;
USE `stackoverflow3`;
CREATE TABLE users (
id INT UNSIGNED PRIMARY KEY NOT NULL AUTO_INCREMENT,
name VARCHAR(25) NOT NULL DEFAULT "-");
CREATE TABLE transactions(
id INT UNSIGNED PRIMARY KEY NOT NULL AUTO_INCREMENT,
user_id INT UNSIGNED NOT NULL,
amount INT UNSIGNED DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES users (id));
INSERT users () VALUES (),(),();
INSERT transactions (user_id,amount)
VALUES (1,120),(2,270),(3, 350),
(2,500), (1,599), (1,550), (3,10),
(3,20), (3,30), (3,50), (3,750);
SELECT
COUNT(t.id),
CONCAT(
((t.amount DIV 100)*100)," to ",((t.amount DIV 100 + 1)*100-1)
) AS amount_range
FROM transactions AS t
GROUP BY amount_range;
Awaiting your questions, Mr. Baggins.