MySQL Change SUM value - mysql

I'm trying to create a query whereby I SUM a column but, if a column contains a certain value, the SUM value has to be reset at that point to take on this value.
ie:
SUM(i.units * op.add_or_subtract) // This would translate to: '50 * -1' or '50 * 1'
The idea is that if op.op_code = 9, the SUM value should be reset to the current value of i.units as a manual adjustment has taken place.
op_code | units | add_or_subtract | SUM value |
--------|-------|-----------------|------------
1 | 50 | 1 | 50
1 | 50 | 1 | 100
4 | 30 | -1 | 70
9 | 225 | 0 | 225
etc etc.
Can anyone help with how I could (if I can) achieve this in MySQL?

try
select *, case when op_code = 9
then #s := units
else (#s := #s + (units * add_or_subtract))
end as sum
from your_table, (select #s := 0) st

try this:
SELECT SUM(IF(op_code = 9, (#var_sum := units),
((#var_sum := #var_sum + units) * add_or_subtract)
) as sum_value
FROM table_name, (SELECT #var_sum := 0) a;

Related

how to return last row in specific sum?

imagine we have 1 row which is students that contain, id, name, marks and rank. write query that return the last name of student where marks is equal to 100 ordered by grade.
example
- id | name | marks | grade |
- 01 | Jeff | 40 | 1 |
- 02 | Annie| 40 | 3 |
- 03 | Ramy | 20 | 5 |
- 04 | Jenny| 20 | 2 |
so the result should return
Annie
because Annie is the last row of the sum of marks where marks is equal to 100. Jeff is the first cause based on grade he's equal to 1 so he should be entered first, second is Jenny and third is Annie. Jeff(40)+Jenny(20)+Annie(40) = 100
You can make a running sum MySQL's user variable.
This query should work from MySQL 5.1 and up.
Query
SELECT
Table1_alias.name
FROM (
SELECT
Table1.name
, (#running_marks_sum := #running_marks_sum + Table1.marks) AS running_marks_sum
FROM
Table1
CROSS JOIN (SELECT #running_marks_sum := 0) AS init_user_param
ORDER BY
Table1.grade ASC
) AS Table1_alias
WHERE
Table1_alias.running_marks_sum = 100
Result
| name |
| ----- |
| Annie |
View on DB Fiddle
MySQL 8.0+ only
Query
SELECT
Table1_alias.name
FROM (
SELECT
Table1.name
, SUM(Table1.marks) OVER(ORDER BY Table1.grade) AS running_marks_sum
FROM
Table1
) AS Table1_alias
WHERE
Table1_alias.running_marks_sum = 100;
Result
| name |
| ----- |
| Annie |
View on DB Fiddle
Keep the cumulative sum of marks to a variable. And use this as a sub-query and select the row having the total is 100. But if no row having the cumulative total as 100, then wont't get any result.
Query
set #total := 0;
select `id`, `name`, `marks`, `grade` from(
select `id`, `name`, `marks`, `grade`, (#total := #total + `marks`) as `total`
from `your_table_name`
order by `grade`
) as `t`
where `t`.`total` = 100;
As mentioned the database structure above, Below is one of the way to get the output
select name from (select * from (SELECT id,name,grade,marks, #total := #total + marks AS total FROM (stud, (select #total := 0) t) order by grade ) t WHERE total <=100 ) final_view order by grade desc limit 1

Update row order by date

I've asked this question in the morning, but did not get any answer for this. So I delete previous one, and ask this again cause I am getting stuck for a long while. Hope you guys can help me.
I have a table named overtime like following:
| total | remain | t_date |
|-------|--------|---------------------|
| 3 | 0 | 2016-01-01 12:20:00 |
| 4 | 0 | 2016-02-01 13:10:00 |
| 2 | 0 | 2016-03-01 14:40:00 |
| 3 | 0 | 2016-04-01 10:20:00 |
| 5 | 2 | 2016-05-01 17:20:00 |
I want to update column remain order by t_date desc, also I have an input parameter, assume it is $h = 9, the expected result is:
| total | remain | t_date |
|-------|--------|---------------------|
| 5 | 5 | 2016-05-01 17:20:00 | -- remain will be updated to 5 cause total = 5, then $h(6) = $h(9) - (total(5) - remain(2))
| 3 | 3 | 2016-04-01 10:20:00 | -- remain will be updated to 3 cause total = 3, then $h(3) = $h(6) - (total(3) - remain(0))
| 2 | 2 | 2016-03-01 14:40:00 | -- remain will be updated to 2 cause total = 2, then $h(1) = $h(3) - (total(2) - remain(0))
| 4 | 1 | 2016-02-01 13:10:00 | -- remain will be updated to 1 cause $h only has 1, then $h will be 0
| 3 | 0 | 2016-01-01 12:20:00 | -- cause $h = 0, this row has no need to be updated
Edited:
The sample data is like above, what I want to do is update column remain, and remain'value bases on total and an input parameter(just assume it is 9):
update order is order by t_date desc. For example, I must update 2016-05-01 17:20:00 row first, then 2016-04-01 10:20:00, then 2016-03-01 14:40:00 and so on...
Parameter is 9, it will be allocated to every row, and remain should be updated to total's value. For example, the first row 2016-05-01 17:20:00, total = 5 and remain = 2, so remain will be updated to 5, and parameter will minus total - remain, it will be 6 and do the next row's allocation, till row 2016-02-01 13:10:00, the paramter is 1, so this row's remain only needs to updated to 1. And another row will have no need to updated.
Here is SQLFiddle demo.
If there is any unclear point for question, please leave a comment, I can explain that.
Any help is appreciated. Thanks in advance.
SELECT QUERY:
SELECT
*,
IF(
(IF(#h <= 0,0,IF(#h >= total,total, #h)) + remain) <= total,
(IF(#h <= 0,0,IF(#h >= total,total, #h)) + remain),
(IF(#h <= 0,0,IF(#h >= total,total, #h)))
) allocated,
#h := #h - (total - remain)
FROM overtime
CROSS JOIN (SELECT #h := 9) var
ORDER BY t_date DESC
Demo of SELECT
UPDATE QUERY:
UPDATE
overtime OT
INNER JOIN
(
SELECT
*,
IF(
(IF(#h <= 0,0,IF(#h >= total,total, #h)) + remain) <= total,
(IF(#h <= 0,0,IF(#h >= total,total, #h)) + remain),
(IF(#h <= 0,0,IF(#h >= total,total, #h)))
) allocated,
#h := #h - (total - remain)
FROM overtime
CROSS JOIN (SELECT #h := 9) var
ORDER BY t_date DESC
) AS t
ON OT.t_date = t.t_date
SET OT.remain = t.allocated;
WORKING DEMO
Demo shows the table data sorted by descending order of date after being updated by the above update query.
More:
See Demo for h=2
You must use a sub query to do this, like following:
update overtime t1
join (
select overtime.*,
total - remain, IF(#h > (total - remain), total, #h + remain) as h,
#h := IF(#h > (total - remain), #h - (total - remain), 0)
from overtime
cross join (
select #h := 9
) t
order by t_date desc
) t2 on t1.t_date = t2.t_date
set t1.remain = t2.h;
Demo Here
You could use a sub-query that makes the calculations using a variable #h:
update overtime
join (select t_date,
least(#h,total)
+ if(least(#h,total) + remain <= total, remain, 0) new_remain,
#h := greatest(0, #h - (total - remain)) h
from overtime,
(select #h := 9) init
order by t_date desc
) as calc
on overtime.t_date = calc.t_date
set overtime.remain := calc.new_remain;
See this SQL fiddle

MySQL top 2 records per group

Basically I need to get only the last 2 records for each user, considering the last created_datetime:
id | user_id | created_datetime
1 | 34 | '2015-09-10'
2 | 34 | '2015-10-11'
3 | 34 | '2015-05-23'
4 | 34 | '2015-09-13'
5 | 159 | '2015-10-01'
6 | 159 | '2015-10-02'
7 | 159 | '2015-10-03'
8 | 159 | '2015-10-06'
Returns (expected output):
2 | 34 | '2015-10-11'
1 | 34 | '2015-09-10'
7 | 159 | '2015-10-03'
8 | 159 | '2015-10-06'
I was trying with this idea:
select user_id, created_datetime,
$num := if($user_id = user_id, $num + 1, 1) as row_number,
$id := user_id as dummy
from logs group by user_id
having row_number <= 2
The idea is keep only these top 2 rows and remove all the others.
Any ideas?
Your idea is close. I think this will work better:
select u.*
from (select user_id, created_datetime,
$num := if(#user_id = user_id, #num + 1,
if(#user_id := id, 1, 1)
) as row_number
from logs cross join
(select #user_id := 0, #num := 0) params
order by user_id
) u
where row_number <= 2 ;
Here are the changes:
The variables are set in only one expression. MySQL does not guarantee the order of evaluation of expressions, so this is important.
The work is done in a subquery, which is then processed in the outer query.
The subquery uses order by, not group by.
The outer query uses where instead of having (actually, in MySQL having would work, but where is more appropriate).

Subtract from one number dynamically

I have a base number 30000 and about 5 rows, where every row have different number and i want to subtract them from the base number in query.
I want to achieve the result column:
ID| num | result
1 | 1000 | 29000
2 | 1200 | 27800
3 | 2100 | 25700
4 | 4300 | 21400
5 | 1100 | 20300
SET #num := 0;
SELECT
id,
num,
IF(#num = 0,#num := (30000 - num) , #num := (#num - num)) AS `Result`
FROM
`mytable`
Demo
Try this:
SELECT id, num, (30000 - #sum:=#sum+num) AS Result
FROM mytable, (SELECT #sum:= 0) AS A
Check this SQL FIDDLE DEMO

Select a row and rows around it

Ok, let's say I have a table with photos.
What I want to do is on a page display the photo based on the id in the URI. Bellow the photo I want to have 10 thumbnails of nearby photos and the current photo should be in the middle of the thumbnails.
Here's my query so far (this is just an example, I used 7 as id):
SELECT
A.*
FROM
(SELECT
*
FROM media
WHERE id < 7
ORDER BY id DESC
LIMIT 0, 4
UNION
SELECT
*
FROM media
WHERE id >= 7
ORDER BY id ASC
LIMIT 0, 6
) as A
ORDER BY A.id
But I get this error:
#1221 - Incorrect usage of UNION and ORDER BY
Only one ORDER BY clause can be defined for a UNION'd query. It doesn't matter if you use UNION or UNION ALL. MySQL does support the LIMIT clause on portions of a UNION'd query, but it's relatively useless without the ability to define the order.
MySQL also lacks ranking functions, which you need to deal with gaps in the data (missing due to entries being deleted). The only alternative is to use an incrementing variable in the SELECT statement:
SELECT t.id,
#rownum := #rownum+1 as rownum
FROM MEDIA t, (SELECT #rownum := 0) r
Now we can get a consecutively numbered list of the rows, so we can use:
WHERE rownum BETWEEN #midpoint - ROUND(#midpoint/2)
AND #midpoint - ROUND(#midpoint/2) +#upperlimit
Using 7 as the value for #midpoint, #midpoint - ROUND(#midpoint/2) returns a value of 4. To get 10 rows in total, set the #upperlimit value to 10. Here's the full query:
SELECT x.*
FROM (SELECT t.id,
#rownum := #rownum+1 as rownum
FROM MEDIA t,
(SELECT #rownum := 0) r) x
WHERE x.rownum BETWEEN #midpoint - ROUND(#midpoint/2) AND #midpoint - ROUND(#midpoint/2) + #upperlimit
But if you still want to use LIMIT, you can use:
SELECT x.*
FROM (SELECT t.id,
#rownum := #rownum+1 as rownum
FROM MEDIA t,
(SELECT #rownum := 0) r) x
WHERE x.rownum >= #midpoint - ROUND(#midpoint/2)
ORDER BY x.id ASC
LIMIT 10
I resolve this by using the below code:
SELECT A.* FROM (
(
SELECT * FROM gossips
WHERE id < 7
ORDER BY id DESC
LIMIT 2
)
UNION
(
SELECT * FROM gossips
WHERE id > 7
ORDER BY id ASC
LIMIT 2
)
) as A
ORDER BY A.id
I don't believe that you can have an "order by" in different sections of a UNION. Could you just do something like this:
SELECT * FROM media where id >= 7 - 4 and id <= 7 + 4 ORDER BY id
I'm agree with the answer suggested by malonso(+1), but if you try it with id= 1, you will get only 5 thumbnails. I don't know if you want this behaviour. If you want always 10 thumbs, you can try:
select top 10 * from media where id > 7 - 4
The problem is that select top is database dependent (in this case is a sql server clause). Other database has similar clauses:
Oracle:
SELECT * media
FROM media
WHERE ROWNUM < 10
AND id > 7 - 4
MySQL:
SELECT *
FROM media
WHERE id > 7 - 4
LIMIT 10
So maybe you can use the last one.
If we do it, we will have the same problem if you want the last 10 thumbs. By example, If we have 90 thumbs and we give an id=88 ... You can solve it adding an OR condition. In MySQL will be something like:
SELECT *
FROM media
WHERE id > 7 - 4
OR (Id+5) > (select COUNT(1) from media)
LIMIT 10
If you're happy to use temp tables, your original query could be broken down to use them.
SELECT
*
FROM media
WHERE id < 7
ORDER BY id DESC
LIMIT 0, 4
INTO TEMP t1;
INSERT INTO t1
SELECT
*
FROM media
WHERE id >= 7
ORDER BY id ASC
LIMIT 0, 6;
select * from t1 order by id;
drop table t1;
Try union all instead. Union requires the server to ensure that the results are unique and this conflicts with your ordering.
I had to solve a similar problem, but needed to account situations where we always got the same number of rows, even if the desired row was near the top or bottom of the result set (i.e. not exactly in the middle).
This solution is a tweak from OMG Ponies' response, but where the rownum maxes out at the desired row:
set #id = 7;
SELECT natSorted.id
FROM (
SELECT gravitySorted.* FROM (
SELECT Media.id, IF(id <= #id, #gravity := #gravity + 1, #gravity := #gravity - 1) AS gravity
FROM Media, (SELECT #gravity := 0) g
) AS gravitySorted ORDER BY gravity DESC LIMIT 10
) natSorted ORDER BY id;
Here's a break down of what's happening:
NOTE: In the example below I made a table with 20 rows and removed ids 6 and 9 to ensure a gap in ids do not affect the results
First we assign every row a gravity value that's centered around the particular row you're looking for (in this case where id is 7). The closer the row is to the desired row, the higher the value will be:
SET #id = 7;
SELECT Media.id, IF(id <= #id, #gravity := #gravity + 1, #gravity := #gravity - 1) AS gravity
FROM Media, (SELECT #gravity := 0) g
returns:
+----+---------+
| id | gravity |
+----+---------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
| 5 | 5 |
| 7 | 6 |
| 8 | 5 |
| 10 | 4 |
| 11 | 3 |
| 12 | 2 |
| 13 | 1 |
| 14 | 0 |
| 15 | -1 |
| 16 | -2 |
| 17 | -3 |
| 18 | -4 |
| 19 | -5 |
| 20 | -6 |
| 21 | -7 |
+----+---------+
Next we order all the results by the gravity value and limit on the desired number of rows:
SET #id = 7;
SELECT gravitySorted.* FROM (
SELECT Media.id, IF(id <= #id, #gravity := #gravity + 1, #gravity := #gravity - 1) AS gravity
FROM Media, (SELECT #gravity := 0) g
) AS gravitySorted ORDER BY gravity DESC LIMIT 10
returns:
+----+---------+
| id | gravity |
+----+---------+
| 7 | 6 |
| 5 | 5 |
| 8 | 5 |
| 4 | 4 |
| 10 | 4 |
| 3 | 3 |
| 11 | 3 |
| 2 | 2 |
| 12 | 2 |
| 1 | 1 |
+----+---------+
At this point we have all the desired ids, we just need to sort them back to their original order:
set #id = 7;
SELECT natSorted.id
FROM (
SELECT gravitySorted.* FROM (
SELECT Media.id, IF(id <= #id, #gravity := #gravity + 1, #gravity := #gravity - 1) AS gravity
FROM Media, (SELECT #gravity := 0) g
) AS gravitySorted ORDER BY gravity DESC LIMIT 10
) natSorted ORDER BY id;
returns:
+----+
| id |
+----+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 7 |
| 8 |
| 10 |
| 11 |
| 12 |
+----+