How to calulate SUM previous and current row value in MySQL table? - mysql

My table is "Activity_table", which have 4 columns. How can I create a function, which SUM each Peroson 2 previous and current activities?
My Activity_table
ID PID AID Act
1 1 1 12
2 1 2 32
3 2 1 5
4 1 3 21
5 2 2 12
6 2 3 19
7 1 4 11
8 2 4 6
PID-PersonID; AID-ActivitieID; Act-Activitie Value
My target:
ID PID AID Act SUM
1 1 1 12 12
2 1 2 32 44
3 2 1 5 5
4 1 3 21 65
5 2 2 12 17
6 2 3 19 36
7 1 4 11 64
8 2 4 6 37
Sum1=12; Sum2=32+12; Sum3=5; Sum4=21+32+12; Sum5=12+5; Sum6=19+12+5; Sum7=11+21+32; Sum8=6+19+12; Thank you,

To use the two previous and the current row,
SELECT ID,PID,AID,Act,
(SELECT SUM(Act)
FROM Activity_table
WHERE ID <= a.ID
AND ID >= a.ID - 2
AND PID = a.PID)
FROM Activity_table a;

You are taking the SUM according to the PID right? Then the last two rows of your target should be modified as,
7 1 4 11 **76**
8 2 4 6 **42**
The most flexible query is for your requirement is,
SELECT ID,PID,AID,Act,
(SELECT SUM(Act)
FROM Activity_table
WHERE ID <= a.ID
AND PID = a.PID)
FROM Activity_table a;
Then if you need only the flow of SUM of a particular PID, you can change it like this,
SELECT ID,PID,AID,Act,
(SELECT SUM(Act)
FROM Activity_table
WHERE ID <= a.ID
AND PID = a.PID)
FROM Activity_table a
WHERE PID = 2;
Result:
ID PID AID Act SUM
3 2 1 5 5
5 2 2 12 17
6 2 3 19 36
8 2 4 6 42

Sorry for the previous wrong Answers (deleted).
This produces the correct answer based on your example:
SET #LastPID = 0, #Act1 = 0,#Act2 = 0,#Act3 = 0;
SELECT ID,PID,AID,Act,`SUM` FROM
(
SELECT ID,PID,AID,Act,#Act3 := IF(#LastPID != PID, 0,#Act2),#Act2 := IF(#LastPID != PID, 0,#Act1), #Act1 := Act, #Act1 + #Act2 + #Act3 `SUM`, #LastPID := PID
FROM Activity_table
ORDER BY PID,ID
) sm
ORDER BY ID
;

Related

calculating the average of marks from the beginning to this record

i have a table named test with the below structure like this
id mark join_id
1 5 1
2 4 1
3 9 1
4 5 2
5 7 2
6 12 2
i want to write a query that can get me the average of the marks from the beginning record to this record with the desired result as below
id mark join_id avg_of_previous_marks
1 5 1 5
2 4 1 4.5
3 9 1 6
4 5 2 5.75
5 7 2 6
6 12 2 7
i wrote this query but it doesn't seem to work correctly
SELECT test.id, test.mark, test.join_id, test_avg.avg_of_previous_marks FROM test
LEFT JOIN (SELECT id, join_id, AVG(mark) as avg_of_previous_marks FROM test GROUP BY
join_id) test_avg
ON test_avg.join_id = test.join_id AND test_avg.id <= test.id
and it gives this resault
id mark join_id avg_of_previous_marks
1 5 1 6
2 4 1 6
3 9 1 6
4 5 2 8
5 7 2 8
6 12 2 8
Its a simple running total that you need.
select id,mark,join_id, avg(mark) over (order by id) avg_of_previous_marks from test_avg ;
fiddle here

SQL : Increment a new column whenever there is a multiple of 5 else dont

I came across this question in a round of interview. A table has the following column.
ID
1
2
3
4
5
6
7
8
9
11
12
13
14
15
16
17
18
19
20
22
23
24
26
The question is to create a new column that starts with '1' and increments on the next ID whenever there is a multiple of 5. So the expected output is
ID
Result
1
1
2
1
3
1
4
1
5
1
6
2
7
2
8
2
9
2
11
2
12
2
13
2
14
2
15
2
16
3
17
3
18
3
19
3
20
3
22
4
23
4
24
4
26
4
You can combine two window functions: LAG() and SUM(). For example:
select id,
1 + sum(case when lid % 5 = 0 then 1 else 0 end) over(order by id) as v
from (
select *, lag(id) over(order by id) as lid from t
) x
order by id
Result:
id v
-- -
1 1
2 1
3 1
4 1
5 1
6 2
7 2
8 2
9 2
11 2
12 2
13 2
14 2
15 2
16 3
17 3
18 3
19 3
20 3
22 4
23 4
24 4
26 4
See running example at DB Fiddle.
For MySQL 5+ you may use, for example
SELECT id, (#result := COALESCE( #result + !(id % 5), 1 )) - !(id % 5) result
FROM t
CROSS JOIN (SELECT #result := NULL) init_variable
ORDER BY id
For MySQL 8+ use
SELECT id, 1 + SUM(!(id % 5)) OVER (ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) resuls
FROM t
You can do this without a subquery:
select t.*,
1 + sum( (id % 5) = 0 ) over (order by id) - (id % 5 = 0)
from t
order by id;
What is the logic here? Calculate the cumulative sum of "5"s up to this row. Then subtract out the value on this row, because the increment takes effect on the next row.
It is also tempting to write this using a window frame clause, but that ends up being a wee bit more complicated because the first value is NULL:
select t.*,
1 + coalesce(sum( (id % 5) = 0 ) over (order by id rows between unbounded preceding and 1 preceding), 0)
from t
order by id;
Here is a db<>fiddle.

Query with CASE statement in Group By I need to create an entry where no results occur in the WHEN

I have a query which groups up incoming payments into date ranges (1-7 days, 3-6 months etc.) and it largely works as I had hoped. However, I want to return a row which says 0 when no income is expected in the date range.
The group by looks like this:
group by
CASE WHEN timestampdiff(day,curdate(),data.duedate) between 0 and 7 then 1
WHEN timestampdiff(day,curdate(),data.duedate) between 8 and 14 then 2
WHEN timestampdiff(day,curdate(),data.duedate) between 15 and 30 then 3
WHEN timestampdiff(month,curdate(),data.duedate) between 1 and 2 then 4
WHEN timestampdiff(month,curdate(),data.duedate) between 2 and 3 then 5
WHEN timestampdiff(month,curdate(),data.duedate) between 3 and 6 then 6
WHEN timestampdiff(month,curdate(),data.duedate) between 6 and 12 then 7
WHEN timestampdiff(year,curdate(),data.duedate) between 1 and 2 then 8
WHEN timestampdiff(year,curdate(),data.duedate) between 2 and 3 then 9
WHEN timestampdiff(year,curdate(),data.duedate) between 3 and 4 then 10
WHEN timestampdiff(year,curdate(),data.duedate) between 5 and 6 then 11
WHEN timestampdiff(year,curdate(),data.duedate) >= 7 then 12
This works correctly in that it will give me the correct amounts, but I want to force the code to give me a 0. So I currently get this:
1 300000
5 150000
8 300000
What I actually want is this:
1 300000
2 0
3 0
4 0
5 150000
6 0
7 0
8 300000
etc.
This is the entire query - I've tried using an IFNULL() but had no success:
select
sum(data.principaldue+data.interestdue) as m
from
(select
la.id
,rep.duedate
,rep.PRINCIPALDUE
,rep.INTERESTDUE
from repayment rep
join loanaccount la on la.ENCODEDKEY = rep.PARENTACCOUNTKEY
join loanproduct lp on lp.ENCODEDKEY = la.PRODUCTTYPEKEY
group by
CASE WHEN timestampdiff(day,curdate(),data.duedate) between 0 and 7 then 1
WHEN timestampdiff(day,curdate(),data.duedate) between 8 and 14 then 2
WHEN timestampdiff(day,curdate(),data.duedate) between 15 and 30 then 3
WHEN timestampdiff(month,curdate(),data.duedate) between 1 and 2 then 4
WHEN timestampdiff(month,curdate(),data.duedate) between 2 and 3 then 5
WHEN timestampdiff(month,curdate(),data.duedate) between 3 and 6 then 6
WHEN timestampdiff(month,curdate(),data.duedate) between 6 and 12 then 7
WHEN timestampdiff(year,curdate(),data.duedate) between 1 and 2 then 8
WHEN timestampdiff(year,curdate(),data.duedate) between 2 and 3 then 9
WHEN timestampdiff(year,curdate(),data.duedate) between 3 and 4 then 10
WHEN timestampdiff(year,curdate(),data.duedate) between 5 and 6 then 11
WHEN timestampdiff(year,curdate(),data.duedate) >= 7 then 12
END
Order by
CASE WHEN timestampdiff(day,curdate(),data.duedate) between 0 and 7 then 1
WHEN timestampdiff(day,curdate(),data.duedate) between 8 and 14 then 2
WHEN timestampdiff(day,curdate(),data.duedate) between 15 and 30 then 3
WHEN timestampdiff(month,curdate(),data.duedate) between 1 and 2 then 4
WHEN timestampdiff(month,curdate(),data.duedate) between 2 and 3 then 5
WHEN timestampdiff(month,curdate(),data.duedate) between 3 and 6 then 6
WHEN timestampdiff(month,curdate(),data.duedate) between 6 and 12 then 7
WHEN timestampdiff(year,curdate(),data.duedate) between 1 and 2 then 8
WHEN timestampdiff(year,curdate(),data.duedate) between 2 and 3 then 9
WHEN timestampdiff(year,curdate(),data.duedate) between 3 and 4 then 10
WHEN timestampdiff(year,curdate(),data.duedate) between 5 and 6 then 11
WHEN timestampdiff(year,curdate(),data.duedate) >= 7 then 12
END
This is not a complete answer, but would be too big for comments;
A temporary table with numbers could be useful:
MySql temporary tables:
CREATE TEMPORARY TABLE TempTable (num int);
INSERT INTO TmpTable VALUES(1,2,3,4,5,6,7,8,9,10,11,12 ...);
Then you could right join on this table to make sure missing values are included.
Lets say you have this:
results(num, val):
1 300000
5 150000
8 300000
This should result in your desired output:
SELECT numbers.num, COALESCE(results.val, 0) as val
FROM results RIGHT JOIN TempTable numbers on results.num = numbers.num
WHERE numbers.num <= 12 --or other max number
1 300000
2 0
...
5 150000
...
Hope this helps.
Edit:
If you don't have permission to create temporary tables, look for a workaround to select consecutive integers, for example:
SELECT #row := #row + 1 as row, t.*
FROM some_table t, (SELECT #row := 0) r
Where some_table is any table with enough rows.
Probably use a top N on that.
Another dirty workaround, might be good enough if you don't need many numbers:
SELECT 1 num
UNION
SELECT 2 num
UNION
...
Edit:
Slightly tidier workaround:
SELECT * FROM (VALUES (1), (2), (3), ... ) x(i)

Write correct SQL Query for:- i used query ? Data is coming from multiple tables

I used query:-
select
employees.N, employees.ID, Date(Month) as m, count(*) as P,
(FullOrHalfLeave) as FOH
from
employees, leaveapplication, leaveapplicationdetails
where
employees.ID = leaveapplication.ID
and leaveapplication.LeaveApplicationID = leaveapplicationdetails.Leave_Application_ID
and TypeOfLeave = 1
and LeaveStatus = 4
and employees.ID = 18
group by
employees.ID,FOH
order by
employee.N, FOH
I get a result like below:-
N ID m P FOH
--------------------------------------
A 18 1 8 1
A 18 1 3 2
A 18 4 6 1
A 18 5 3 1
A 18 5 1 2
A 18 6 3 2
A 18 9 1 1
A 18 9 4 2
I want result like this:
N ID m Pad FOH
----------------------------------------
A 18 1 9.5 -
A 18 4 6 -
A 18 5 3.5 -
A 18 6 1.5 -
A 18 9 3 -
You don't need FOH returned, just do:
select employees.N, employees.ID, Date(Month) as m, SUM(1 / FullOrHalfLeave) as P
this way, if FOH = 1 then you'll be counting one day and if FOH = 2 you'll be counting half day.

update statement with aggregate function

Good day everyone..!:-)
I have this table tab where totalUsed is equal to the sum of all used values referenced to name
cid name used total
1 a 1 1
2 a 3 4
3 a 6 10
4 b 3 3
5 b 7 10
6 b 10 0
7 a 5 0
i have this code but it only copy totalUsed's adjacent used value
UPDATE tab
SET totalUsed=
(
SELECT SUM(used)
)
cid name used total
1 a 1 1
2 a 3 3
3 a 6 6
4 b 3 3
5 b 7 7
6 b 10 10
7 a 5 5
if used is set as 10 for cid 6, totalUsed should be 20
and for cid 7 it should be 15.
how to do it in mysql?
it should look like this.
cid name used total
1 a 1 1
2 a 3 4
3 a 6 10
4 b 3 3
5 b 7 10
6 b 10 20
7 a 5 15
thanks for help
:-)
In most dialects of SQL, you can do this using an update with a correlated subquery:
UPDATE tab
SET totalUsed = (SELECT SUM(used)
from tab tab2
where tab2.name = tab.name and
tab2.cid <= tab.cid
);
EDIT:
The above will not work in MySQL. You can do this instead:
UPDATE tab join
(select tab2.cid,
(SELECT SUM(used)
from tab tab3
where tab3.name = tab2.name and
tab3.cid <= tab2.cid
) as cum_used
from tab tab2
) tab2
on tab.cid = tab2.cid
SET tab.totalUsed = tab2.cum_used;