How can subtract a number from the rows to the end of the subtraction in MySQL with query update
If have table like this
Store Table
itemId storeCode qoh
1 1 20
1 2 30
1 3 40
and i want subtract "80" form qoh to get the output
itemId storeCode qoh
1 1 0
1 2 0
1 3 10
I tried by and not work
set #sum = 80;
Update store SET qoh =
(SELECT IF((#sum := #sum - qoh) > 0,0,qoh))
ORDER BY storeCode ASC;
What is the appropriate adjustment to do?
If you are running MySQL 8.0, you can do the computation with window functions.
The following select query gives you the expected results:
select
s.*,
case when sum(qoh) over(partition by itemid order by storecode) - qoh >= 80
then qoh
else greatest(
sum(qoh) over(partition by itemid order by storecode) - 80,
0
)
end new_qoh
from store s
You can then turn this to an update:
update store s
inner join (
select
s.*,
sum(qoh) over(partition by itemid order by storecode) sum_qoh
from store s
) n
on n.itemid = s.itemid
and n.storecode = s.storecode
and n.sum_qoh - s.qoh < 80
set s.qoh = greatest(n.sum_qoh - 80, 0)
Demo on DB Fiddle:
itemId | storeCode | qoh
-----: | --------: | --:
1 | 1 | 0
1 | 2 | 0
1 | 3 | 10
1 | 4 | 50
I added an extra line at the end of your data to demonstrate that the queries leave the "following" qon untouched.
There are Merchants and they can submit Claims.
I need to find the longest time period during which a Merchant had at least 1 claim. So a time period (in fractions of a day, whatever) per merchant_id.
So, for example:
+-------------+-----------+----------------------+----------------------+
| merchant_id | claim_id | from | to |
+-------------+-----------+----------------------+----------------------+
| 1 | 11 | 2016-08-15 12:00:00 | 2016-08-17 12:00:00 |
| 1 | 22 | 2016-08-16 12:00:00 | 2016-08-18 12:00:00 |
| 1 | 33 | 2016-08-19 12:00:00 | 2016-08-20 12:00:00 |
| 2 | 66 | 2016-08-15 12:00:00 | 2016-08-17 12:00:00 |
| 2 | 67 | 2016-08-18 12:00:00 | 2016-08-19 12:00:00 |
+-------------+-----------+----------------------+----------------------+
For merchant_id = 1 it would be 3 days.
For merchant_id = 2 it would be 2 days.
How do I do that?
Doing this alone in MySQL is really complex. I've tried for a particular merchant_id. I am not still sure if this is 100% right without checking for different set of inputs.
But you can give this a try and later I can explain the logic behind.
SELECT
firstTable.merchant_id,
MAX(TIMESTAMPDIFF(DAY,firstTable.from,secondTable.to)) AS maxConsecutiveDays
FROM
(
SELECT
A.merchant_id,
A.from,
#rn1 := #rn1 + 1 AS row_number
FROM merchants A
CROSS JOIN (SELECT #rn1 := 0) var
WHERE A.merchant_id = 2
AND NOT EXISTS (
SELECT 1 FROM merchants B WHERE B.merchant_id = A.merchant_id AND A.idt <> B.idt AND A.`from` BETWEEN B.from AND B.to
)
ORDER BY A.from
) AS firstTable
INNER JOIN (
SELECT
A.merchant_id,
A.to,
#rn2 := #rn2 + 1 AS row_number
FROM merchants A
CROSS JOIN (SELECT #rn2 := 0) var
WHERE A.merchant_id = 2
AND NOT EXISTS (
SELECT 1 FROM merchants B WHERE B.merchant_id = A.merchant_id AND A.idt <> B.idt AND A.to BETWEEN B.from AND B.to
)
ORDER BY A.to
) AS secondTable
ON firstTable.row_number = secondTable.row_number;
WORKING DEMO
Algorithm:
Let's consider the following steps for a particular merchant_id
First find all the start points which are not inside in any of the
ranges. I call this independent start points. Let's say these start
points are stored in a set S.
Second now find all the end points which are not inside in any of
the ranges. These are independent end points and are stored in a set
E.
Sort the sets in ascending order of time.
Now give a rank to every element of a set starting from 1.
Join these two sets on matching rank number.
Now enumerate the two sets simultaneously and get the difference in
days. And later find the maximum of this difference.
The last step can be illustrated by the following code snippet:
int maxDiff = 0;
for(int i=0; i< E.size(); i++){
if((E.get(i) - S.get(i) > maxDiff){
maxDiff = E.get(i) - S.get(i);
}
}
And maxDiff is your output;
EDIT:
In order to get longest consecutive days for each merchant check this DEMO
I have a requirement of getting next date and previous date
the table structure is as follows
| auto_id | id | next_date | next_activity |
| 1 | 1 | 22-12-2012 | - |
| 2 | 1 | 25-12-2012 | - |
| 3 | 1 | 26-12-2012 | - |
| 4 | 1 | 28-12-2012 | - |
so i need next_day and previous_day
next_day = next_date after current day
previous_day = next_date before current_date
(SELECT * FROM `activity` WHERE id = 1 and next_date > CURDATE() order by next_date asc limit 1)
UNION
(SELECT * FROM `activity` WHERE id = 1 and next_date = CURDATE() )
UNION
(SELECT * FROM `activity` WHERE id = 1 and next_date < CURDATE() order by next_date desc limit 1)
ORDER BY next_date desc limit 2
Other way to do it self join the table...
Is there a way to optimize the table
Here is another way:
SELECT next_date, date_diff
FROM (SELECT *,
#dateDiff := datediff(next_date, curdate()) AS date_diff,
#pDateDiff :=
IF((#dateDiff < 0 AND #dateDiff > #pDateDiff),
#dateDiff,
#pDateDiff)
AS pDateDiff,
#nDateDiff :=
IF((#dateDiff > 0 AND #dateDiff < #nDateDiff),
#dateDiff,
#nDateDiff)
AS nDateDiff
FROM activity, (SELECT #pDateDiff := -9999, #nDateDiff := 9999) tmp
WHERE id = 1) aView
WHERE date_diff IN (#pDateDiff, 0, #nDateDiff)
ORDER BY next_date;
date_diff value gives perspective of prev and next dates.
pick all dates where id = 1
find the date difference between next_date and curdate() & store in an user defined variable #dateDiff.
#pDateDiff is another variable which tracks maximum among negative #dateDiff values (our previous date)
#nDateDiff is yet another variable which tracks minimum among positive #dateDiff values (our next date)
in the end, select only those dates which are in (-ve max, 0, +ve min).
PS: if you've duplicate date entries then query may return all of them.
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;
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 |
+----+