SQL - Show months without data using group by - php

I've been searching without success for a way to list the data of all months, on format YEAR-MONTH, between two dates in a report.
I have this query that work well but doesn't show the months when doesn't exist data.
SELECT DATE_FORMAT(FROM_UNIXTIME(bt.date_submitted),'%Y-%m') AS month, count(*) as the_count
FROM bug_table bt
WHERE bt.category_id=2
AND bt.date_submitted BETWEEN " . db_prepare_string( $db_datetimes['start'] ) . "
AND " . db_prepare_string( $db_datetimes['finish'] ) . "
GROUP BY month
ORDER BY month ASC
The date_submitted is a UNIX_TIMESTAMP and $db_datetimes is a associative array on php that store the dates chosen by the user.
For instance, I want a list all months between 2016-10-01 and 2017-03-20. I got :
month | the_count
2017-03 7
2017-02 5
2017-01 2
2016-12 10
But I would like as below, including zero on months that have any record:
month | the_count
2017-03 7
2017-02 5
2017-01 2
2016-12 10
2016-11 0
2016-10 0
Sincerely I prefer to solve this problem on SQL, but any ideas using SQL or php will be welcome!

You can create a table with all months and do a left join from that table to your main table. And then replace NULLs with 0.

Have you tried to join them on the dates
FROM table1 LEFT OUTER JOIN date ON table1.date=table2.date

You can go ahead with one of the following approaches:
Create a list of month-year values dynamically (using a query) and LEFT JOIN result of that query to the one you are using. An example SO answer is here.
Create a calenar table and populate it with dates. You can then use the same LEFT JOIN with calendar and get the values.
Handle this logic in service/business layer of your application (i.e.create an array of month-year and show the values on User Interface based on results retrieved from db).
I would recommend using third approach as it's the cleanest one and won't add complexity to your db or service layer.

Related

Finding ocuppations via SQL and/or PHP

I am making a student web app. Amongst other tables, I have a table in which students enroll and enrollments are between two dates.
This app uses MySQL 5.6 and PHP 7.2
It has the following fields:
IDStudent
StartDate
EndDate
IDCourse
Each course has a maximum capacity in which it cannot be surpassed.
I want to know, given a start date, end date and IDCourse, how many concurrent students are in a course. I get an approxiumate value just counting rows between two dates
SELECT COUNT(*) FROM enrollments
WHERE IDCourse = ?
AND (
(StartDate BETWEEN "<start date>" AND "<end date>")
OR
(EndDate BETWEEN "<start date>" AND "<end date>")
OR
(StartDate <= "<start date>" AND EndDate>= "<end date>")
)
But that doesn't take account non overlapping ranges. It counts every enrollment.
For example, I have this very simple case:
Want to find how many students are enrolled between 01/01/2021 and 05/01/2021 at a specified course
And I have those 3 enrollments on that course:
01/01/2021 - 02/01/2021
03/01/2021 - 04/01/2021
20/12/2020 - 01/02/2021
I should get 2 count and not 3, because 1 and 2 don't overlap while 3 overlaps both.
I tried to search online but I didn't found something similar, maybe I am not using the correct keywords!
I found Determine max number of overlapping DATETIME ranges but that is for MySQL 8
Many thanks for your help
Regards
I think you may need to create a calendar table between the first start date and the last end date, count by date and then select the max between the period you are interested:
select max(stcount)
from
(
select c.dt, count(*) stcount from calendar_table c
join enrollments e on c.dt between e.StartDate and e.EndDate
group by c.dt
) countbydate
where dt between '2021-01-01' and '2021-01-05'
db-fiddle:
https://www.db-fiddle.com/f/dXuKMoRQ2ivLt5qi5AVFcG/0

create mysql query for fetching data in 3 days slotwise

I want to create a query for fetch all the data from database. But condition is get from before current month and data will convert into 3 days slot wise with count.Like if i have 12 month data and in July 1 to 3 date data will insert into 50 rows and from 3 to 6 date data will insert into 5 rows.How i fetch this things.That is the mail problem. I have wondered but nothing found related my problem. Is this possible.
I assume you have a DATE or a DATETIME column, that marks the day of the insert, say the column created_at. Then you could get the desired result.
SELECT
COUNT(*)
FROM
your_table
WHERE
created_at BETWEEN '2014-07-01' AND '2014-07-31 23:59:59'
GROUP BY
(DAYOFMONTH(created_at) - 1) DIV 3
Explanation:
With the help of the DIV operator you get your 3-day-slots.
Note
If there's a slot without rows, it won't be in the result. To get these too, you could use a LEFT JOIN with the maximum 11 slots.
Demo
If you having timestamps as attribute type then might be this help you
SELECT count(*) as count
FROM table
AND
TIMESTAMPDIFF(DAY,date,2014-08-01)<=3";
The date is the attribute of your column, TIMESTAMPDIFF will find difference between given date if it's under 3 days from 2014-08-01 records will show up. This way by just sending one date into this position you can find 3 slots of date from your table.

displaying the results of a query on an invoices table month by month, year by year

I have an invoices table in my database, one of the fields of which is a created date.
Users can enter a date range and the query will get results from between the range given. I want to then go through the results and display them so that all the results from, for example, jan 2013 are in a table together (pref with totals for that table), feb 2013 invoices are all in a table together and so on across years if requested.
I've never had to do something like this before and I'm trying to figure out the best way to do this.
Has anyone had to deal with this before and if so what was the best method to do so?
I have done the same thing in my rails project.
1st you fetch records 'group by invoice_year invoice_month'
then i built a hash(or any data structure you prefer) and add data corresponding to that year and month. Then use this to build your table
example: range is jan-2013 to present day
invoice_data = {'2013' => { 'jan' => {some data}, 'feb' => {some data}...}, '2014' => {'jan' {some data}}}
you have to format the query data and build such data structure.
look at extract method in sql.
Updated answer since you might not know the first part...do the hard part directly in you SQL instead of some massive php loop. Select whatever you want to display and end the query with:
WHERE invoice_date BETWEEN '$start_date' AND '$end_date' GROUP BY YEAR(invoice_date), MONTH(invoice_date)
You can pass the 2 php variables through php from the form your users use to enter the date range. Make sure you quote them if you're using DATETIME format and getting it through datepicker for example. Then it should be a piece of cake to go from there with php and display it the way you want...
You should have a look at the SQL GROUP BY Statement ( http://www.w3schools.com/sql/sql_groupby.asp ).
Assuming you have the totals of the invoices in a field called invoice_total and that your invoice date is in a field called invoice_date the following SQL statement could be used:
SELECT sum(invoice_total), year, month
FROM invoices
GROUP BY YEAR(invoice_date) as year, MONTH(invoice_date) as month

Optimising PHP/mysql algorithm

I have to make some statistics for my application, so I need an algorithm with a performance as best as possible. I have some several question.
I have a data structure like this in the mysql database:
user_id group_id date
1 5 2012-11-20
1 2 2012-11-01
1 4 2012-11-01
1 3 2012-10-15
1 9 2013-01-18
...
So I need to find the group of some user at a specific date. For example, the group of the user 1 at date 2012-11-15 (15 november 2012) should return the most recent group, which is 2 and 4 (many group at the same time) at date 2012-11-01 (the closest and smaller date).
Normally, I could do a Select where date <= chosen date order by date desc, etc... but that's not the point because if I have 1000 users, it will need 1000 requests to have all the result.
So here are some question:
I have already using the php method to loop through the array to avoid the high number of mysql request, but it's still not good because the array size may be 10000+. Using a foreach (or for?) is quite costly.
So my question is if given an array, ordered by date (desc or asc), what's the fastest way to find the closest index of the element which contain a date smaller (or greater) than a given date; beside using a for or foreach loop to loop through each element.
If there is no solution for the first question, then what kind of data structure would you suggest for this kind of problem.
Note: the date is in mysql format, it's not converted in timestamp when you stored it in an array
EDIT: this is a sql fiddle http://sqlfiddle.com/#!2/dc28d/1
For dos_id = 6, t="2012-11-01" it should returns only 2 and 5 at date "2010-12-10 13:16:58"
Not sure why you'd want to do this in php. Here's some SQL using joins instead to get most recent group(s) for all users given a date. Make sure you've got indexes on date and userid.
SELECT *
FROM test t1
LEFT JOIN test t2
ON t1.userid = t2.userid AND t2.thedate <= '2012-11-15' AND t2.thedate > t1.thedate
WHERE t1.thedate <= '2012-11-15' AND t2.userid IS NULL;
SQLfiddle
Or using your SQLFiddle
SELECT t1.*
FROM dossier_dans_groupe t1
LEFT JOIN dossier_dans_groupe t2
ON t1.dos_id = t2.dos_id AND t2.updated_at <= '2012-11-01'
AND t2.updated_at > t1.updated_at
WHERE t1.updated_at <= '2012-11-01' AND t2.dos_id IS NULL;
This would give you a list of all users and their groups (1 row per group) for the latest date that is smaller than the one you specify (2012-11-15 below).
SELECT user_id, group_id, date FROM table WHERE date <= '2012-11-15' AND NOT EXISTS (SELECT 1 FROM table test WHERE test.user_id = table.user_id AND test.date > table.date and test.date <= '2012-11-15')

jQuery flot, MySQL count rows for each day

I've table for hits that contains of 2 fields
id time(TIMESTAMP)
1 2012-05-03 08:56:17
2 2012-04-26 03:22:20
3 2012-05-03 08:56:17
How can I count how many rows for each day for example for the past 10 days?
I mean what is the right query for it that produces:
2012-05-03 => 25 hits
2012-05-05 => 55 hits
etc
Also, how can i arrange the returned array as jQuery flot array ?
any answers / articles regarding my questions would be great help
Thank you
You want to use a Group By on the date field
something like
select count(*), dateField from tablename
group by dateField
Here is how I'd do it:
select count(id), DATE(time) as hitday from yourtable group by hitday
And regarding your jQuery thing, I am not very sure if I understand it or not. You want mysql to generate a jQuery parsable array for you? For that you definitely need to have a kinda middle man like php.
Here is another thread which shows you how a select result is converted to json using php.

Categories