Setting null values to dates that aren't in MySQL - php

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
;

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

Count MySQL-Entries per day, inclusive days without any entries (Date Range)

I have a MySQL-Table
id mydate content
----------------------------------
1 2015-06-20 some content
2 2015-06-20 some content
3 2015-06-22 some content
Now I want to count the entries for each day:
SELECT DATE(mydate) Date, COUNT(DISTINCT id) dayCount FROM mytable
GROUP BY DATE(mydate) HAVING dayCount > -1 ORDER BY DATE(mydate) DESC
This works for me, result:
2015-06-20 = 2
2015-06-22 = 1
How can I fetch days without any entries? In my example the result should be:
2015-06-19 = 0
2015-06-20 = 2
2015-06-21 = 0
2015-06-22 = 1
2015-06-23 = 0
Based on this:
<?php
$today = date("Y-m-d");
$mystartdate = date_create($today);
date_sub($mystartdate, date_interval_create_from_date_string('14 days'));
$mystartdate = date_format($mystartdate, 'Y-m-d');
?>
Finaly I want to output the counts of the last 14 days, also with "0-days". Hope you understand my problem.
For this you can create new table that holds the increment numbers, but it's not a great idea. However, if doing it this way, use this table to construct a list of dates using DATE_ADD.
LEFT JOIN onto your table of data based on the time portion to achieve your list of dates
for more info go through the link
MySQL how to fill missing dates in range?
try below-
SELECT a.date_field, COUNT(DISTINCT b.id) dayCount FROM
(SELECT date_field FROM
(
SELECT
MAKEDATE(YEAR(NOW()),1) +
INTERVAL (MONTH(NOW())-1) MONTH +
INTERVAL daynum DAY date_field
FROM
(
SELECT t*10+u daynum
FROM
(SELECT 0 t UNION SELECT 1 UNION SELECT 2 UNION SELECT 3) A,
(SELECT 0 u 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) B
ORDER BY daynum
) AA
) AAA
WHERE MONTH(date_field) = MONTH(NOW()) ) a
LEFT JOIN mytable b ON a.date_field=DATE(b.mydate)
GROUP BY a.date_field HAVING dayCount > -1 ORDER BY a.date_field DESC;

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

reach memory limit with loop inside loop inside loop

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.

Categories