get next upcoming game, display on mondays - php

There will be games at saturdays but not every weekend. I Want to show on a new week if there is an upcoming game that following saturday.
I was thinking of something like this to check 6 days ahead if we are on ex monday, if there is a game that saturday it should find it. But of course it lacks parts, just a start but Im stuck. As for now it finds the next game which is 1 month away, it should only show if there is one that week.
"Select * From games Where date > DATE_ADD(CURDATE(),INTERVAL 6 DAY)"

You can use PHP's DateTime class to find the date for next Saturday:
$nextSaturday = new DateTime('next Saturday');
From there, just select where the date equals $nextSaturday->format('Y-m-d') to find the games (if any) for next Saturday.

What you want to ask the database to fetch is "all games WITHIN the next 6 days" really
SELECT * FROM `games` WHERE `date` >= CURDATE()
AND `date` <= DATE_ADD(CURDATE, INTERVAL 6 DAY)
... or you could use BETWEEN ...
SELECT * FROM `games` WHERE `date`
BETWEEN CURDATE()
AND DATE_ADD(CURDATE(), INTERVAL 6 DAY)
[edit]
Having just worked out what you meant by "display on mondays" in the question title ... you only actually want to execute the query on Mondays :)
So you'll either only want to run the query on Mondays (done at the application level) or run the query with an AND DAYOFWEEK(CURDATE()) = 2 in it so it'd be something like
SELECT * FROM `games` WHERE `date` >= CURDATE()
AND `date` <= DATE_ADD(CURDATE, INTERVAL 6 DAY)
AND DAYOFWEEK(CURDATE()) = 2
Although that's rather inefficient as you'll always run the query even if there's no need to - it would be better to have the application only execute it on Mondays.
In which case you're better off with drrcknlsn's solution using PHP's DateTime object and doing something like (or munging the whole thing into a Cron job):
if(date("N") == 1) {
$oNextSaturday = new DateTime('next Saturday');
$sFetchUpcomingGames = "SELECT * FROM `games` WHERE `date` = '{$oNextSaturday->format('Y-m-d')}'";
//execute the database query and perform relevant outputs, and so on
}

Related

MYSQL SELECT records older than 1 year ago

I try to get a list of all records that are older than 1year ago from my database, the field for expired_contract has next information.
expired_contract DATE NOT NULL
So it takes the DATE in the next format: YEAR-MM-DD, next i have the sql that i cant get it working sadly.
$sql = "SELECT *
FROM My_Contracte
WHERE expired_contract >= DATE_SUB(NOW(),INTERVAL 1 YEAR)
ORDER BY id_contract DESC";
I tried a lot of "WHERE" commands but none worked as i expected. Can you help me get this working? I'm looking on this for about 5hours i need exact command to get it worked.
The $sql gets me something but takes it wrong, i get dates like: 2015-10-01, 2016-10-01 and date like 2014-09-30 doesn't show up.
Basically i want to show dates like:
If today is 2015-10-01 i want to see dates older than 1year ago so from 2014-09-30 and not showing dates like 2015-10-01, 2016-10-01.
Maybe do i have to edit something in database?
Looking for your help, thank you!
You have to use lower than instead of greater or equals:
$sql = "SELECT * FROM My_Contracte WHERE expired_contract < DATE_SUB(NOW(),INTERVAL 1 YEAR)
SELECT SUM(impressions) AS impressions FROM My_Contracte where DATE(expired_contract) BETWEEN DATE_SUB( CURDATE( ) ,INTERVAL 1 YEAR ) AND CURDATE( )
SELECT * FROM My_Contracte WHERE (expired_contract NOT BETWEEN DATE_SUB(CURDATE(),INTERVAL 1 YEAR) AND CURDATE()) and expired_contract<=NOW() ;

Filter a range of dates with SQL

I have to filter the results of an SQL starting at the current day and displaying all records until the current day + 10 days. So if it's the 22th of December I should display all records starting that day and that aren't after the the 1st of January. How can I do this? I tried a simple query like the one below but it seems to only display the records until the last day of the current month and then instead of showing the records of the next year goes back to the first month of the current year.
SELECT *
FROM mytable
WHERE DAY(mydatefield) >= DAY(CURRENT_TIMESTAMP)
AND mydatefield <= DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 10 DAY)
EDIT 1
I'm using Symfony 2 + Doctrine 2 querybuilder so the query must be compatible with them
EDIT 2
I solved the Doctrine 2 problem by using the query suggested by eggyal with this PHP code
$queryBuilder->where($queryBuilder->expr()->gte('mydatefield', 'CURRENT_DATE()'))->andWhere($queryBuilder->expr()->lte('mydatefield', 'DATE_ADD(CURRENT_DATE(), 10, \'DAY\')'));
The problem lies in your erroneous use of MySQL's DAY() function, which returns the day of the month. You should use instead DATE(); you can also simplify with the BETWEEN ... AND ... comparison operator:
SELECT *
FROM mytable
WHERE DATE(mydatefield) BETWEEN CURDATE() AND CURDATE() + INTERVAL 10 DAY
Note that, in order to benefit from index optimisation, you could instead do:
SELECT *
FROM mytable
WHERE mydatefield >= CURDATE() AND mydatefield < CURDATE() + INTERVAL 11 DAY
You should be able to use this:
SELECT *
FROM mytable
WHERE date(mydatefield) >= date(CURRENT_TIMESTAMP)
AND date(mydatefield) <= date(DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 10 DAY))
See SQL Fiddle with Demo
try this
SELECT *
FROM mytable
WHERE DATE( mydatefield ) BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL 10 DAY

Very specific MySQL query I want to improve

This is my scenario: I have a table that contains events, every event has a field called 'created' with the timestamp in which that event was created. Now I need to sort the events from newest to oldest, but I do not want MySQL to return them all. I need only the latest in a given interval, for example in a range of 24 hours (EDIT: I'd like to have a flexible solution, not only for a 24 hours range, but maybe every few hours). And I only need for the last 10 days. I have achieved that but i'm sure in the most inefficient ways possible, that is, something like that:
$timestamp = time();
for($i = 0; $i < 10; $i++) {
$query = "SELECT * FROM `eventos` WHERE ... AND `created` < '{$timestamp}' ORDER BY `created` DESC LIMIT 1";
$return = $database->query( $query );
if($database->num( $return ) > 0) {
$event = $database->fetch( $return );
$events[] = $event;
$timestamp = $timestamp - 86400;
}
}
I hope I was clear enough. Thanks,
Jesús.
If you have an index with created as the leading column, MySQL may be able to do a reverse scan. If you have a 24 hour period that doesn't have any events, you could be returning a row that is NOT from that period. To make sure you're getting a row in that period, you would really need to include a lower bound on the created column as well, something like this:
SELECT * FROM `eventos`
WHERE ...
AND `created` < FROM_UNIXTIME( {$timestamp} )
AND `created` >= DATE_ADD(FROM_UNIXTIME( {$timestamp} ),INTERVAL -24 HOUR)
ORDER BY `created` DESC
LIMIT 1
I think the big key to performance here is an index with created as the leading column, along with all (or most) of the other columns referenced in the WHERE clause, and making sure that index is used by your query.
If you need a different time interval, down to the second, this approach could be easily generalized.
SELECT * FROM `eventos`
WHERE ...
AND `created` < DATE_ADD(FROM_UNIXTIME({$timestamp}),INTERVAL 0*{$nsecs} SECOND)
AND `created` >= DATE_ADD(FROM_UNIXTIME({$timestamp}),INTERVAL -1*{$nsecs} SECOND)
ORDER BY `created` DESC
LIMIT 1
From your code, it looks like the 24-hour periods are bounded at an arbitrary time... if the time function returns e.g. 1341580800 ('2012-07-06 13:20'), then your ten periods would all be from 13:20 on a given day to 13:20 the following day.
(NOTE: be sure that if your parameter is a unix timestamp integer, that this is being interpreted correctly by the database.)
It might be more efficient to pull the ten rows in a single query. If there is a guarantee that 'timestamp' is unique, then it's possible to craft such a query, but the query text will be considerably more complex than what you have now. We could mess with getting MAX(timestamp_) within each period, and then joining that back to get the row... but that's going to be really messy.
If I were going to try to pull all ten rows I would probably try going with a UNION ALL approach, not very pretty, but it least it could be tuned.
SELECT p0.*
FROM ( SELECT * FROM `eventos` WHERE ...
AND `created` < DATE_ADD(FROM_UNIXTIME({$timestamp}),INTERVAL 0*24 HOUR)
AND `created` >= DATE_ADD(FROM_UNIXTIME({$timestamp}),INTERVAL -1*24 HOUR)
ORDER BY `created` DESC LIMIT 1
) p0
UNION ALL
SELECT p1.*
FROM ( SELECT * FROM `eventos` WHERE ...
AND `created` < DATE_ADD(FROM_UNIXTIME({$timestamp}),INTERVAL -1*24 HOUR)
AND `created` >= DATE_ADD(FROM_UNIXTIME({$timestamp}),INTERVAL -2*24 HOUR)
ORDER BY `created` DESC LIMIT 1
) p1
UNION ALL
SELECT p2.*
FROM ( SELECT * FROM `eventos` WHERE ...
AND `created` < DATE_ADD(FROM_UNIXTIME({$timestamp}),INTERVAL -2*24 HOUR)
AND `created` >= DATE_ADD(FROM_UNIXTIME({$timestamp}),INTERVAL -3*24 HOUR)
ORDER BY `created` DESC LIMIT 1
) p2
UNION ALL
SELECT p3.*
FROM ...
Again, this could be generalized, to pass in a number of seconds as an argument. Replace HOUR with SECOND, and replace the '24' with a bind parameter that has a number of seconds.
It's rather long winded, but it should run okay.
Another really messy and complicated way to get this back in a single result set would be to use an inline view to get the end timestamp for the ten periods, something like this:
SELECT p.period_end
FROM (SELECT DATE_ADD(t.t_,INTERVAL -1 * i.i_* {$nsecs} SECOND) AS period_end
FROM (SELECT FROM_UNIXTIME( {$timestamp} ) AS t_) t
JOIN (SELECT 0 AS i_
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
) i
) p
And then join that to your table ...
ON `created` < p.period_end
AND `created` >= DATE_ADD(p.period_end,INTERVAL -1 * {$nsecs} SECOND)
And pull back MAX(created) for each period GROUP BY p.period_end, wrap that in an inline view.
And then join that back to your table to get each row.
But that is really, really messy, hard to understand, and not likely to be any faster (or more efficient) than what you are already doing. The most improvement you could make is the time it takes to run 9 of your queries.
Assuming you want the latest (having the greatest created date) event per day for the last 10 days.
so let's get the latest timestamp per day
$today = date('Y-m-d');
$tenDaysAgo = date('Y-m-d', strtotime('-10 day'));
$innerSql = "SELECT date_format(created, '%Y-%m-%d') day, MAX(created) max_created FROM eventos WHERE date_format(created, '%Y-%m-%d') BETWEEN '$today' and '$tenDaysAgo' GROUP BY date_format(created, '%Y-%m-%d')";
Then we can select all the events that match those created dates
$outerSql = "SELECT * FROM eventos INNER JOIN ($innerSql) as A WHERE eventos.created = A.max_created";
I haven't had a chance to test this, but the principles should be sound enough.
If you want to group by some other arbitrary number of hours you would change innerSql:
$fromDate = '2012-07-06' // or if you want a specific time '2012-07-06 12:00:00'
$intervalInHours = 5;
$numberOfIntervals = 10;
$innerSql = "SELECT FLOOR(TIMESTAMPDIFF(HOUR, created, '$fromDate') / $intervalInHours) as grouping, MAX(created) as max_created FROM eventos WHERE created BETWEEN DATE_SUB('$fromDate', INTERVAL ($intervalInHours * $numberOfIntervals) HOUR) AND '$fromDate' GROUP BY FLOOR(TIMESTAMPDIFF(HOUR, created, '$fromDate') / $intervalInHours)";
I'd add another column that is the date(not time) and then use MySQL "group by" to get the most recent for each date.
http://www.tizag.com/mysqlTutorial/mysqlgroupby.php/
This tutorial does just that, but by product type instead of date. This should help!
Do you want all of the events within the 10 days, or just one event per day within the 10 day period?
Either way, consider MySQL's date functions for assistance. It should help you get the date range you want.
Here's one that will get you the first event of the day for the last 10 days.
SELECT *
FROM eventos
WHERE created BETWEEN DATE_SUB(DATE(NOW()), INTERVAL 10 DAY) AND DATE_ADD(DATE(NOW()), INTERVAL 1 DAY)
GROUP BY DATE(created)
ORDER BY MAX(created) DESC
LIMIT 10
Try this:
SELECT *
FROM eventos
WHERE created BETWEEN DATE_SUB(DATE(NOW()), INTERVAL 10 DAY) AND DATE_ADD(DATE(NOW()), INTERVAL 1 DAY)
ORDER BY created DESC
LIMIT 10

SQL Query Concerning Dates

I have the following relation in my schema:
Entries:
entryId(PK) auto_inc
date date
In order to count the total entries in the relation I use a query in my php like this:
$sql = mysql_query("SELECT COUNT(*) as Frequency FROM Entries WHERE date = '$date'");
My question is how can I count the number of entries for the CURRENT month..
You want a between query based on your date column.
WHERE date BETWEEN startdate AND enddate.
Between is equivalent to date >= startdate AND date <= enddate. It would of course be also possible to just use >= AND < explicitly which would simplify it a bit because you don't need to find the last day of the month, but just the first day of the following month using only DATE_ADD(..., INTERVAL 1 MONTH).
However startdate and enddate in this case would be derived from CURDATE().
You can use CURDATE(), MONTH(), DATE_ADD and STR_TO_DATE to derive the dates you need (1st day of current month, last day of current month). This article solves a similar problem and all the techniques needed are shown in examples that you should be able to adapt:
http://www.gizmola.com/blog/archives/107-Calculate-a-persons-age-in-a-MySQL-query.html
The first day of the current month is obvious YEAR-MONTH(CURDATE())-01. The last day you can calculate by using DATE_ADD to add 1 Month to the first day of the current month, then DATE_ADD -1 Days.
update-
Ok, I went and formulated the full query. Don't think str_to_date is really needed to get the index efficiency but didn't actually check.
SELECT count(*)
FROM entries
WHERE `date` BETWEEN
CONCAT(YEAR(CURDATE()), '-', MONTH(CURDATE()), '-', '01')
AND
DATE_ADD(DATE_ADD(CONCAT(YEAR(CURDATE()), '-', MONTH(CURDATE()), '-', '01'), INTERVAL 1 MONTH), INTERVAL -1 DAY);
Try this
SELECT COUNT(1) AS `Frequency`
FROM `Entries`
WHERE EXTRACT(YEAR_MONTH FROM `date`) = EXTRACT(YEAR_MONTH FROM CURDATE())
See EXTRACT() and CURDATE()
Edit: Changed NOW() to CURDATE() as it is more appropriate here
Try
$sql = mysql_query("SELECT COUNT(*) as Frequency FROM Entries WHERE MONTH(date) = MONTH(NOW()) );

How to minimize the load in queries that need grouping with different invervals?

I'm looking for a best practice advice how to speed up queries and at the same time to minimize the overhead needed to invoke date/mktime functions. To trivialize the problem I'm dealing with the following table layout:
CREATE TABLE my_table(
id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT,
important_data INTEGER,
date INTEGER);
The user can choose to show 1) all entries between two dates:
SELECT * FROM my_table
WHERE date >= ? AND date <= ?
ORDER BY date DESC;
Output:
10-21-2009 12:12:12, 10002
10-21-2009 14:12:12, 15002
10-22-2009 14:05:01, 20030
10-23-2009 15:23:35, 300
....
I don't think there is much to improve in this case.
2) Summarize/group the output by day, week, month, year:
SELECT COUNT(*) AS count, SUM(important_data) AS important_data
FROM my_table
WHERE date >= ? AND date <= ?
ORDER BY date DESC;
Example output by month:
10-2009, 100002
11-2009, 200030
12-2009, 3000
01-2010, 0 /* <- very important to show empty dates, with no entries in the table! */
....
To accomplish option 2) I'm currently running a very costly for-loop with mktime/date like the following:
for(...){ /* example for group by day */
$span_from = (int)mktime(0, 0, 0, date("m", $time_min), date("d", $time_min)+$i, date("Y", $time_min));
$span_to = (int)mktime(0, 0, 0, date("m", $time_min), date("d", $time_min)+$i+1, date("Y", $time_min));
$query = "..";
$output = date("m-d-y", ..);
}
What are my ideas so far? Add additional/ redundant columns (INTEGER) for day (20091212), month (200912), week (200942) and year (2009). This way I can get rid of all the unnecessary queries in the for loop. However I'm still facing the problem to very fastly calculate all dates that doesn't have any equivalent in database. One way to simply move the problem could be to let MySQL do the job and simply use one big query (calculate all the dates/use MySQL date functions) with a left join (the data). Would it be wise to let MySQL take the extra load? Anyway I'm reluctant to use all these mktime/date in the for loop. Since I have complete control over the table layout and code even suggestions with major changes are welcome!
Update
Thanks to Greg I came up with the following SQL query. However it still bugs me to use 50 lines of sql statements - build up with php - that maybe could be done faster and more elegantly otherwise:
SELECT * FROM (
SELECT DATE_ADD('2009-01-30', INTERVAL 0 DAY) AS day UNION ALL
SELECT DATE_ADD('2009-01-30', INTERVAL 1 DAY) AS day UNION ALL
SELECT DATE_ADD('2009-01-30', INTERVAL 2 DAY) AS day UNION ALL
SELECT DATE_ADD('2009-01-30', INTERVAL 3 DAY) AS day UNION ALL
......
SELECT DATE_ADD('2009-01-30', INTERVAL 50 DAY) AS day ) AS dates
LEFT JOIN (
SELECT DATE_FORMAT(date, '%Y-%m-%d') AS date, SUM(data) AS data
FROM test
GROUP BY date
) AS results
ON DATE_FORMAT(dates.day, '%Y-%m-%d') = results.date;
You definitely shouldn't be doing a query inside a loop.
You can group like this:
SELECT COUNT(*) AS count, SUM(important_data) AS important_data, DATE_FORMAT('%Y-%m', date) AS month
FROM my_table
WHERE date BETWEEN ? AND ? -- This should be the min and max of the whole range
GROUP BY DATE_FORMAT('%Y-%m', date)
ORDER BY date DESC;
Then pull these into an array keyed by date and loop over your data range as you are doing (that loop should be pretty light on CPU).
Another idea is not to use string inside the query. Transform the string parameter to datetime, on mysql.
STR_TO_DATE(str,format)
http://dev.mysql.com/doc/refman/5.0/en/date-and-time-functions.html

Categories