reach memory limit with loop inside loop inside loop - php

Disclaimer: I am aware that I have in my code deprecated mysql functions. That is on my todo list.
I have a MySql select giving me seasons for diferent items in a house booking system.
Kind of:
Low season: 2010-01-01, 2010-03-01, 100 //meaning start,end,price
This comes in my first sql:
while($season_row=mysql_fetch_assoc($season_res)){
$seasonsArray[$season_row['id_item']][] = array(
$season_row['season_start'],
$season_row['season_end'],
$season_row['daily_price']
);
}
The dates are defined here (arriving to the function as YYYY-mm-dd):
function seasonPrice($from,$to,$requested_item){
$start = round(strtotime($from)/86400)*86400; // like 2008-01-01
$end = round(strtotime($to)/86400)*86400; // to 2015-01-01
$formattedStart = date('Y-m-d', $start);
$formattedEnd = date('Y-m-d', $end);
Now I need to loop between 2 dates, between the items of the $seasonsArray and then check the price of that item in that specific day.
I did this with:
foreach($seasonsArray as $item=>$value){
for( $thisDay = $start; $thisDay < $end; $thisDay = $thisDay + 86400){
foreach($value as $innerValue){
$season_start = roundToSeconds($innerValue[0]);
$season_end = roundToSeconds($innerValue[1]);
if($thisDay >= $season_start && $thisDay <= $season_end) {
$foundPrice[] = round($innerValue[2]);
}
}
$thisSerie[] = array($thisDay * 1000, isset($foundPrice) ? $foundPrice[0] : 0);
// security check to avoid double assigned seasons to same day
if(count($foundPrice) > 1){ die('There is double bookings in item: '.$item);}
unset($foundPrice);
}
$seasonPrices[] = array(
'data'=> $thisSerie,
'label'=> 'House ID: '.$item,
);
}
But I get: Fatal error: Allowed memory size of 100663296 bytes exhausted
Any suggestion on where my code can be improved to not need so much memory? Or is there a bug and I don't see it?

I'd generate a range of days and join against your seasons table, and use a single query to get the desired resulset, e.g.:
SELECT dates.Date,
coalesce(s.price, 0) AS price
FROM
(SELECT a.Date
FROM
( SELECT curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY AS Date, '0' AS price
FROM
(SELECT 0 AS a
UNION ALL SELECT 1
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
UNION ALL SELECT 6
UNION ALL SELECT 7
UNION ALL SELECT 8
UNION ALL SELECT 9) AS a
CROSS JOIN
(SELECT 0 AS a
UNION ALL SELECT 1
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
UNION ALL SELECT 6
UNION ALL SELECT 7
UNION ALL SELECT 8
UNION ALL SELECT 9) AS b
CROSS JOIN
(SELECT 0 AS a
UNION ALL SELECT 1
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
UNION ALL SELECT 6
UNION ALL SELECT 7
UNION ALL SELECT 8
UNION ALL SELECT 9) AS c) a
WHERE a.Date BETWEEN '$from' AND '$to'
ORDER BY a.Date) dates
LEFT JOIN seasons s ON dates.Date BETWEEN s.start AND s.END
The complicated inner query avoids the creation of a temp table (taken from generate days from date range) and works for up to 1000 days, but creating a temp table would be fine.

It looks to be like you are never leaving the for loop. Where do $start and $end get set. Verify their values by printing them out.
As far as optimization, there is no need to be looping through each day. Skip the for loop that counts days and use $season_start and $season_end to calculate the day in the second foreach loop.
In fact, there is a bug right now unless $session_start and $session_end are always more than a day apart, because sometime the event will happen in between the 24 hour periods you are looping by.

Related

Pivot query to display data for each day of the year

I have one database table as following:
id|start_date|end_date|sec
1|2018-08-01|2018-08-03|2500
2|2018-08-02|2018-08-13|100
3|2018-08-01|2018-08-05|500
So I want to display the report date wise so that user can know how many seconds available in specific days.
For example, I need below things:
Date /Day total
2018-08-01 (2500+500)=3000 //this date comes in 1&3 records
2018-08-02 (2500+100+500)=3100 //this date comes in all 1,2,&3 records
2018-08-03 (2500+100+500)=3100 //this date comes in all 1,2,&3 records
2018-08-04 (100+500)=600 //this date comes in 2&3 records
2018-08-05 (100+500)=600 //this date comes in 2&3 records
2018-08-06 (100)=100 //this date comes in 3 records
I am trying to use mysql and php but I don't know how to do.
There is no clean way to generate a table with each day of current year on the fly, I took the subtable code from generate days from date range (to give him credit) because it didn't use any loops etc and had a short execution time. That being said, you can go with :
SELECT
dpy.day as Day,
SUM(IFNULL(t.sec,0)) as total
FROM
(
select a.Date as day
from (
select DATE_FORMAT(CURRENT_DATE(), '%Y-12-31') - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY as Date
from (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as a
cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as b
cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as c
) a
where a.Date between DATE_FORMAT(CURRENT_DATE(), '%Y-01-01') and DATE_FORMAT(CURRENT_DATE(), '%Y-12-31')
) dayPerYear dpy
LEFT JOIN
your_table t ON dpy.day > t.start_date AND dpy.day < t.end_date
GROUP BY dpy.day
Just replace your_table by your table name
Lets begin by assuming that your records are already fetched and are available in an associative array named $rows (after a query and a mysqli_fetch_array type call):
$ra=array();
$nr=count($rows);
for ($i=0; $i<$nr; $i++) {
$start=$rows['start_date'];
$end=$rows['end_date'];
$sec=$rows['sec'];
if (isset($ra[$start])) {
$ra[$start]+=$sec;
} else {
$ra[$start]=$sec;
}
if (isset($ra[$end])) {
$ra[$end]+=$sec;
} else {
$ra[$end]=$sec;
}
}
print_r($ra);
You have to loop through days
select sum(sec) from table where ('2018-08-01') between start_date and end_date ;
For generating the days With php, populate an array of days in current month

MySQL select all days in date range even if no data is available for day

I have entries for each days as a punch in/out clock. I would like to display all days for the date givens week and show the days even if no data in found for that day.
Example:
Monday 2015-05-04
- 2015-05-04 10:30:00
- 2015-05-04 15:45:34
Tuesday 2015-05-05
- 2015-05-05 08:43:23
- 2015-05-05 17:18:13
Wednesday 2015-05-06
- 2015-05-06 09:03:12
Thursday 2015-05-07
0 Entries
Friday 2015-05-08
0 Entries
The DB schema looks like:
id | user_id | punch_time | punch_status
I would ideally like to be able to change the date given to MySQL to any timestamp and it would show the days and results for that week.
Thanks :D
NEW
Any ideas why this doesn't work to get days when no records are present?
SELECT * FROM punch_clock, calendar_table WHERE calendar_table.dt = DATE(punch_clock.punch_time) && calendar_table.week_num = $week && calendar_table.y = $year ORDER BY punch_clock.punch_time
Newer Query
SELECT * FROM punch_clock LEFT JOIN calendar_table ON calendar_table.dt = DATE(punch_clock.punch_time) WHERE calendar_table.week_num = 18 && calendar_table.y = 2015;
With MySQL, I usually use a calendar table for this purpose (containing all the dates until 2030 for example)
It allows to do many other things like this kind of queries, manage special days, etc..
You'll want to LEFT JOIN your table on it, I mean this calendar table have to be "left positioned"
Taking your last query, I'd do this:
SELECT *
FROM calendar AS cal
LEFT JOIN punch_clock AS puc
ON (cal.dt = DATE(puc.punch_time))
WHERE TRUE
AND cal.week_num = 18
AND cal.y = 2015
;
Didn't try, but this is the idea.
Have a try of this:
SELECT *
FROM
(
SELECT a.Date AS mydate
FROM (
SELECT date('2015-05-08') - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY AS Date
FROM (SELECT 0 AS a
UNION ALL SELECT 1
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
UNION ALL SELECT 6
UNION ALL SELECT 7
UNION ALL SELECT 8
UNION ALL SELECT 9) AS a
CROSS JOIN (SELECT 0 AS a
UNION ALL SELECT 1
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
UNION ALL SELECT 6
UNION ALL SELECT 7
UNION ALL SELECT 8
UNION ALL SELECT 9) AS b
CROSS JOIN (SELECT 0 AS a
UNION ALL SELECT 1
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
UNION ALL SELECT 6
UNION ALL SELECT 7
UNION ALL SELECT 8
UNION ALL SELECT 9) AS c
) a
WHERE a.Date BETWEEN '2015-05-04' AND '2015-05-08'
) dates
LEFT JOIN
(
SELECT *
FROM
table1
) data
ON DATE_FORMAT(dates.mydate, '%Y%m%d') = DATE_FORMAT(data.punch_time, '%Y%m%d')
SQL Fiddle: http://sqlfiddle.com/#!9/72ee3/15/0
This is a fast but not that ideal solution for your question. But I think it's just enough for use.
If you'd like to solve the problem "perfectly", I suggest you reading this article: http://www.brianshowalter.com/calendar_tables
Even while this is a really old question I did not like the idea of an extra calendar table and came up with this query, adjusted for the question.
SELECT pc.`id`, pc.`user_id`, pc.`punch_status`, dates.`date` FROM punch_clock pc RIGHT JOIN (SELECT week.`date` FROM (
SELECT CURDATE() AS `date`
UNION SELECT CURDATE() + INTERVAL 1 DAY
UNION SELECT CURDATE() + INTERVAL 2 DAY
UNION SELECT CURDATE() + INTERVAL 3 DAY
UNION SELECT CURDATE() + INTERVAL 4 DAY
UNION SELECT CURDATE() + INTERVAL 5 DAY
UNION SELECT CURDATE() + INTERVAL 6 DAY
UNION SELECT CURDATE() - INTERVAL 1 DAY
UNION SELECT CURDATE() - INTERVAL 2 DAY
UNION SELECT CURDATE() - INTERVAL 3 DAY
UNION SELECT CURDATE() - INTERVAL 4 DAY
UNION SELECT CURDATE() - INTERVAL 5 DAY
UNION SELECT CURDATE() - INTERVAL 6 DAY
) AS week
WHERE YEARWEEK(`date`, 0) = YEARWEEK(CURDATE(), 0)) AS dates ON dates.`date` = DATE(pc.`punch_time`) ORDER BY dates.`date`
Not exactly pretty, but it does the job without an extra table. This uses a week from Monday to Sunday, use YEARWEEK(xxx, 1) for Sunday to Monday week.

MySQL + PHP: Display items per week?

This has me a stummped...
If I have a this MySQL table:
UserId | Commission | Date Of Commission
1 | 200.00 | 2014-02-12
1 | 50.00 | 2014-04-01
2 | 10.00 | 2014-04-05
and I would like to display the Total Commission for a specific user per week starting from his/her first record, and display 0 for that range if there's no record.
how would I go about it?
Sample Output
UserId | Date Range | Total Commission
1 | 02/10/14 - 02/16/14 | 200.00
1 | 02/17/14 - 02/23/14 | 0.00
...
1 | 03/31/14 - 04/06/14 | 50.00
I'm not a seasoned coder so any help will be much appreciated.
Thanks!
Edit:
I have tried this:
SELECT IFNULL(SUM(Commisssion),0) Total ,DATE_SUB(`DateOfCommission`,INTERVAL 7 DAY)
AS RangStart,DATE_SUB(`DateOfCommission`,INTERVAL 1 DAY) AS RangeEnd
FROM `comms` WHERE `UserId` = '$UserID' GROUP BY DATE(`DateOfCommission`) DESC
but it starts the week with whatever date the first record was entered..
This is very tricky to accomplish. Here is what I managed to do with small modifications it should work they way it needs to be. I have done it for userid = 1 and this could be done for other users as well.
In the query I have 2 lines
where a.Date BETWEEN (select min(date) from transactions where UserId = 1) AND NOW()
and
WHERE date BETWEEN (select min(date) from transactions where UserId = 1) AND NOW()
The query will try to generate the list of dates using the min() date of transaction for the user till today. Instead of now() this could be used as max() date of transaction for the user as well.
select
t1.date_range,
coalesce(SUM(t1.Commission+t2.Commission), 0) AS Commission
from
(
select
a.Date as date,
concat(
DATE_ADD(a.Date, INTERVAL(1-DAYOFWEEK(a.Date)) +1 DAY),
' - ',
DATE_ADD(a.Date, INTERVAL(7- DAYOFWEEK(a.Date)) +1 DAY)
) as date_range,
'0' as Commission
from (
select curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY as date
from (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as a
cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as b
cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as c
) a
where a.Date BETWEEN (select min(date) from transactions where UserId = 1) AND NOW()
)t1
left join
(
SELECT date ,
coalesce(SUM(Commission), 0) AS Commission
FROM transactions
WHERE date BETWEEN (select min(date) from transactions where UserId = 1) AND NOW()
AND UserId = 1
GROUP BY date
)t2
on t2.date = t1.date
group by t1.date_range
order by t1.date_range asc
DEMO
So, this is sort of an algorithm you could use:
$Result = select distinct userid from table(this will fetch all userids from table)
while(There are rows in $Result)
{
$Userid = $Result['userid']
$StartDateRes = mysql_query(select Date, WEEKOFYEAR(Date) as week from table where userid = Userid order by date asc limit 1)
$StartDateRow = mysql_fetch_assoc($StartDateRes)
$StartDate = $StartDateRes['Date']
$StartWeekNumber = $StartDateRes['week']
$EndDateRes = mysql_query(select Date, WEEKOFYEAR(Date) as week from table where userid = Userid order by date desc limit 1)
$EndDateRow = mysql_fetch_assoc($EndDateRes)
$EndDate = $EndDateRes['Date']
$EndWeekNumber = $EndWeekRes['week']
for($i=$StartWeekNumber; $i<=$EndWeekNumber; $i++)
{
$StartDateOfWeek = FuncToFindStartDateOfWeek($i)
$EndDateOfWeek = FuncToFindEndDateOfWeek($i)
$Result2 = mysql_query(select sum(commission) as sum from table where date between StartDateOfWeek and EndDateOfWeek group by userid)
$Row2= mysql_fetch_assoc($Result2)
$Sum = $Row2['sum']
mysql_query("insert into OutputTable values($UserId, $StartDateOfWeek. '-'. $EndDateOfWeek ,$Sum");
}
}
SELECT UserId, COALESCE(SUM(Commission),0), YEARWEEK(DateOfCommission) AS TheWeek
GROUP BY UserId, TheWeek
ORDER BY UserId, TheWeek;
This will not print the nice date range, but should get you started in a SQL-only direction where the sum is broken down by the week of the year. I think you could take it from this point to add the nicer formatting of the Year/Week column. YEARWEEK() should give you pretty fast results.
The easiest way I can think of doing this as follows
Step 1: Get the date of the first record
"SELECT dateofcommission FROM comissionstable WHERE id='userid' ORDER BY dateofcommission ASC LIMIT 1"
The above query will return the first date of commission only
Step 2: Create a loop which starts from the date you got in Step 1 and continue the loop till the date is greater than or equal to today's date. Increment this date using PHP date function.
date('Y-m-d', strtotime($dateofcommission. ' + 7 days'));
Step 3: In this loop you can get the commission with-in the starting date and ending date. Starting date will be the date before adding 7 days and ending date will be the one after you have added 7 days.
SELECT SUM(commission) FROM commissiontable WHERE dateofcommission>= startingdate AND dateofcomission < endingdate AND id='userid'
The above logic should work. If you end up having some issues with this logic feel free to post in comments. I would be happy to help
The following is another solution
function getStartAndEndDate($week, $year) {
$time = strtotime("1 January $year", time());
$day = date('w', $time);
$time += ((7*$week)+1-$day)*24*3600;
$return[0] = date('Y-n-j', $time);
$time += 6*24*3600;
$return[1] = date('Y-n-j', $time);
return $return;
}
$query = mysqli_query($con, "SELECT userid, COALESCE( SUM( commission ) , 0 ) AS thecommission , YEARWEEK( doc ) AS TheWeek FROM commission GROUP BY userid, TheWeek ORDER BY userid, TheWeek");
while ($array = mysqli_fetch_array($query)) {
$test = $array['TheWeek'];
$store_array = getStartAndEndDate(substr($test,4,2), substr($test,0,4));
echo $array['userid']."-".$array['thecommission']."-".$store_array[0]."/".$store_array[1]."<br>";
}

Most efficient way to populate a calendar?

I have built a simple PHP calendar which displays a 12 month period. I have a MySQL database which holds calendar events. I would like to populate my calendar with my events. I could of course run a MySQL query for each day but 365 queries seems very inefficient, as does running a single MySQL query and looping through the results 365 times. What, if any, are the alternatives? Is there a simple, and efficient, way of doing this?
$query = "SELECT idEvent, day, hour , event from calendar where year = 2014 order by day, hour ";
$calendarEvents = array();
if ($result = $mysqli->query($query)) {
/* fetch associative array */
while ($row = $result->fetch_assoc()) {
$calendarEvents[ $row["day"] ][ $row["idEvent"] ] = $row;
}
/* free result set */
$result->free();
}
Then you have the data from 2014 with 1 query and access them with array $calendarEvents.
For each day you have all events by id, so with foreach loop you get all events for that day with an order same as query order
for($i = 1; $i < 365 ; $i++){
$date = date("Y-m-d" , strtotime( "2013-12-31 + $i day"));
foreach( $calendarEvents[ $date ] as $idEvent => $c){
echo $idEvent . " -- ".$date." - ". $c["hour"]." - ".$c["event"];
}
}
To give you a result set with one row per day of the year, and all the events (if any) for that day concatenated into another column you could do something like this:-
SELECT DateSub.aDay, GROUP_CONCAT(CONCAT_WS(':', SomeCalendarTable.eventName, SomeCalendarTable.startTime, SomeCalendarTable.endTime)) AS DaysEvents
FROM
(
SELECT DATE_ADD('2014/01/01', INTERVAL (Units.DayNo + Tens.DayNo * 10 + Hundreds.DayNo * 100) DAY) AS aDay
FROM
(SELECT 1 AS DayNo UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 0) Units,
(SELECT 1 AS DayNo UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 0) Tens,
(SELECT 1 AS DayNo UNION SELECT 2 UNION SELECT 3 UNION SELECT 0) Hundreds
HAVING YEAR(aDay) = '2014'
) DateSub
LEFT OUTER JOIN SomeCalendarTable
ON DateSub.aDay = SomeCalendarTable.calendarDate
GROUP BY DateSub.aDay

Setting null values to dates that aren't in MySQL

So, I am fetching some data from MySQL with that code:
<?
$query=mysql_query("SELECT date,COUNT(*) as num FROM downloads WHERE prjID='".$_GET['id']."' GROUP BY date ORDER BY date ASC");
$num=mysql_num_rows($query);
$res='';
$i=0;
while($row=mysql_fetch_array($query)){
$i++;
$date=date("d.m.Y", strtotime($row['date']));
$dan=date("d", strtotime($row['date']));
$mesec=date("m", strtotime($row['date']));
$leto=date("Y", strtotime($row['date']));
if($i=1){
$danPrvi=$leto.", ".($mesec-1).", ".$dan;
$dan1=date("d", strtotime(time()));
$mesec1=date("m", strtotime(time()));
$leto1=date("Y", strtotime(time()));
$danZadnji=$leto1.", ".($mesec1-1).", ".$dan1;
}
$numb=1;
if($row['num']!=1){
$res.="[Date.UTC(".$leto.",".($mesec-1).",".$dan."),".$row['num']."], ";
}
else{
if($i!=$num){
$res.="[Date.UTC(".$leto.",".($mesec-1).",".$dan."),".$numb."], ";
}
else{
$res.="[Date.UTC(".$leto.",".($mesec-1).",".$dan."),".$numb."]";
}
}
}
?>
And I get results like that:
1.3.2013 - 1
6.3.2013 - 5
But I would like to get results like that:
1.3.2013 - 1
2.3.2013 - 0
3.3.2013 - 0
4.3.2013 - 0
5.3.2013 - 0
6.3.2013 - 1
I am also using Highcharts, so date values must be formatted like Date.UTC(year, month-1, day)
EDIT:
I don't have all dates in my databases. In my examle, there are only 1.3.2013 and 6.3.2013, so how would I detect and set 0 value for all dates between, that don't have a value >=1?
I imagine what you need to do is set up a construct to hold the dates you are interested in and either use that in your query or post processing the query data. For example:
<?php
$query=mysql_query("SELECT date,COUNT(*) as num FROM downloads WHERE prjID='".$_GET['id']."' GROUP BY date ORDER BY date ASC");
$num=mysql_num_rows($query);
// Get the first and last dates in the result set
$firstRow = mysql_result($query, 0);
$lastRow = mysql_result($query, $num-1);
// Now make thos the begin and end dates
$beginDate = new DateTime(strtotime($firstRow['date']));
$endDate = new DateTime(strtotime($lastRow['date']));
$currentDate = $beginDate;
$interestingDates = array();
// Populate our interestingDates array with all counts set to 0
while ($currentDate <= $endDate){
$interestingDates[$currentDate->format('d.m.Y')] = 0;
$currentDate->add(new DateInterval('P1D'));
}
// Reset the data result for looping over
mysql_data_seek($query,0);
while($row=mysql_fetch_array($query)){
// Go ahead and format the string
$formatedString = date("d.m.Y", strtotime($row['date']));
// If the string is in our interestingDates array, update the count
if (array_key_exists($formatedString, $interestingDates)){
$interestingDates[$formatedString] = $row['num'];
}
}
// Print it out
foreach ($interestingDates as $key=>$value){
print "$key - $value\n";
}
NOTE 1: mysql_query is deprecated as of PHP 5.5.0 and will be removed in the future. Pleasue use another API - I recommend pdo_mysql.
NOTE 2: The current query is not parameterized. Using PDO, this would perhaps look like:
$sth = $dbh->prepare('SELECT date,COUNT(*) as num FROM downloads WHERE prjID= :prjID GROUP BY date ORDER BY date ASC');
$sth->bindParam(':prjID', $_GET['id'], PDO::PARAM_INT);
$sth->execute();
Disclaimer - I haven't actually run this code, I just wrote it off the top of my head. You might need to test/debug it.
Assuming you have data for all dates, just not for a particular id, you can do a condition sum:
SELECT date, sum(case when prjID='".$_GET['id']."' then 1 else 0 end) as num
FROM downloads
GROUP BY date
ORDER BY date ASC
WARNING! It's a very bad style to do it in SQL, but if you want -- you can :)
http://sqlfiddle.com/#!2/b491a/1/0
SELECT dynamic_date AS date, COUNT(downloads.date) AS num FROM (
SELECT DATE_ADD(start, INTERVAL i - 1 DAY) AS dynamic_date, prjID FROM
(
SELECT #i := #i + 1 AS i FROM
(SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t1,
(SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t2,
(SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) t3,
(SELECT #i:=0) init
) i_sequence,
(SELECT MIN(date) start, MAX(date) finish, prjID FROM downloads WHERE prjID=5 GROUP BY prjID) minmax
WHERE DATE_ADD(start, INTERVAL i - 1 DAY) <= finish
) date_sequence
LEFT JOIN downloads ON dynamic_date = downloads.date and downloads.prjID=date_sequence.prjID
GROUP BY dynamic_date
;

Categories