Running the following statement, MySQL seems to mix things up:
select now(), if(false, date(now()), time(now()));
| 2013-07-24 10:06:21 | 2010-06-21 00:00:00 |
If replacing the second argument of the if with a literal string, the statement behaves correctly:
select now(), if(false, 'Banana', time(now()));
| 2013-07-24 10:06:21 | 10:06:21 |
Is this a bug or some really strange quirk?
The return type of IF has to be a datatype that includes the types of both arguments. So if one of the arguments is a DATE and the other is a TIME, the type of IF will be DATETIME.
This doesn't seem necessary in the trivial example query, but consider something like:
SELECT IF(col1, date(col2), time(col2)) AS dt
FROM Table
All the rows of the result have to have the same datatype in the dt column, even though the specific data will depend on what's in that row.
If you want just the date or time, convert it to a string.
Related
I am parsing a json array and one field I am pulling out is closedate. However closedate has two different date formats one is YYYY-MM-DD and the other is a 13 digit timestamp. I am trying to get consistent formatting of the dates as well as have it be an integer compared to a string. Right now the query returning the close date is:
json_array_elements(ld.data->'Table1'->'Details')->>'closeDate' as closedate
and it returns close date as a string:
id
closedate
1
2021-09-29
2
1606824000000
Someone was telling me to do something like a case statement with regex. But I am not familiar with regex function. Any help is appreciated.
Edit: I have
case when x.closedate::text ~* '^[0-9]{13}$' then
to_timestamp(x.closedate::bigint/1000)
when x.closedate = '0' then null
when x.closedate = '' then null
else
to_date(x.closedate,'MMDDYYYY') end as transactionclosedate
the case statement works for converting the 13 digit timestamp to a date but I am getting the error:
ERROR: date/time field value out of range: "2020-10-23"
when trying to convert the date strings in the correct format to dates in the else part of the case statement.
An example of one way to make this work. My regex skills are not strong so others may have a better solution:
create table regex_test (id int, fld_1 varchar);
insert into regex_test values (1, '1606824000000'), (2, '2021-09-29');
select * from regex_test ;
id | fld_1
----+---------------
1 | 1606824000000
2 | 2021-09-29
select
id,
case when fld_1 ~* '^[0-9]*$' then
to_timestamp(fld_1::bigint/1000)
else
fld_1::timestamp end as ts_fld
from
regex_test;
id | ts_fld
----+------------------------
1 | 2020-12-01 04:00:00-08
2 | 2021-09-29 00:00:00-07
I hope this query help you
with data as (
select
json_array_elements(data->'Table1'->'Details')->>'closeDate' as closedate
from your_table
)
select
case when closedate::text ~ '^[0-9]+$' then
to_timestamp(closedate::numeric / 1000)::date
else
closedate::date
end
from data;
Either of the other answers would be ok providing that only the specified formats exist. However, containing those formats requires a text field; which may contain anything. It is dangerous to assume if the content is not 13 digits then it is a valid formatted ISO date. I would validate that as well (and verify digits length).
select id,
, case when closedate ~* '^[0-9]{13}$' then
to_timestamp(closedate::bigint/1000)
when is_valid_iso_date(closedate) then
closedate::timestamp
else
'-infinity'::timestamp -- or whatever to indicate Invalid Date.
from <your table> ;
The problem being that is_valid_iso_date function. It turns out however I had to create just that a couple years ago, I'll make the result available here.
DISCLAIMER: While the function has given no known erroneous results it has NOT been exhaustively tested.
Lets say, I have a table:
+------------+-----------+------+-----+-------------------+-----------------------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-----------+------+-----+-------------------+-----------------------------+
| id | int(10) | NO | PRI | | AUTOINCREMENT |
| id_action | int(10) | NO | IDX | | |
| a_date | date | NO | IDX | | |
| a_datetime | datetime | NO | IDX | | |
+------------+-----------+------+-----+-------------------+-----------------------------+
Each row has some id_action, and the a_date and a_datetime when it was executed on the website.
My question is, when I want to return COUNT() of each id_action grouped by a_date, is it same, when I use this two selects, or they are different in speed? Thanks for any explanation.
SELECT COUNT(id_action), id_action, a_date
FROM my_table
GROUP BY a_date
ORDER BY a_date DESC
and
SELECT COUNT(id_action), id_action, DATE_FORMAT(a_datetime, '%Y-%m-%d') AS `a_date`
FROM my_table
GROUP BY DATE_FORMAT(a_datetime, '%Y-%m-%d')
ORDER BY a_date DESC
In other words, my question is, that each action has its datetime, and if I really need column a_date, or it is the same using DATE_FORMAT function and column a_datetime and I dont need column a_date?
I ran both the queries on similar table on MySQL 5.5.
The table has 10634079 rows.
First one took 10.66 initially and always takes approx 10 secs on further attempts.
Seconds Query takes 1.25 mins to execute first time, on second, 3rd.... attempts its taking 22.091 secs
So in my view, if your are looking for performance, then you must have column a_date, as its taking half of the time when executed without Date_Format.
If performance is not the primay concern (like data redundancy can be) then a_datetime column will serve all other date/datetime related purposes.
DATE : The DATE type is used for values with a date part but no time part.
DATETIME: The DATETIME type is used for values that contain both date and time parts.
so if you have DATETIME you can always derive DATE from it but from DATE you can not get DATETIME.
And as per your sql there will not be a major difference.
It will be better not to have a_date because you already have a_datetime.
but in general if you can use TIMESTAMP you should, because it is more space-efficient than DATETIME.
Using a_date to group by day will be more efficient than a_datetime because of your conversion. In T-SQL I use a combination of DATEADD() and DATEDIFF() to get the date only from DATETIME since math is more efficient than data conversion. For example (again, using T-SQL though I'm sure there's something similar for MySQL):
SELECT COUNT(id_action), id_action,
DATEADD(DD,DATEDIFF(DD,0,a_datetime),0) AS [a_date]
FROM my_table
GROUP BY DATEADD(DD,DATEDIFF(DD,0,a_datetime),0) AS [a_date]
ORDER BY a_date DESC
This will find the number of days between day 0 and a_datetime then add that number of days to day 0 again. (Day 0 is just an arbitrary date chosen for it's simplicity.)
Perhaps the MySQL version of that would be:
DATE_ADD('2014-01-01', INTERVAL DATEDIFF('2014-01-01',a_datetime) DAY)
Sorry I don't have MySQL installed or I would try that myself. I'd expect it to be more efficient than casting/formatting but less efficient than using a_date.
If you are doing a function in your group by clause: "GROUP BY DATE_FORMAT(a_datetime, '%Y-%m-%d')", you will not be leveraging your index: "a_datetime".
As for speed, I believe there will be no noticeable difference between indexing on datetime vs date (but it's always easy to test with 'explain')
Lastly, you can always read a datetime as a date (using cast functions if need be). Your schema is not normalized if you have both a a_date and a_datetime. You should consider removing one of them. If date provides enough granularity for your application, then get rid of datetime. Otherwise, get rid of a_date and cast as required
As already mentioned, the performance of any function(o_datetime) will be worse than just a_date. The choice depends on on your needs, if there is no need to DATETIME, take a DATE and that is.
If you still need to find a function to convert, then I advise you to take a date().
See also How to cast DATETIME as a DATE in mysql?
Put the two statements in editor SQL and execute (CTRL-L) statistics.
https://technet.microsoft.com/en-us/library/ms178071%28v=sql.105%29.aspx
https://msdn.microsoft.com/pt-br/library/ms190287.aspx?f=255&MSPPError=-2147217396
Im trying to figure out a way to fix a database schema issue.
In column 1 a y-m-d H:i:s date is stored (timestamp field)
col1 = 2009-11-12 00:00:00
In column 2 a time is stored (varchar)
col2 = 15:48
I'm thinking that storing it in one column would be more efficient than separately, so I'm trying to make column 3 a datetime field
col3 = 2009-11-12 15:48:00
Unless keeping it original is fine.
Yes, definitely use one field, you can get just the date or time from it later if you need. I believe you can run the following query to update col3 with the correct datetimes.
UPDATE tablename
SET col3 = CAST(LEFT(col1, 10) + " " + col2 + ":00", DATETIME)
If you don't have anything accessing these old fields (col1 and col2), you should get rid of them for clarity. If you do, it is going to be tricky decided whether or not to maintain two fields for the same data.
Addtime should do what you need
mysql> select addtime('2012-05-05 00:00:00', '11:12');
+-----------------------------------------+
| addtime('2012-05-05 00:00:00', '11:12') |
+-----------------------------------------+
| 2012-05-05 11:12:00 |
+-----------------------------------------+
1 row in set (0.00 sec)
Contrary to the other answers... I wouldn't immediately suggest combining these columns.
Consider how the columns are going to be queried - in my experience, efficient queries are more important than disk space efficiency - as such, if you're want to select rows based on date (ignoring time) and/or time (ignoring date) you would want these in separate columns. Whilst you can get the date from a datetime column, if you have lots of rows, doing that on each row before running a query would be really inefficient. (For example... consider this SO question)
How to find out average timestamp the field timestamp in a table gettime
Timestamp
2010-02-08 14:17:36 | 127.0.0.1 |
2010-02-08 14:17:30 | 127.0.0.1 |
2010-02-08 14:17:30 | 127.0.0.1 |
The following query gives some number how to format it and get it in seconds.
select avg(timestamp) from gettime;
the above gives some random number .How to format this
From Overview of Date and Time Types
The SUM() and AVG() aggregate
functions do not work with temporal
values. (They convert the values to
numbers, which loses the part after
the first nonnumeric character.) To
work around this problem, you can
convert to numeric units, perform the
aggregate operation, and convert back
to a temporal value.
Examples:
SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(time_col)))
FROM tbl_name;
SELECT FROM_DAYS(SUM(TO_DAYS(date_col))) FROM
tbl_name;
What's the best way to store a date value for which in many cases only the year may be known?
MySQL allows zeros in date parts unless the NO_ZEROES_IN_DATE sql mode is enabled, which isn't by default. Is there any reason not to use a date field where if the month and day may be zero, or to split it up to 3 different fields for year, month and day (year(4), tinyint, tinyint)?
A better way is to split the date into 3 fields. Year, Month, Day. This gives you full flexibility for storing, sorting, and searching.
Also, it's pretty trivial to put the fields back together into a real date field when necessary.
Finally, it's portable across DBMS's. I don't think anyone else supports a 0 as a valid part of a date value.
Unless portability across DBMS is important, I would definitely be inclined to use a single date field. If you require even moderately complex date related queries, having your day, month and year values in separate fields will become a chore.
MySQL has a wealth of date related functions - http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html. Use YEAR(yourdatefield) if you want to return just the year value, or the same if you want to include it in your query's WHERE clause.
You can use a single date field in Mysql to do this. In the example below field has the date data type.
mysql> select * from test;
+------------+------+
| field | id |
+------------+------+
| 2007-00-00 | 1 |
+------------+------+
1 row in set (0.00 sec)
mysql> select * from test where YEAR(field) = 2007;
+------------+------+
| field | id |
+------------+------+
| 2007-00-00 | 1 |
+------------+------+
I would use one field it will make the queries easier.
Yes using the Date and Time functions would be better.
Thanks BrynJ
You could try a LIKE operative. Such as:
SELECT * FROM table WHERE date_feield LIKE 2009;
It depends on how you use the resulting data. A simple answer would be to simply store those dates where only the year is known as January 1. This approach is really simple and allows you to aggregate by year using all the standard built in date functions.
The problem arises if the month or date is significant. For example if you are trying to determine the age of a record in days, weeks, months or if you want to show distribution across this smaller level of granularity. This problem exists any way, though. If you have some full dates and some with only a year, how do you want to represent them in such instances.