MySql - Update the status of rows using FIFO logic - php

I've the below mysql table where I want to update multiple rows of approval_status field with 1 if the quantity passed is more than the qty_req value using MySql or PHP.
Requisition table:
id
part_id
qty_req
approval_status
1
16
20
0
2
17
30
0
3
16
40
0
4
17
50
0
5
17
60
0
Example:
$update_status=Array (
[0] => Array ( [part_id] => 17 [qty] => 90 )
[1] => Array ( [part_id] => 16 [qty] => 70 )
)
From the above array, 90 is the quantity available for the part_id 17. I want to update the approval_status as 1 in the requisition table for the rows with the part_id as 17 with the below scenario:
Update the approval_status to 1 as the quantity of the first row with part_id 17 is 30 which is less than 90.
Update the approval_status to 1 as the quantity of the second row with part_id 17 is 30+50=80 which is less than 90.
Third row won't update as the total 30+50+60=140 is greater than 90.
Unfortunately, I couldn't find any tutorial to achieve this.
Any help would be appreciated. Thanks in advance.

Single data:
UPDATE test t1
NATURAL JOIN ( SELECT *, SUM(qty_req) OVER (ORDER BY reg_date) cum_sum
FROM test
WHERE part_id = #part_id ) t2
SET t1.approval_status = 1
WHERE cum_sum <= #qty;
Multiple data:
UPDATE test t1
NATURAL JOIN ( SELECT test.*,
SUM(qty_req) OVER (PARTITION BY part_id ORDER BY reg_date) cum_sum,
qty
FROM test
JOIN JSON_TABLE(#json,
'$[*]' COLUMNS ( part_id INT PATH '$.part_id',
qty INT PATH '$.qty')) jsontable USING (part_id) ) t2
SET t1.approval_status = 1
WHERE cum_sum <= qty;
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=7cf062d81a138657dee78f3026623783
Column reg_date and unique index added for to provide definite and unambiguous rows ordering.
The solution applicable to MySQL 5.6:
UPDATE test t1
NATURAL JOIN ( SELECT t1.part_id,
t1.qty_req,
t1.approval_status,
t1.reg_date,
SUM(t2.qty_req) cum_sum
FROM test t1
JOIN test t2 USING (part_id)
WHERE t1.reg_date >= t2.reg_date
GROUP BY t1.part_id,
t1.qty_req,
t1.approval_status,
t1.reg_date ) t2
JOIN ( SELECT 17 part_id, 90 qty
UNION ALL
SELECT 16, 50 ) t3 USING (part_id)
SET t1.approval_status = 1
WHERE t2.cum_sum <= t3.qty
https://dbfiddle.uk/?rdbms=mysql_5.6&fiddle=0aad358941f66d1f385799072237d513
Source data must be formed as a query text - see subquery t3.

Related

How can I join a table on itself to get the first or the last for each unique item?

I have the following MySQL query that I have been working on for some time. I am trying to get a single unique record for each list_id in the in statement. I am joining the table on itself and checking the lists.ordered table to determine if I need to grab the first or the last order item.
SELECT lists.ordered,
cnt1.*
FROM cnt_lists AS cnt1
LEFT JOIN lists
ON cnt1.list_id = lists.id
LEFT JOIN cnt_lists AS cnt2
ON ( cnt1.list_id = cnt2.list_id
AND ( ( lists.ordered = 1
AND cnt1.id < cnt2.id )
OR ( lists.ordered = 0
AND cnt1.id > cnt2.id ) ) )
WHERE cnt2.id IS NULL
AND cnt1.list_id IN ( '3176', '3295', '3296' )
AND cnt1.listable_type = 'App\\Models\\Movie';
It is working great to grab the last item, but when I try to grab the first order item it actually grabs the second.
Anyone have any suggestions?
Sample Data:
id list_id listable_id listable_type order created_at updated_at
2226 3296 7 App\Models\Movie 1 2016-12-02 12:56:29 2016-12-02 15:07:42
2224 3296 1 App\Models\Movie 2 2016-11-29 00:06:57 2016-12-02 15:07:42
2227 3296 9 App\Models\Movie 3 2016-12-02 12:56:35 2016-12-02 12:56:35
2228 3296 54 App\Models\Movie 4 2016-12-02 12:56:39 2016-12-02 12:56:39
2229 3296 40 App\Models\Movie 5 2016-12-02 12:56:43 2016-12-02 12:56:43
2230 3296 65 App\Models\Movie 6 2016-12-02 12:56:47 2016-12-02 12:56:47
In the example above if lists.ordered = 0 it would grab row 2230 because it is the highest order. If lists.ordered = 1 it would grab row 2226 because it is the lowest order. The highest works fine, but the lowest is grabbing 2224 which is the second lowest.
Don't try to do it in a single SQL. Instead, write application code or a Stored Procedure to pick between 'first' and 'last'.
Even simpler than that is probably this:
SELECT ... FROM ...
ORDER BY ordered ASC -- ASC gives #1; DESC gives #6 in the example
LIMIT 1;
If the output is more than one row, you may need Groupwise max problem.
OK, there is a messy way to do it in one statement:
( SELECT 0 AS which, ... ORDER BY ordered ASC LIMIT 1 )
UNION ALL
( SELECT 1 AS which, ... ORDER BY ordered DESC LIMIT 1 )
HAVING which = (however you pick first vs last);
You would like to find 1st / last row ordered by the order column, but you did the ordering on the id column? In that case, try this:
SELECT lists.ordered,
cnt1.*
FROM cnt_lists AS cnt1
LEFT JOIN lists
ON cnt1.list_id = lists.id
LEFT JOIN cnt_lists AS cnt2
ON ( cnt1.list_id = cnt2.list_id
AND ( ( lists.ordered = 1
AND cnt1.order < cnt2.order )
OR ( lists.ordered = 0
AND cnt1.order > cnt2.order ) ) )
WHERE cnt2.id IS NULL
AND cnt1.list_id IN ( '3176', '3295', '3296' )
AND cnt1.listable_type = 'App\Models\Movie';

MySql - count from beginning until each day

Let's say I have the following table (keep in mind that this table will have 10000+ rows):
id total date
1 5 2015-05-16
2 8 2015-05-17
3 4 2015-05-18
4 9 2015-05-19
5 3 2015-05-20
I want the query to give the following result:
1
date => 2015-05-16
total => 5
2
date => 2015-05-17
total => 13
3
date => 2015-05-18
total => 17
4
date => 2015-05-19
total => 26
5
date => 2015-05-20
total -> 29
I can't think of any query that would do this right now, that's why I am not providing any code that I have tried.
Any thoughts? I am not sure if this is possible only with mysql, maybe I have to use and php.
This could be done using user defined variable in mysql and then get the running total as
select
id,
total,
date
date from
(
select
id,
#tot:= #tot+total as total,
date from my_table,(select #tot:=0)x
order by date
)x
You can do this -
SELECT
a.id,
a.date,
(SELECT SUM(b.total) FROM your_table WHERE b.date <= a.date) as new_total
FROM your_table a, your_table b
ORDER BY a.date ASC
This should do it:
select id, (select sum(total) from table a where a.date <= b.date) from table b

PHP MYSQL Duplicate rows filter

I want find duplicate records in table
table_name: billing
there are 7 cols, but I am intrested in only 3 cols values.
I want find rows, where 3 cols (suite, comment, amount) values are similar
For example:
suite comment amount other col
11 44 0.00 88
11 44 0.00 33
17 48 1.00 11
17 48 1.00 35
17 48 1.00 21
Not particularly efficient, but if you don't need to do this quickly.
Select b.* from
(SELECT
Suite, comment, amount
FROM
YourTable
GROUP BY
Suite, comment, amount
Having count(1) > 1) a
Inner join
YourTable b
On a.suite = b.suite and a.comment = b.comment and a.amount = b.amount

MySQL secondary query is too performance intensive

I have a query where I want to pull in ID's from another table based on the ID of the item selected in another table. I'm currently doing this with an additional query based on the results that I get from the main query. It's resulting in many many additional queries. Is there a way to condense this into 1 query?
SELECT music.id,
SUM(linked_tags.weight) AS total_weight
FROM (music)
INNER JOIN linked_tags ON linked_tags.track_id = music.id
AND linked_tags.tag_id IN (7,56,59)
GROUP BY music.id
ORDER BY total_weight DESC
Then the additional query comes from running the results from the main query through a foreach loop, where 2713 is the ID of an item in the music table.
SELECT tag_id,
weight
FROM (linked_tags)
JOIN tags_en ON tags_en.id = linked_tags.tag_id
WHERE track_id = '2713'
This results in this object, where all_tags is the data that comes from the 2nd query:
[id] => 1500
[name] => Some Track Name
[total_weight] => 10
[all_tags] => Array
(
[0] => 20
[1] => 28
[2] => 4
[3] => 13
[4] => 16
[5] => 7
[6] => 42
[7] => 56
[8] => 61
)
Is there a way to pull this all into 1 query?
You can combine them directly using join:
select tag_id, weight
from (SELECT music.id,
SUM(linked_tags.weight) AS total_weight
FROM music join
linked_tags
ON linked_tags.track_id = music.id AND linked_tags.tag_id IN (7,56,59)
GROUP BY music.id
) m join
linked_tags
on m.id = linked_tags.track_id join
tags_en
ON tags_en.id = linked_tags.tag_id;
EDIT:
If I understand the query correctly, you are trying to get all tags on "tracks" (or "music") that have one or more tags in the set of (7,56,59). And, you want to get the sum of the weights of those three tags.
You can do this in one pass, if you don't mind have the tags in a comma-delimited list:
SELECT m.id,
SUM(case when lt.tag_id IN (7,56,59) then lt.weight end) AS total_weight,
sum(lt.tag_id IN (7, 56, 59)) as NumSpecialTags,
group_concat(lt.tag_id) as AllTags
FROM music m join
linked_tags lt
ON lt.track_id = m.id
GROUP BY m.id
having NumSpecialTags > 0
order by total_weight desc;
You then have to parse the AllTags list at the application layer.

CodeIgniter SQL query - how to sum values for each month?

I have the following table:
//table_1
record_id user_id plant_id date cost
1 1 1 2011-03-01 10
2 1 1 2011-03-02 10
3 1 1 2011-04-10 5
4 1 2 2011-04-15 5
I would like to build a query (if possible using CI Active Records, but MySQL is fine) in which I generate the following result:
[1] => [1] => [March 2011] [20]
=> [April 2011] [5]
[2] => [March 2011] [0]
=> [April 2011] [5]
I have tried using $this->db->group_by but I think I'm not using it correctly.
If anyone could give me a pointer or roadmap to get this done it would be much appreciated -- thanks!
Sample table
drop table if exists t;
create table t( record_id int, user_id int, plant_id int, date datetime, cost float);
insert t select
1 ,1, 1 ,'2011-03-01', 10 union all select
2 ,1, 1 ,'2011-03-02', 10 union all select
3 ,1, 1 ,'2011-04-10', 5 union all select
4 ,1, 2 ,'2011-04-15', 5;
Because you want to see the row with 0, you need to do a cross join between the year-month and all user-plants.
select up.user_id, up.plant_id, ym2, ifnull(sum(t.cost),0) totalcost
from (select distinct date_format(date, '%Y-%m') ym, date_format(date, '%M %Y') ym2 from t) dates
cross join (select distinct t.user_id, t.plant_id from t) up
left join t on date_format(t.date, '%Y-%m') = dates.ym
and up.user_id=t.user_id
and up.plant_id=t.plant_id
group by up.user_id, up.plant_id, ym2, ym
order by up.user_id, up.plant_id, date(concat(ym,'-1'));
The fact that Month Year does not sort correctly also requires the complex treatment of the dates for ordering purposes at the end.
This is a pretty intense way to do it and it would be far preferable to store the month-year as a column itself if you need to make these queries frequently, but this is basically how it works:
SELECT CONCAT(MONTHNAME(date), ' ', YEAR(date)) AS monthyear, COUNT(*) AS count GROUP BY YEAR(date), MONTH(date), plant_id;
That should get you the resultset you're looking for.

Categories