How is a date compared to a number in the SQL standard - mysql

Does the following have predictable behavior, or is this not defined and up to how the database implements it?
select date '2015-01-02' > 2015;
-- or --
select '2015-01-02' > 2015;
In other words, comparing a date (or string that can be cast to a date) to an integer that is a year.

The two are quite different, because the first compares a date to a number and the second compares a string to a number.
In standard SQL, both would return an error, but the errors would be slightly different. In the first, the error would be something like "date cannot be converted to int". In the second, it would be something like "string cannot be converted to int". Most databases follow the type conversion rules of the standard.
MySQL does very strange things. It will convert a date to a number format of the form YYYYMMDD. So the first expression returns false and the second true:
select date '2015-01-01' = 2015, date '2015-01-01' = 20150101
See here.
I would strongly discourage you from depending on this behavior.
In the realm of strings, this returns true:
select '2015-01-01' = 2015
However, what looks like a similar example returns false:
select '2015.01.01' = 2015
This is because MySQL converts leading characters to a number, until it no longer can.
I would advise you to avoid even thinking about such constructs and not depending on the results of implicit conversion (explicit conversion is a different matter). The following are the best way to determine if a date falls in a particular year:
where year(date) = 2015
where date >= '2015-01-01' and date < '2016-01-01'

It is generally completely up to the database. However, here is a correct way to express your inequalities:
select date '2015-01-02' >= date '2015-01-01' AND
date '2015-01-02' < date '2016-01-01'
The above inequality checks if the '2015-01-02' date occur anywhere in the 2015 calendar year. It is also ANSI compliant, and should work on most SQL databases (though note that not all SQL databases require/use the date keyword when defining date literals).

The only databases (I know of) that would accept as valid expressions like:
date '2015-01-02' > 2015
and
'2015-01-02' > 2015
are MySql and MariaDB because they do implicit Type Conversion in Expression Evaluation.
See the demo.
SQLite would also accept the 2nd expression for the same reason, but not the 1st query because it does not use the keyword date to cast a string to date.
Databases like SQL Server, Oracle or Postgresql need explicit data type conversions (if possible) so that the 2 operands can be compared.

Related

Losy comparison between dates

Is there a way I can make MySQL return 1 instead of 0 for SELECT NOW() = '2016-10-10' without casting (CAST('2016-10-10' AS DATE)) or converting to date (DATE('2016-10-10')).
My real case scenario is a comparison between a DATE and a DATETIME column. I want to JOIN on those columns, but that's possible only if I can make MySQL compare only the date, ignoring the time.
I can't do the cast/convert because that is very expensive ( Slow query performance left joining a view ).
It's not the '2016-10-10' string that you need to cast (since it is a valid date literal), but NOW().
NOW() returns your current timestamp, with hours, minutes and seconds. While '2016-10-10' is interpreted as '2016-10-10 00:00:00'. Which, presumably is not equal to the current time.
So
SELECT DATE(NOW()) = '2016-10-10'
UPD:
I can make MySQL compare only the date, ignoring the time.
For the comparison coldate = coldatetime you can compare on range, like:
coldate <= coldatetime AND coldate + INTERVAL 1 DAY > coldatetime
Depending on your actual case it may or may not be beneficial.

Common date format function for Oracle-sql and Mysql

I need to write the common queries for MySQL and Oracle databases. Problem occurs when I have to put date conditions.
For example: there is one field Txn_date which is in format of '20150116' in MySQL and '16-JAN-2015' in Oracle.
I use date_format(now(),'%Y%m%d') for MySQL and to_char(sysdate,'dd-MON-YYYY') for Oracle.
Is there any common function of way by which I can use the same function in both Oracle and MySQL?
I tried Txn_date in ( date_format(now(),'%Y%m%d') OR to_char(sysdate,'dd-MON-YYYY') ) but did not work because to_char() not recognized in MySQL.
First, in MySQL dates usually have the following format when converted implicitly - 2015-01-16 - rather than 20150116. I think you can do the following in both MySQL and Oracle (it is standard SQL) - I've checked it in Oracle (10g) and it works, and it seems to work in my fiddling with MySQL:
SELECT * FROM mytable
WHERE mydate IN ( DATE '2015-01-16', DATE '2015-01-18' );
The string literal to be converted to DATE has to be of the form yyyy-mm-dd. Now this will work if your dates are dates and don't have a time portion. Now if your dates do have a time portion, then things become more difficult since MySQL uses the DATE() function to get the date portion, while Oracle would use TRUNC(). But you can get around that with judicious use of >= and <, e.g.:
SELECT * FROM mytable
WHERE ( mydate >= DATE '2015-01-16' AND mydate < DATE '2015-01-17' )
OR ( mydate >= DATE '2015-01-18' AND mydate < DATE '2015-01-19' );
Now if you want to use SYSDATE, the best thing to do would be to use the ANSI standard CURRENT_DATE or CURRENT_TIMESTAMP. These can be compared directly with no need for formatting and should work in both MySQL and Oracle. You can also do date arithmetic using INTERVAL, in which case you could try the following:
SELECT * FROM mytable
WHERE mydate > CURRENT_DATE - INTERVAL '1' DAY;
UPDATE I've been doing some thinking about this. The query immediately above doesn't really work if you want to get all the rows that have been entered today. The difficulty is that Oracle recognizes ANSI date literals as dates (that is, with no time portion), but there isn't, as far as I know, an ANSI-standard way of converting a date/time value (which an Oracle DATE is) to a date. That said, both Oracle and MySQL support the EXTRACT() function, so you should be able to do the following to get today's records:
SELECT * FROM mytable
WHERE EXTRACT(YEAR FROM mydate) = EXTRACT(YEAR FROM CURRENT_DATE)
AND EXTRACT(MONTH FROM mydate) = EXTRACT(MONTH FROM CURRENT_DATE)
AND EXTRACT(DAY FROM mydate) = EXTRACT(DAY FROM CURRENT_DATE);
Definitely unwieldy, especially if one has more than one date to consider (which I assume you do since you're using the IN operator), but should work on both platforms. See SQL Fiddle Demo here (MySQL) and here (Oracle).
MySQL and Oracle use different syntax for converting dates to strings - you'll have to use different queries.

Compare two unix time stamps to see if they are on the same date in MySql

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.

MySQL date validity

Does MySQL provide any function which verifies the validity of a date? The DATE function returns NULL upon provision of the invalid date 2013-02-30 for example. However, I am also using STR_TO_DATE simultaneously, which mysteriously stops DATE from working correctly.
SELECT DATE('2013-02-30'); NULL
SELECT STR_TO_DATE('2013-02-30', '%Y-%m-%d'); NOT NULL
SELECT DATE('2013-02-40'); NULL
SELECT STR_TO_DATE('2013-02-40', '%Y-%m-%d'); NULL
SELECT DATE(STR_TO_DATE('2013-02-30', '%Y-%m-%d')); NOT NULL
Why does STR_TO_DATE halt DATE's functionality and is there some workaround to verify if a date is valid when using STR_TO_DATE (which I am obligated to use)?
I have stumbled upon the answer in the meantime: apparently the DATE function skips a few validation checks, when the data type is already that of 'date' (STR_TO_DATE converts strings to date data types). Therefore, converting the date to a string after having parsed it to the correct format with STR_TO_DATE, does the trick:
#valid_date = NOT ISNULL(DATE(CONVERT(STR_TO_DATE('2013-02-29', '%Y-%m-%d'), CHAR))).
It is very difficult to verify if a field is a date because of all the different possible date formats that would need to be taken into account. BUT if you know that the field date formats are one of these:
'yyyy-mm-dd'
'yyyy-mm-dd hh:mm:ss'
'yyyy-mm-dd whatever'
This code will help you:
SELECT count(*) FROM `table`
WHERE DATE(STR_TO_DATE(`column`, '%Y-%m-%d')) IS NOT NULL
AND `column` NOT REGEXP '^[0-9\.]+$'
Basically :
the first condition tells you if is a date, but unfortunately doesn't exclude numbers (ex: DATE(STR_TO_DATE(**1**, '%Y-%m-%d')) = '2001-00-00'
the second ensures that numbers are excluded, which leaves you with dates only that follow the formats above.
If count(*) is >0 then it's a date, if it is 0 it's something else.
Note
This method works for strings of any date format as long as you know in advance what format they follow (which I know is not always the case but still helpful). Just replace the format a priori in STR_TO_DATE
i can't understand your purpose clearly, maybe this is a idea;
SELECT DATE_FORMAT(yourFiled, '%Y-%m-%d') days FROM yourTable GROUP BY days;
this is not null; you can change it. some like
SELECT DATE_FORMAT(yourFiled, '%Y-%m-%d') days FROM yourTable WHERE yourFiled > '2013-9-23 00:00:00' GROUP By days;
Try this:
SELECT DATE(STR_TO_DATE('2013-00-30', '%Y-%m-%d')); --is also NOT NULL

how do I select on date equality in a mySQL query?

I want to select rows from a table given a particular date of record in mysql
SELECT * from TABLENAME WHERE FROM_DATE='06/11/2012'
I am not getting anything useful.
First of all, you should use the standard date format Y-m-d - otherwise you have to make some nasty queries and sorting is a real b*tch.
Using the standard date format you can easily do something like this:
SELECT * FROM tablename WHERE from_date > '2012-06-11'
DATE comparisons are very likely what you want here. If your from_date column has the data type of DATE, then your code should be safe and robust if you do this:
WHERE from_date = STR_TO_DATE('06/11/2012', '%m/%d/%Y')
#Repox pointed out that you might consider putting your date literals in the canonical format '2012-06-11'. That's true, if you can do it. But STR_TO_DATE will do it for you if you need it to. There's a list of the %x conversion items here. http://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_date-format
If you're using DATETIME data types, beware: comparisons are more complex than they seem. DATETIME items are like floating point numbers: if one of them exactly equals another it's only by coincidence. That's because they represent moments (milliseconds) in time, not just days.
Presuming your from_date column has the DATETIME type, you should use
WHERE from_date >= STR_TO_DATE('06/11/2012', '%m/%d/%Y')
AND from_date < STR_TO_DATE('06/11/2012', '%m/%d/%Y') + INTERVAL 1 DAY
This will catch all moments in time on the day you want, up to but not including the first moment of the next day.
If your from_date items are represented as character strings, take the trouble to convert them to DATE or DATETIME data types. Seriously. Your results will be far better.
SELECT * from TABLENAME WHERE FROM_DATE='2012/06/13'
It would be better if you use the DATE() function of mysql
SELECT * FROM tablename WHERE DATE(from_date) > '2012-06-11'
Because, if the datatype of the from_date you set as TIMESTAMP or DATETIME then it won't return the correct results sometimes when you directly use the '>' symbol