Operators on date/time fields - mysql

Is the + and - operator defined in standard sql on date/time types, for example Time, Duration, Date, Datetime, Timestamp. Here is an example from MySQL:
select
date '1983-09-05', time '01:02:03', timestamp '2014-01-01 01:02:03',
date '1983-09-05' + time '01:02:03', timestamp '1983-09-05 01:02:03' + time '01:02:03'
It seems the last three results just give gibberish (in terms of actually giving a meaningful result). Is there a standard in how time-types are supposed to add and subtract or it's undefined behavior and it's suggested to use functions for this kind of stuff?

MySQL doesn't produce gibberish. It just gets confused. The problem is that MySQL is trying to figure out what you mean:
Is + really for numeric addition or for date/times?
Is the constant really a string, date/time, or number?
MySQL makes choices for these that are (perhaps) counterintuitive. For instance, a date/time values such as '2014-01-01 01:02:03' is converted to a number that looks like 20140101010203. This conversion happens implicitly under some circumstances.
Let me illustrate this just with the dates. You might think these are equivalent:
select '2021-01-01' + 40,
date '2021-01-01' + 40,
'2021-01-01' + interval 40 day
And the results are:
2061 20210141 2021-02-10
What is happening? In the first, + is treated a number addition. the first argument is converted to a number -- that is leading digits up to the first non-digit.
In the second, + is treated a number addition as well. The date is converted to a number and it looks like 20,210,101 -- that is YYYYMMDD as an integer.
Finally, the third tells MySQL to do what you intend -- add 40 days.
This has nothing to do with standard SQL, which clearly defines adding and subtracting intervals and the difference of timestamps. These are just the rules that MySQL uses for disambiguating + and - and for converting date/times to numbers.

Related

Time stamp calculation on SQL queries

I'm trying to display the total travel time that each customer in data time format (00:00:00)
But I'm confused to whether I should just do
SEC_TO_TIME(SUM(endtime - starttime))
or
SEC_TO_TIME(SUM(TIME_TO_SEC(endtime - starttime)))
or
SEC_TO_TIME(SUM(TIME_TO_SEC(endtime) - TIME_TO_SEC(starttime))
if endtime and startime is in datetime format
which one should be the right way to do it. I'm getting a different result for first and the second one
1st:
convert endtime and starttime into timestamp using UNIX_TIMESTAMP
2nd:
subtract them
note: the result will be in seconds
(UNIX_TIMESTAMP(endtime) - UNIX_TIMESTAMP(starttime))
to determine how much time did the costumer consume
3rd:
convert the result into time
SEC_TO_TIME( (UNIX_TIMESTAMP(endtime) - UNIX_TIMESTAMP(starttime)) )
so your final query should be like this
SELECT SEC_TO_TIME( (UNIX_TIMESTAMP(endtime) - UNIX_TIMESTAMP(starttime)) ) FROM travel
Of your three options, I would recommend:
SEC_TO_TIME(SUM(TIME_TO_SEC(endtime)
- TIME_TO_SEC(starttime))
First, I think this is clearest on what you want to do:
Convert the date/times to seconds
Add up the seconds
Convert back to a time
Second, MySQL treats date/time values as numbers in a numeric context. This can produce strange results because 2018-09-13 is turned into 20,180,913, and that is not usually what you want.

MySQL 5.6 calculation with datatype time within function returns wrong value - but outside is ok

I use mySQL 5.6 on Windows 7 Pro x64 and have the following problem.
SELECT fee(100, '12:00:00');
returns 500,000 which is obviously not correct.
But
SELECT 100 * '12:00:00'/24;
returns the correct result which is 50.
DROP FUNCTION IF EXISTS fee;
DELIMITER //
CREATE FUNCTION fee(price INT, duration TIME)
RETURNS DECIMAL(15,2)
BEGIN
RETURN price * duration/24;
END //
DELIMITER ;
Have you ever encountered this problem? What is the reason behind it?
Thanks for any hints for solving this.
My guess is that in 100 * '12:00:00'/24 expression '12:00:00' is evaluated as string, not as a time expression, and in '12:00:00'/24 operation the string is converted to a number, so it is executed as 12/24, which gives the expected result.
However, when the fee() function is called, '12:00:00' is passed to a parameter with TIME data type. In the duration/24 operation duration is converted to integer first, then the division is executed. However, select cast(cast('12:00:00' as time) as integer) conversion yields 120000, not 12. 120000/24*100=500000 - this is the output received from the original function. According to mysql documentation on TIME:
Be careful about assigning abbreviated values to a TIME column. MySQL
interprets abbreviated TIME values with colons as time of the day.
That is, '11:12' means '11:12:00', not '00:11:12'. MySQL interprets
abbreviated values without colons using the assumption that the two
rightmost digits represent seconds (that is, as elapsed time rather
than as time of day). For example, you might think of '1112' and 1112
as meaning '11:12:00' (12 minutes after 11 o'clock), but MySQL
interprets them as '00:11:12' (11 minutes, 12 seconds). Similarly,
'12' and 12 are interpreted as '00:00:12'.
Although the documentation describes integer to time conversion, it is safe to assume that time to integer conversion works the same way. I would use price * time_to_sec(duration)/86400 to get the right result.
Thank you all for your helps and comments.
#Shadow, #B98 – you are right. The problem has to do with converting '12:00:00' to its corresponding numeric value.
I searched a lot about how MySQL performs converting time to number in general, however I didn't find anything.
So I started a little bit experimenting on it and this is what I found out about it yet:
The default datatype in MySQL is VARCHAR, so every value/"variable" which has no explicit datatype its datatype is VARCHAR(length of value/variable) as you've correctly guessed, Shadow.
Converting VARCHAR to a numeric datatype works generally like this: take all digits from the left of the string up to the point you find a character except 0-9. If immediately after the digits there is a dot “.”, take the dot as the decimal point and continue searching for decimal digits till the string ends or you find a character except 0-9.
So in short: take from the left of the string what matches the pattern [0-9][.[0-9]] and throw the rest of it away – as you mentioned it, B98. Examples: '12:30:59' = 12; '12whatever30whatever59' = 12; '12.30.59' = 12.30; '12.30whatever' = 12.30
However, converting TIME to a numeric datatype works a little bit different: First remove the colons then convert it to an integer. Exempels: '12:00:00' = 120000; '12:30:59' = 123059
Converting DATETIME to a numeric datatype works the same way as converting TIME to numeric, except here get the dashes in the date part, the space between date and time and the colons in the time part removed and then gets the whole string converted to an integer. Exempels: '2015-12-24 12:59:59' = '20151224125959'
Below you find a query which shows this behavior of MySQL.
DROP VIEW IF EXISTS datetimeTypes;
CREATE VIEW datetimeTypes AS
SELECT
'12:59:00.50' AS timeImplicit,
CAST('12:59:00.50' AS TIME) AS timeExplicit,
'12:59:00.50' / 1 AS timeImplicitDiv,
CAST('12:59:00.50' AS TIME) / 1 AS timeExplicitDiv,
'2015-12-24 12:59:59' AS datetimeImplicit,
CAST('2015-12-24 12:59:59' AS DATETIME) AS datetimeExplicit,
'2015-12-24 12:59:59' / 1 AS datetimeImplicitDiv,
CAST('2015-12-24 12:59:59' AS DATETIME)/1 AS datetimeExplicitDiv;
SHOW FIELDS FROM datetimeTypes;
SELECT * FROM datetimeTypes;

MySQL and datetime

If I have a table with a DATETIME column I can insert dates that have a format like:
2015-03-25 10:10:10
2015-03-25 10:10
2015-03-25 10
2015-03-25
It will fill in the remainder with zeros. I can't however use
2015-03
2015
As it will give an 'Incorrect datetime value' error. It is however possible to use these last two in a SELECT like [..] WHERE timestamp < '2015-03' ..
Is there a way that MySQL will fill in the remainder of datetimes with 01-01 for the month and day if omitted in datetimes or do I have to do that manually myself?
I.e. I would like to use '2015-03' in an INSERT statement, or do something like SELECT DATE_FORMAT('2015-03', '%Y%m%dT%H%i%S')
As stated in Date and Time Literals:
MySQL recognizes DATE values in these formats:
As a string in either 'YYYY-MM-DD' or 'YY-MM-DD' format. A “relaxed” syntax is permitted: Any punctuation character may be used as the delimiter between date parts. For example, '2012-12-31', '2012/12/31', '2012^12^31', and '2012#12#31' are equivalent.
As a string with no delimiters in either 'YYYYMMDD' or 'YYMMDD' format, provided that the string makes sense as a date. For example, '20070523' and '070523' are interpreted as '2007-05-23', but '071332' is illegal (it has nonsensical month and day parts) and becomes '0000-00-00'.
As a number in either YYYYMMDD or YYMMDD format, provided that the number makes sense as a date. For example, 19830905 and 830905 are interpreted as '1983-09-05'.
MySQL recognizes DATETIME and TIMESTAMP values in these formats:
As a string in either 'YYYY-MM-DD HH:MM:SS' or 'YY-MM-DD HH:MM:SS' format. A “relaxed” syntax is permitted here, too: Any punctuation character may be used as the delimiter between date parts or time parts. For example, '2012-12-31 11:30:45', '2012^12^31 11+30+45', '2012/12/31 11*30*45', and '2012#12#31 11^30^45' are equivalent.
The only delimiter recognized between a date and time part and a fractional seconds part is the decimal point.
The date and time parts can be separated by T rather than a space. For example, '2012-12-31 11:30:45' '2012-12-31T11:30:45' are equivalent.
As a string with no delimiters in either 'YYYYMMDDHHMMSS' or 'YYMMDDHHMMSS' format, provided that the string makes sense as a date. For example, '20070523091528' and '070523091528' are interpreted as '2007-05-23 09:15:28', but '071122129015' is illegal (it has a nonsensical minute part) and becomes '0000-00-00 00:00:00'.
As a number in either YYYYMMDDHHMMSS or YYMMDDHHMMSS format, provided that the number makes sense as a date. For example, 19830905132800 and 830905132800 are interpreted as '1983-09-05 13:28:00'.
Notably, MySQL does not support the incomplete formats that you wish to use.
That MySQL happens to accept some of the incomplete formats you've tried (apparently by padding with zeroes) is undocumented behaviour, quite possibly unintended by the developers. It cannot (and should not) be relied upon, not least because edge cases could exist under which the behaviour breaks; or because the behaviour could be changed without warning in a future release.
If it's absolutely necessary to provide such incomplete temporal literals to MySQL (which it shouldn't be, as your data access layer ought to be aware of the type of values it is handling and provide them to MySQL in a supported format), you can use its STR_TO_DATE() function to parse them accordingly:
Unspecified date or time parts have a value of 0, so incompletely specified values in str produce a result with some or all parts set to 0:
mysql> SELECT STR_TO_DATE('abc','abc');
-> '0000-00-00'
mysql> SELECT STR_TO_DATE('9','%m');
-> '0000-09-00'
mysql> SELECT STR_TO_DATE('9','%s');
-> '00:00:09'
Range checking on the parts of date values is as described in Section 11.3.1, “The DATE, DATETIME, and TIMESTAMP Types”. This means, for example, that “zero” dates or dates with part values of 0 are permitted unless the SQL mode is set to disallow such values.
So, for example, you might use:
STR_TO_DATE('2015-03', '%Y-%m');
Try unix_timestamp()
SELECT like [..] WHERE unix_timestamp(timestamp) < '2015-03'

MySQL SEC_TO_TIME gives hh:mm:ss.000000

As said in the caption I am wondering why the SEC_TO_TIME-Function of MySQL gives me that Zeros at the end.
Refering to the docu (http://dev.mysql.com/doc/refman/5.0/en/date-and-time-functions.html#function_sec-to-time) that shouldn't happen (I am using MySQL 5.0.11).
Any Idea why this Zeros appears and how to get rid of them? To much zeros for displaying miliseconds.
Sine the zeros doens't break MySQLs Date-funcions, it's more a "I don't like that"-Question rather than a real Problem (at least till now^^)
// EDIT: I just figured out that the zeros aren't coming from the SEC_TO_TIME but from the FROM_UNIXTIME()-Function. Thx to #Abhik Chakraborty to ask for the input!
// EDIT2: I used FROM_UNIXTIME(last_try, '%Y-%m-%d %H:%i:%s') to get rid of the zeros. But When I do TIME(FROM_UNIXTIME(last_try, '%Y-%m-%d %H:%i:%s')) the zeros are back. Why??
Seems like every function adds the zeros back. Using SEC_TO_TIME on a simple integer-value also gives zeros...
Here is the whole query iam using:
SELECT
SEC_TO_TIME(FLOOR(TIME_TO_SEC((TIME(FROM_UNIXTIME(`last_try`))))/1800)*1800)
FROM `last48h`
The query reads the timestamp, gets only the time, converts it to seconds, breaks the seconds into half-hours (/1800 gives 0 < x < 48) rounds down and converts back to time
SEC_TO_TIME produces a TIME data type for its result. You can format that as you wish with DATE_FORMAT.
If you actually need subsecond time resolution you'll need to move to version 5.6.4 or beyond.
When you directly SELECT any sort of TIME data type to display, you get a default TIME-to-string conversion operation. The default TIME-to-string conversion in some generations of MySQL yields a string ending in hh:mm:ss+zz00. +zz00 is a timezone indicator, and often displays as +0000. Any chance that's what you're seeing?
It doesn't make sense to try to handle a UNIX_TIMESTAMP() style number of seconds using SEC_TO_TIME(). As of mid-2014 the current unix timestamp value is above 1.39 gigaseconds. TIME data types are used for stuff like elapsed times, and have a limit of just under 839 hours (3 megaseconds, precisely 3020399 seconds), and silently truncate their values.
For example, this is a good use of SEC_TO_TIME:
SELECT SEC_TO_TIME(end_timestamp - start_timestamp) AS duration
edit
Strangely enough, this query
SELECT
SEC_TO_TIME(FLOOR(TIME_TO_SEC((TIME(FROM_UNIXTIME(UNIX_TIMESTAMP()))))/1800)*1800) AS a,
FLOOR(TIME_TO_SEC((TIME(FROM_UNIXTIME(UNIX_TIMESTAMP()))))/1800)*1800 AS b,
TIME_TO_SEC((TIME(FROM_UNIXTIME(UNIX_TIMESTAMP()))))/1800 AS c,
FROM_UNIXTIME(UNIX_TIMESTAMP()) AS d,
FROM_UNIXTIME(UNIX_TIMESTAMP() - UNIX_TIMESTAMP() % 1800) as e
doesn't show any of the 0000 stuff through the phpmyadmin instance I use.
By the way, most people who round time to the nearest interval (a half-hour in your case) prefer to use a modulo and a subtraction; it's less dependent on implicit numerical type conversion than your method.
SELECT TIME(FROM_UNIXTIME(last_try - last_try%1800))
does what the query in your question does.
I had the same problem with the 'SEC_TO_TIME' function.
I had overlooked the fact that I was storing timestamps as a VARCHAR.
I changed my datatypes from VARCHAR to BIGINT and it is formatting the output values as expected (hh:mm:ss).
Try to use TIME_FORMAT with %k specifier it should help.

why this MySQL SELECT doesn't include the right dates?

The main problem is, that I have stored in database datetime , not the date (what I need). Ok never mind.
I have thousands of reports stored each day.
I need to LEFT by 10 my datetime_view (to cut the time) and everything's fine. Except this. I'm trying to figure out why do I have to put in the condition + one day from the future? Otherwise it won't search what I want.
SELECT
LEFT(datetime_view,10),
count(type)
FROM reports
WHERE
type IN (1,2,5)
AND
datetime_view>='2012-10-28'
AND
datetime_view<='2012-11-04'
group by LEFT(datetime_view,10);
You can see I must search from the future. Why??
It gives me an output from 28.10 to 3.11 ....
don't use string operations on date/time values. MySQL has a huge set of functions for date/time manipulation. Try
GROUP BY DATE(datetime_view)
instead, which will extract only the date portion of the datetime field. Your string operation is not y10k compliant. Using the date() function is.
As for your plus one day, consider how the comparisons are done: A plain date value, when used in date/time comparisons, has an implicit 00:00:00 time value attached to it, e.g. all dates have a time of "midnight".
i think it's better to use DATE(datetime_view) to cut the time instead of LEFT(datetime_view,10), also on the where condition:
DATE(datetime_view) <= '2012-11-03'