MySql - count from beginning until each day - php

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

Related

MySql - Update the status of rows using FIFO logic

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.

Mysql, get data between two dates, if there is not data at the starting date, I need to get data from last entered date?

I have a table with following entries
Created at
tax
2020-01-05
300
2020-06-10
350
2020-09-15
400
2020-09-28
320
If I try to get data between 2020-02-01 to 2020-10-30 I need to get output as following
Created at
tax
2020-02-01
300
2020-03-01
300
2020-04-01
300
2020-05-01
300
2020-06-01
300
2020-06-10
350
2020-07-01
350
2020-08-01
350
2020-09-01
350
2020-09-15
400
2020-09-28
320
2020-10-01
320
In MySQl 8 you can generate the missing records recursively. Otherwise, a temporary table is required.
WITH RECURSIVE dates (created_at) AS (
SELECT '2020-02-01' as created_at
UNION ALL
SELECT DATE_ADD(created_at, INTERVAL 1 MONTH)
FROM dates WHERE created_at <= '2020-10-30'
), result_table AS (
SELECT created_at, tax FROM taxes
UNION
SELECT created_at, (
SELECT tax FROM taxes t
WHERE t.created_at <= d.created_at
ORDER BY t.created_at DESC LIMIT 1
) AS tax
FROM dates d
)
SELECT * FROM result_table
WHERE created_at BETWEEN '2020-02-01' AND '2020-10-30'
ORDER BY 1
db<>fiddle

mysql - select count multiple tables, group by week, custom date range

For example, I have 2 tables and a date range (1 dec 2015 - 10 jan 2016).
First table: USERS
id (int) date (datetime)
1 3-dec-2015
2 4-dec-2015
3 19-dec-2015
4 20-dec-2015
5 21-dec-2015
6 29-dec-2015
7 30-dec-2015
Second table: BIRTHDAYS
id (int) date (datetime)
1 6-dec-2015
2 8-dec-2015
3 9-dec-2015
4 17-dec-2015
5 28-dec-2015
The result after the query should be the following:
[0] 1st week => 2 users, 1 birthday
[1] 2nd week => 0 users, 2 birthday
[2] 3ed week => 1 users, 1 birthday
[3] 4th week => 1 users, 0 birthday
[4] 5th week => 2 users, 1 birthday
[5] 6th week => 0 users, 0 birthday
Any ideas how to achive this result or something close? I can use and PHP if needed.
I would start off with something like this:
select ((week(dateb) - week('2015-12-01')) + 1) as week_number, count(a.dateb) as userdates
from users as a
where dateb between '2015-12-01' and '2016-01-01'
group by week(dateb)
order by week(dateb);
and
select ((week(dateb2) - week('2015-12-01')) + 1) as week_number, count(dateb2) as birthdays
from birthdays
where dateb2 between '2015-12-01' and '2016-01-01'
group by week(dateb2)
order by week(dateb2);
Demo, http://sqlfiddle.com/#!9/c83cb/21
from there you can fiddle with the outputting with PHP.
Also note with this approach only rows with populated data are returned. So you should check on the iteration that each row is incremented by 1.
e.g. so for users when you got from week 1 to week 3 you should output week 2 = 0; or however you want to display it.

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.

printing restaurant opening hours from a database table in human readable format using php

i have a table that lists the opening hours of restaurants. the columns are id, eateries_id, day_of_week, start_time, and end_time. each eatery is represented in the table multiple times because there is a separate entry for each day. see this previous question for more details:
determine if a restaurant is open now (like yelp does) using database, php, js
i'm wondering now how to take the data from this table and print it out in a human readable format. for example, instead of saying "M 1-3, T 1-3, W 1-3, Th 1-3, F 1-8" i would like to say "M-Th 1-3, F 1-8". similarly, i want "M 1-3, 5-8" instead of "M 1-3, M 5-8". how might i do this without a brute force method of numerous if statements?
thanks.
Thought I would have a bash at this.
Test Table
CREATE TABLE `opening_hours` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`eateries_id` int(11) DEFAULT NULL,
`day_of_week` int(11) DEFAULT NULL,
`start_time` time DEFAULT NULL,
`end_time` time DEFAULT NULL,
PRIMARY KEY (`id`)
)
Test Data
INSERT INTO `test`.`opening_hours`
(
`eateries_id`,
`day_of_week`,
`start_time`,
`end_time`)
SELECT 2 AS eateries_id, 1 AS day_of_week, '13:00' AS start_time, '15:00' as end_time union all
SELECT 2 AS eateries_id, 1 AS day_of_week, '17:00' AS start_time, '20:00' as end_time union all
SELECT 2 AS eateries_id, 2 AS day_of_week, '13:00' AS start_time, '15:00' as end_time union all
SELECT 2 AS eateries_id, 2 AS day_of_week, '17:00' AS start_time, '20:00' as end_time union all
SELECT 2 AS eateries_id, 3 AS day_of_week, '13:00' AS start_time, '15:00' as end_time union all
SELECT 2 AS eateries_id, 4 AS day_of_week, '13:00' AS start_time, '20:00' as end_time union all
SELECT 2 AS eateries_id, 5 AS day_of_week, '13:00' AS start_time, '15:00' as end_time union all
SELECT 2 AS eateries_id, 6 AS day_of_week, '13:00' AS start_time, '20:00' as end_time union all
SELECT 2 AS eateries_id, 7 AS day_of_week, '13:00' AS start_time, '21:00' as end_time
union all
SELECT 3 AS eateries_id, 1 AS day_of_week, '13:00' AS start_time, '15:00' as end_time union all
SELECT 3 AS eateries_id, 2 AS day_of_week, '13:00' AS start_time, '15:00' as end_time union all
SELECT 3 AS eateries_id, 3 AS day_of_week, '13:00' AS start_time, '15:00' as end_time union all
SELECT 3 AS eateries_id, 4 AS day_of_week, '13:00' AS start_time, '20:00' as end_time union all
SELECT 3 AS eateries_id, 5 AS day_of_week, '13:00' AS start_time, '15:00' as end_time union all
SELECT 3 AS eateries_id, 6 AS day_of_week, '13:00' AS start_time, '20:00' as end_time union all
SELECT 3 AS eateries_id, 7 AS day_of_week, '13:00' AS start_time, '21:00' as end_time
View definition to consolidate opening hours by day
CREATE VIEW `test`.`groupedhours`
AS
select `test`.`opening_hours`.`eateries_id` AS `eateries_id`,
`test`.`opening_hours`.`day_of_week` AS `day_of_week`,
group_concat(concat(date_format(`test`.`opening_hours`.`start_time`,'%l'),' - ',date_format(`test`.`opening_hours`.`end_time`,'%l %p')) order by `test`.`opening_hours`.`start_time` ASC separator ', ') AS `OpeningHours`
from `test`.`opening_hours`
group by `test`.`opening_hours`.`eateries_id`,`test`.`opening_hours`.`day_of_week`
Query to find the 'islands' of contiguous days with the same opening hours (based on one by Itzik Ben Gan)
SET #rownum = NULL;
SET #rownum2 = NULL;
SELECT S.eateries_id,
concat(CASE WHEN
S.day_of_week <> E.day_of_week
THEN
CONCAT(CASE S.day_of_week
WHEN 1 THEN 'Su'
WHEN 2 THEN 'Mo'
WHEN 3 THEN 'Tu'
WHEN 4 THEN 'We'
WHEN 5 THEN 'Th'
WHEN 6 THEN 'Fr'
WHEN 7 THEN 'Sa'
End, ' - ')
ELSE ''
END,
CASE E.day_of_week
WHEN 1 THEN 'Su'
WHEN 2 THEN 'Mo'
WHEN 3 THEN 'Tu'
WHEN 4 THEN 'We'
WHEN 5 THEN 'Th'
WHEN 6 THEN 'Fr'
WHEN 7 THEN 'Sa'
End, ' ', S.OpeningHours) AS `Range`
FROM (
SELECT
A.day_of_week,
#rownum := IFNULL(#rownum, 0) + 1 AS rownum,
A.eateries_id,
A.OpeningHours
FROM `test`.`groupedhours` as A
WHERE NOT EXISTS(SELECT * FROM `test`.`groupedhours` B
WHERE A.eateries_id = B.eateries_id
AND A.OpeningHours = B.OpeningHours
AND B.day_of_week = A.day_of_week -1)
ORDER BY eateries_id,day_of_week) AS S
JOIN (
SELECT
A.day_of_week,
#rownum2 := IFNULL(#rownum2, 0) + 1 AS rownum,
A.eateries_id,
A.OpeningHours
FROM `test`.`groupedhours` as A
WHERE NOT EXISTS(SELECT * FROM `test`.`groupedhours` B
WHERE A.eateries_id = B.eateries_id
AND A.OpeningHours = B.OpeningHours
AND B.day_of_week = A.day_of_week + 1)
ORDER BY eateries_id,day_of_week) AS E
ON S.eateries_id = E.eateries_id AND
S.OpeningHours = S.OpeningHours AND
S.rownum = E.rownum
Results
eateries_id Range
2 Su - Mo 1 - 3 PM, 5 - 8 PM
2 Tu 1 - 3 PM
2 We 1 - 8 PM
2 Th 1 - 3 PM
2 Fr 1 - 8 PM
2 Sa 1 - 9 PM
3 Su - Tu 1 - 3 PM
3 We 1 - 8 PM
3 Th 1 - 3 PM
3 Fr 1 - 8 PM
3 Sa 1 - 9 PM
You want to union a bunch of intervals for each day. Stick to 24h format (actually convert it to seconds first I guess) until you have to convert it to a human-friendlier format.
http://pyinterval.googlecode.com/svn/trunk/html/index.html
The trouble is that when you allow seconds ... a restaurant which closes 1 second earlier will be missed :( Perhaps you need to allow 15 or 5 -minute increments. Round the data in DB if you have to. So, the approach is: using an interval data structure, union all intervals for a given day together. Now reverse the dictionary. Instead of mapping days to intervals, map intervals to days. Now find a way to represent those groups of days intelligently. For instance, set(1,2,3) can be displayed as "M-W", so I would suggest: for every power set of the set {1,2,3,4,5,6,7} (or {1,2,3,4,5}) find the best human representation (by hand). Now hard-code this logic -save it into a dictionary which maps a sorted string (this is important) such as "1235" to a human representation such as "M-W,F". Displaying 1-3, 5-8 is easy, once you work with an interval object as is described in the link above. Good luck! Let me know what problems you run into.
EDIT:
This is not the best example that they have (does not show union of overlapping intervals), but you care about the "|" operator
unioned:
>>> interval[1, 4] | interval[2, 5]
interval([1.0, 5.0])
>>> interval[1, 2] | interval[4, 5]
interval([1.0, 2.0], [4.0, 5.0])
You could just implement this class yourself, but it might be prone to bugs.

Categories