I have a table with the following columns:
|start_date |TZ |
|Dec 2, 2012 |Eastern |
|Dec 2, 2012 |GMT |
Note 1: our server is in UTC time.
Note 2:The column start_date is a date field, not a timestamp field. Dec 2nd 2012 implicitly means "2012-12-02 00:00:00"
Note 3: The above table is actually multiple normalized tables, but for simplicity, I de-normalized it.
Note 4: I can put anything into the TZ table to make this easy.
I would like to select from my_table where start_date <= now()
However, this doesn't work because of timezone. If the current date/time is
Dec 1st Eastern at 9PM (which is Dec 2nd 1AM UTC), the above query will return both results,
but I really only want the 2nd one. This is further complicated by daylight savings.
Ideally, I would like a query that does the following:
select * from my_table where convert_to_utc_timestamp(start_date,tz) <= now()
The above method would convert start_date to a timestamp and then convert it to the right timezone.
How would I do this in SQL?
There are two functions you'll probably find useful.
The first is:
STR_TO_DATE(start_date,'%M %d,%Y')
That will get your string, in the specified format, converted to a MySQL DATE datatype.
If you have the mysql.time_zone_name et al. tables populated, you can use the function:
CONVERT_TZ()
(need to check that CONVERT_TZ takes a DATE and will return a DATETIME or TIMESTAMP, or include a time component in the string being converted to get a DATETIME, e.g.
STR_TO_DATE( CONCAT(start_date,' 00:00:00'),'%M %d,%Y %T')
Wrap that expression in the CONVERT_TZ() function, e.g.
CONVERT_TZ( datetime_expr ,'US/Eastern','GMT')
To make use of the values stored in your TZ column, those are going to need to match, or you need to come up with a way to match to, the values stored in the mysql.time_zone_name table.
Related
In my sales table have the date column and the date is formatted like this January 9, 2018, 5:06 pm. How can I specifically get all the sales made from the month of January 2018 using MySQL?
assuming you date column is a date data type columns you can use year() and month() function
select * from my_table
where year(my_date_col) = 2018
and month(my_date_col) = 1
Since
the date is formatted like this January 9, 2018, 5:06 pm
and this is not recognizable time format for MySQL I belive the solution is:
SELECT * FROM table WHERE date like 'January%2018%';
I know this is not a "best practice" as well as storing time in a wrong type.
Since the column isn't already in a DATE format, let's assume it has to stay in its current format for the application purposes and add an auxiliary DATE column to select on (because SELECTs will be more optimized this way).
First we add a VIRTUAL column (requires MySQL 5.7+), which is a calculated column that is calculated at runtime but not stored on disk. We don't need to store this column on disk because we're going to index it later. Our VIRTUAL column will convert the application date's format to a MySQL DATE.
Replace str_date with your current date column:
ALTER TABLE `my_table`
ADD `virtual_date` DATETIME AS (STR_TO_DATE(`str_date`, '%M %d, %Y, %l:%i %p')) VIRTUAL AFTER `date`;
Now add the INDEX:
ALTER TABLE my_table ADD INDEX (`virtual_date`);
Now we can perform a properly indexed SELECT:
SELECT *
FROM my_table
WHERE virtual_date >= '2018-01-01'
AND virtual_date <= '2018-01-31'
or
SELECT *
FROM my_table
WHERE virtual_date >= '2018-01-01'
AND virtual_date <= LAST_DAY('2018-01-01')
The MySQL DATE format is YYYY-MM-DD. The latter query is logically simpler; the LAST_DAY() function will give you the last DATE of the month given a DATE or DATETIME value.
I have two unix timestamps 65 seconds apart and i am trying to query mysql in this manner
This is 65 seconds apart 1504684252 + 65
SELECT ask FROM live_rates WHERE the_time BETWEEN
UNIX_TIMESTAMP(1504684252) AND UNIX_TIMESTAMP(1504684317)
SELECT ask FROM live_rates WHERE the_time BETWEEN
FROM_UNIXTIME(1504684252) AND FROM_UNIXTIME(1504684317)
In my table, there is an event starting at timestamp 1504684252 and ending 65 seconds later. Why is there no data returned by either of the queries?.
Your question doesn't really make it clear what data type the_time is - if it's a unix timestamp (i.e. an integer) already, just query it as an integer, given that you seem to know the unix timestamps of your date range:
SELECT ask FROM live_rates WHERE the_time BETWEEN 1504684252 AND 1504684317
If your data in the_time is stored as a datetime, I'd say you should make sure that the server really is converting the values you've supplied to a datetime range that includes the relevant record:
SELECT ask FROM live_rates WHERE id = abcdef
SELECT FROM_UNIXTIME(1504684252), FROM_UNIXTIME(1504684317)
Run these two queries (with the id replaced) and eyeball the data. You'll find the reason, I'm sure - possibly something like a timezone issue, and your unix timestamps are translating to some time in UTC whereas your table data is showing some other time and you've hence mis-translated the time shown into a unix time.. Or possibly that it's an end date that has some time component outside the range of the unix times you specified even though the date part of it is correct.
It's hard to say for sure without a complete, verifiable example (a create table statement with inserted test data and a select demonstrating the problem. Try sqlfiddle.com)
ps; Don't use UNIX_TIMESTAMP() in the way you wrote here; it's not intended to have a unix timestamp passed into it. You either pass it no arguments (in which case it gives you the current datetime of the server clock, as a unix timestamp) or you pass it some datetime (and it will return you the equivalent unix timestamp of that datetime)
If the_time is a unix timestamp as opposed to a date i.e. 1504684252 instead of 2017-09-01 00:00:00, then you don't need FROM_UNIXTIME, which converts a unix timestamp to a date:
SELECT ask FROM live_rates WHERE the_time BETWEEN 1504684252 AND 1504684317
UNIX_TIMESTAMP takes a date/time as parameter and returns an integer. When you're feeding it with an integer it returns 0 - so you're selecting between 0 and 0.
FROM_UNIXTIME takes an integer but it returns a date/time - so if the_time is an integer they're not compatible.
I need to modify the output of a datetime field so that it returns the first day of the week in which that date falls. For example, if the date is 9/2/2016, it should return 8/29/2016 because that's the Monday of that week (I want Monday, not Sunday). However, I also need to convert the timestamp to a different timezone. The result is that I end up having to convert the timezone twice:
CONVERT_TZ(timestamp, '+00:00', '+05:00') - INTERVAL WEEKDAY(CONVERT_TZ(timestamp, '+00:00', '+05:00')) day
I can't simply perform the INTERVAL calculation on the UTC datetime and then convert the timezone on the result because the timezone conversion may affect the result of the WEEKDAY function, e.g. a datetime of 2016-9-5 00:00 UTC will actually fall on 9/4 for EST, thus causing it to be part of a different week.
Is there a way to avoid having to make two calls to CONVERT_TZ in the SELECT statement?
You can put it into a subquery.
SELECT converted - INTERVAL WEEKDAY(converted) AS day
FROM (SELECT CONVERT_TZ(timestamp, '+00:00', '+05:00') AS converted
FROM yourTable) AS x
Another way is to assign a user variable. To be able to use it twice, put the assignment into the condition part of an IF() expression -- that ensures that the assignment will be done before the uses.
IF(#converted := CONVERT_TZ(timestamp, '+00:00', '+05:00'),
#converted - INTERVAL(WEEKDAY(#converted) DAY,
NULL) AS day
I have a database(table), in which 2 fields are:
fromdate varchar(20)
todate varchar(20)
Dates are stored in this fashion:
YYYY-MM-DD HH:mm:ss
For ex: '2014-10-30 10:10:10' in database.
Now I want to compare two dates and fetch records from database by using query, 2014-09-10 10:10:10(fromdate) to 2014-10-10 10:10:10(todate)
How to fetch all accurate records.. Is there any kind of solution..
Thanks.
Just compare the string without extra overhead.
This format "YYYY-MM-DD HH:mm:ss" shares chronological and literal alphabetical order
SELECT * FROM someTable
WHERE fromdate >= '2014-09-10 10:10:10' AND todate <= '2014-10-10 10:10:10'
Also, I would create an index on those columns.
i have a database(table), in which 2 fields are: fromdate varchar(20)
todate varchar(20)
It is a design flaw. Date should always be a DATE data type and never be string.
dates are stored in this fashion YYYY-MM-DD HH:mm:ss
DATE is never stored in any format. it is for us, human beings to understand.
Oracle stores DATE in total of 7 bytes. Each byte in it stores values for an element of the DATE as follows:
Byte Description
---- -------------------------------------------------
1 Century value but before storing it add 100 to it
2 Year and 100 is added to it before storing
3 Month
4 Day of the month
5 Hours but add 1 before storing it
6 Minutes but add 1 before storing it
7 Seconds but add 1 before storing it
for eg :"2014-10-30 10:10:10" in database.
Now i want to compare two dates and fetch records from database by
using query, 2014-09-10 10:10:10(fromdate) to 2014-10-10
10:10:10(todate)
Just use to_date('2014-10-30 10:10:10', 'YYYY-MM-DD HH24:MI:SS')
NOTE This is for Oracle database. I see you have tagged SQL Server too. I don't understand why did you do that.
Use STR_TO_DATE()
select * from your_table
where str_to_date(fromdate, '%Y-%m-%d %H:%i:%s') >= '2014-09-10 10:10:10'
and str_to_date(todate, '%Y-%m-%d %H:%i:%s') <= '2014-10-10 10:10:10'
First, you can use convert function:
SELECT CONVERT(Datetime, '2014-10-30 18:00:00', 120)
Second, if you can't change the existing columns and their type, it does not mean that you can't add additional column with correct date type that duplicates the meaning of "wrong" column. This would both save your legacy code and help you in new development, as all the operations with convertation in queries are very expensive in terms of performance.
I have read various questions here on Stackoverflow about the use of FROM_UNIXTIME but none directly deal with what I am trying to do. I have one timestamp in a variable coming from php (that has been reformatted - e.g. 25 March 2014) to a function which uses a database query to determine if there are other entries in the database that have the same date (not time). I've run across various methods for formatting and comparing timestamp entries using MySql and ended up with the following but I understand that it isn't very efficient. Does anyone know of a better way to accomplish this?
FROM_UNIXTIME(sd.timestart, "%e %M %Y") = ?'
where the variable in my array for comparison is the date format listed above. This accomplishes what I want but, again, I don't think it is the most efficient way to get this done. Any advice and/or ideas will be much appreciated.
*EDIT*
My timestamp is stored as an integer so I'm trying to use:
$thissessiondate = strtotime($date->timestart, strtotime('today'));
and
$tomorrowdate = strtotime($date->timestart, strtotime('tomorrow'));
to do trim to midnight but get an error (strtotime() expects parameter 2 to be long) and when I move 'today' to the first argument position, I get a conversion to 11 pm instead of 0:00...? I'm making some progress but my very incomplete knowledge of both PHP and MySQL are holding me back.
If you can avoid it, don't wrap columns used in predicates in expressions.
Have your predicates on bare columns to make index range scans possible. You want the datatype conversion to happen over on the literal side of the predicate, wherever possible.
The STR_TO_DATE function is the most convenient for this.
Assuming the timestart column is DATE, DATETIME or TIMESTAMP (which it really should be, if it represents a point in time.)
WHERE sd.timestart >= STR_TO_DATE( ? , "%e %M %Y")
AND sd.timestart < STR_TO_DATE( ? , "%e %M %Y") + INTERVAL 1 DAY
Effectively, what that's doing is taking the string passed in as the first argument to the STR_TO_DATE function, MySQL is going to convert that string to a DATETIME, based on the format specified as the second argument. And that effectively becomes a literal that MySQL can use to compare to the stored values in the column.
If there's an appropriate index available, MySQL will consider an index range scan operation to satisfy that predicate.
You'd need to pass in the same value twice, but that's not really a problem.
On the second line, we're just adding a day to the same value. So what MySQL is seeing is this:
WHERE sd.timestart >= STR_TO_DATE( '25 March 2014' , "%e %M %Y")
AND sd.timestart < STR_TO_DATE( '25 March 2014' , "%e %M %Y") + INTERVAL 1 DAY
In terms of performance, that's equivalent to:
WHERE sd.timestart >= '2014-03-15 00:00:00'
AND sd.timestart < '2014-03-16 00:00:00'
If you do it the other way around, and wrap timestart in a function, that's going to require MySQL to evaluate the function on every single row (or at least, on every row that isn't filtered out by another predicate first.)
IMPORANT NOTE
Be aware that MySQL interprets datetime values as being in the timezone of the MySQL connection, which defaults to the timezone setting of the MySQL server. MySQL is going to interpret datetime literals in the current setting of the timezone. For example, if MySQL timezone is set to +00:00, then datetime literals will be interpreted as UTC.
I assumed the format string matches the data being passed in, I don't use %e or %m. The %Y is a four digit year. (The list of format elements is in the MySQL documentation, under the DATE_FORMAT function.
Reference: http://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_str-to-date
If your timestart column is INTEGER or other numeric datatype, representing a number of seconds or some other unit of time since the beginning of an era, you can use the same approach for performance benefits.
In the predicate, reference bare columns from the table, and do any conversions required on the literal side.
If you aren't using MySQL functions to do the conversion to "seconds since Jan 1, 1970 UTC" when rows are inserted (which is really what the TIMESTAMP datatype is doing internally), then I wouldn't recommend using MySQL functions to do the conversion in the query either.
If you're doing the conversion from date and time to an integer type "timestamp" in PHP, then I'd do the inverse conversion in PHP as well, and do the trimming to midnight and the adding of a day in PHP.
In that case, your MySQL query would be of the simple form:
WHERE sd.timestart >= ?
AND sd.timestart < ?
Where you would pass in the appropriate integer values, to compare to the INTEGER timestamp column.
Note that MySQL does provide a function for converting to "seconds since Jan 1 1970 UTC", so if timestart is seconds since Jan 1 1970 UTC, then something like this is valid:
WHERE sd.timestart >= UNIX_TIMESTAMP(STR_TO_DATE( '25 March 2014' , "%e %M %Y"))
AND sd.timestart < UNIX_TIMESTAMP(STR_TO_DATE( '25 March 2014' , "%e %M %Y") + INTERVAL 1 DAY)
BUT... again, be aware of timezone conversion issues; if the MySQL database has a different timezone setting than the web server. If you are going to store "integer", then I wouldn't muck that up with the conversion that MySQL does, which may not be exactly the same as the conversion functions the web server does.
If you store your date as an int timestamp, you can do this
round(sd.timestart/86400)=round(UNIX_TIMESTAMP(NOW())/86400)
This will get everything in your database that is from the same day.
For example:
SELECT id FROM uploads WHERE (approved=0 OR approved is NULL) AND round(uploads.date/86400)<=round(UNIX_TIMESTAMP(NOW())/86400) order by uploads.date DESC LIMIT 20
Will display all the uploads for today and the days before, without showing the future uploads. 86400 is the number of seconds in one day.