Subtract a number from the rows until the number ends to zero - mysql

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.

Related

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

Select quantity of record instances separated by weeks

I have a table like the below:
CompanyID | Logged | UniqueID
A | 2014-06-24 | 8
B | 2014-06-24 | 7
A | 2014-06-16 | 6
B | 2014-06-16 | 5
A | 2014-06-08 | 4
B | 2014-06-08 | 3
A | 2014-06-01 | 2
B | 2014-06-01 | 1
I'm stuck trying to create an SQL statement that will return the quantity of rows found for each unique CompanyID, separated into 4 week periods, so something like the below:
CompanyID | Period (week) | Quantity
A | 0 | 1
B | 0 | 1
A | 1 | 1
B | 1 | 1
A | 2 | 1
B | 2 | 1
A | 3 | 1
B | 3 | 1
I have done something similar before, except by the last 7 days instead of last 4 weeks, but am not sure if this can be reworked:
select CompanyID,
case DATE_FORMAT(Logged, '%Y%m%d')
when '20140618' then '0'
when '20140619' then '1'
when '20140620' then '2'
when '20140621' then '3'
when '20140622' then '4'
when '20140623' then '5'
when '20140624' then '6'
end as period ,
count(UniqueID) as quantity from TABLE
where DATE_FORMAT(Logged, '%Y%m%d')
in (20140618,20140619,20140620,20140621,20140622,20140623,20140624) group by CompanyID,
DATE_FORMAT(Logged, '%Y%m%d')
Is there a more straightforward way to obtain the output desired above?
Maybe something like this?
SQL FIDDLE to test with
Theres the original query that doesn't use any hard coding... that is generally a really bad practice. it will have the count inflated by 1 since it starts with one and you want it to start with zero so to fix this do a select of the original query where you fix the count and then also not show the user defined variable
SELECT CompanyID, Period - 1 as Period, Quantity FROM(
SELECT
CompanyID,
if(#a = Logged, #b, #b := #b + 1) as Period,
COUNT(*) as Quantity,
#a := Logged
FROM test
JOIN (SELECT #a := '', #b := 0) as temp
GROUP BY UniqueID
ORDER BY Period
) as subQuery
ORIGINAL QUERY
SELECT
CompanyID,
if(#a = Logged, #b, #b := #b + 1) as Period,
COUNT(*) as Quantity,
#a := Logged
FROM test
JOIN (SELECT #a := '', #b := 0) as temp
GROUP BY UniqueID
ORDER BY Period

select query that sums rows and returns them as columns

Weird question... I know. So let me explain.
Here is a my text table
quota | metric_id
100 | 1
20 | 2
100 | 15
50 | 1
30 | 2
20 | 15
50 | 1
200 | 2
10 | 15
I want a SELECT statement that would return the following
id_1 | id_2 | id_15
200 | 250 | 130
Here is what the data is:
Column `id_1` is sum of the 3 rows where `metric_id=1`
Column `id_2` is sum of the 3 rows where `metric_id=2`
Column `id_15` is sum of the 3 rows where `metric_id=15`
Any help?
One possible approach:
SELECT SUM(quota * (metric_id = 1)) AS id_1,
SUM(quota * (metric_id = 2)) AS id_2,
SUM(quota * (metric_id = 15)) AS id_15
FROM Table1
Demo. It's quite simple: metric_id = N is 1 for the rows having that attribute equal to N, and 0 for all the other rows. Multiplying quota by 1 is quota, by 0 - 0, and the rest is an easy task for SUM() function.
Try this
select SUM(case when metric_id = 1 then quota END) id_1
SUM(case when metric_id = 2 then quota END) id_2
SUM(case when metric_id = 15 then quota END) id_15
FROM Table1

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

How to query number of changes in a column in MySQL

I have a table that stores items with two properties. So the table has three columns:
item_id | property_1 | property_2 | insert_time
1 | 10 | 100 | 2012-08-24 00:00:01
1 | 11 | 100 | 2012-08-24 00:00:02
1 | 11 | 101 | 2012-08-24 00:00:03
2 | 20 | 200 | 2012-08-24 00:00:04
2 | 20 | 201 | 2012-08-24 00:00:05
2 | 20 | 200 | 2012-08-24 00:00:06
That is, each time either property of any item changes, a new row is inserted. There is also a column storing the insertion time. Now I want to get the number of changes in property_2. For the table above, I should get
item_id | changes_in_property_2
1 | 2
2 | 3
How can I get this?
This will tell you how many distinct values were entered. If it was changed back to a previous value, it will not be counted as a new change, though. Without a chronology to your data, hard to do much more.
select item_id, count(distinct property_2)
from Table1
group by item_id
Here is the closest that I could get to your desired result. I should note however, that you are asking for the number of changes to property_2 based on item_id. If you are analyzing strictly those two columns, then there is only 1 change for item_id 1 and 2 changes for item_id 2. You would need to expand your result to aggregate by property_1. Hopefully, this fiddle will show you why.
SELECT a.item_id,
SUM(
CASE
WHEN a.property_2 <>
(SELECT property_2 FROM tbl b
WHERE b.item_id = a.item_id AND b.insert_time > a.insert_time LIMIT 1) THEN 1
ELSE 0
END) AS changes_in_property_2
FROM tbl a
GROUP BY a.item_id
My take :
SELECT
i.item_id,
SUM(CASE WHEN i.property_1 != p.property_1 THEN 1 ELSE 0 END) + 1
AS changes_1,
SUM(CASE WHEN i.property_2 != p.property_2 THEN 1 ELSE 0 END) + 1
AS changes_2
FROM items i
LEFT JOIN items p
ON p.time =
(SELECT MAX(q.insert_time) FROM items q
WHERE q.insert_time < i.insert_time AND i.item_id = q.item_id)
GROUP BY i.item_id;
There is one entry for each item that is not selected in i, the one that has no predecessor. It counts for a change though, that's why the sums are incremented.
I would do it this way, with user-defined variables to keep track of the previous row's value.
SELECT item_id, MAX(c) AS changes_in_property_2
FROM (
SELECT IF(#i = item_id, IF(#p = property_2, #c, #c:=#c+1), #c:=1) AS c,
(#i:=item_id) AS item_id,
(#p:=property_2)
FROM `no_one_names_their_table_in_sql_questions` AS t,
(SELECT #i:=0, #p:=0) AS _init
ORDER BY insert_time
) AS sub
GROUP BY item_id;