Losy comparison between dates - mysql

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.

Related

A more elegant way to get current year-month to insert in a 'where' clause

The table I'm querying from has this DateTime column.
created_time
2022-03-19T15:21:52+08:00
2022-03-19T13:10:22+08:00
2022-03-19T13:09:52+08:00
2022-03-19T13:02:47+08:00
2022-03-20T20:51:03+08:00
select extract(year_month from curtime())
Using extract (as above) doesn't work as it will get me: 202203.
SELECT created_time
FROM `freemark-prod-zohocrm`.patients p
where select extract(year_month from curtime())
Therefore the query above will not give me any result as my 'where' clause needs to specifically ask for '2022-03%' and not 202203.
SELECT created_time
FROM `freemark-prod-zohocrm`.patients p
where date_format(p.created_time, '%Y')=(select extract(year from curtime()))
and date_format(p.created_time, '%m')=(select extract(month from curtime()))
Therefore I am currently using the query above to obtain Year='2022' AND Month='03' which I feel doesn't look that nice and might cause me future problems.
I am wondering if there is a more elegant way to get the current 'Year-Month' (eg.'2022-03%') to use in my 'where' clause.
Thank you for your time.
LIKE Example db<>fiddle
Since the query is a simple YYYY-MM prefixed lookup, use LIKE DATE_FORMAT(NOW(), '%Y-%m-%%') as 2022-03-%. Functioning the same for either DATETIME or VARCHAR column data-types, and is by-far the fastest solution regardless of indexing.
SELECT p.created_time
FROM `freemark-prod-zohocrm`.patients p
WHERE p.created_time LIKE DATE_FORMAT(NOW(), '%Y-%m-%%');
Compare YEAR_MONTH Criteria db<>fiddle
To fix the issue with the original query not returning results, match the criteria column and value functions. However, as a function is called on the column value a full-table scan will be performed.
SELECT p.created_time
FROM `freemark-prod-zohocrm`.patients p
WHERE EXTRACT(YEAR_MONTH FROM p.created_time) = EXTRACT(YEAR_MONTH FROM NOW());
To prevent a full-table scan avoid altering column values in the criteria using DATE_FORMAT(created_time), EXTRACT(... FROM created_time) or other functions, which will cause MySQL to check all rows in the table to determine if the condition matches.
MySQL 5.5 and earlier db<>fiddle
Note: In MySQL 5.5 and earlier, extract(year_month from curtime()) or for any date specific Temporal Intervals will return NULL because curtime() returns the TIME portion as HH:MM:SS.The behavior appears to have changed in MySQL 5.6 and later, where EXTRACT() will apply to the current date when the date argument is supplied as a TIME data-type and failing when supplied as a time string literal.
However, an undesirable value will be returned when using a date + time interval such as DAY_MINUTE and the date portion of the value is omitted.
SELECT
curtime(), /* 19:07:40 */
extract(year_month from curtime()), /* NULL */
extract(day_minute from curtime()); /* 1907 */
To resolve the issue always use NOW(), otherwise in MySQL 5.5 and earlier curtime() should be replaced with CURDATE() or NOW() depending on the interval being used.
DATE Interpreted Example db<>fiddle
As DATE_FORMAT() returns a string literal, to prevent string comparison issues in MySQL such as '10' > '2' = false, enforce a DATE or DATETIME context.
When using DATE or DATETIME interpreted values (see explanation below) to retrieve the rows for an entire month, the following criteria values can be used to force the condition to process in the context of a DATE data-type
DATE(DATE_FORMAT(NOW(), '%Y-%m-01')) to get the first day of the current month as a DATE data-type
LAST_DAY(NOW()) + INTERVAL 1 DAY to get the the first day of the next month as a DATE data-type.
SELECT p.created_time
FROM `freemark-prod-zohocrm`.patients p
WHERE p.created_time >= DATE(DATE_FORMAT(NOW() ,'%Y-%m-01'))
AND p.created_time < LAST_DAY(NOW()) + INTERVAL 1 DAY;
LAST_DAY(NOW()) will return the date as 2022-03-31
+ INTERVAL 1 DAY will increment the date by one day to 2022-04-01
MySQL 5.6+ Results
CREATE TABLE patients_varchar (
`id` INTEGER NOT NULL PRIMARY KEY,
`created_time` VARCHAR(25),
INDEX(`created_time`)
);
INSERT INTO patients_varchar
(`id`, `created_time`)
VALUES
('1', '2022-02-19T15:21:52+08:00'), /* Added to verify range */
('2', '2022-03-19T15:21:52+08:00'),
('3', '2022-03-19T13:10:22+08:00'),
('4', '2022-03-19T13:09:52+08:00'),
('5', '2022-03-19T13:02:47+08:00'),
('6', '2022-03-20T20:51:03+08:00'),
('7', '2022-03-31T20:51:03+08:00'),
('8', '2022-04-20T20:51:03+08:00'); /* Added to verify range */
created_time
2022-03-19T13:02:47+08:00
2022-03-19T13:09:52+08:00
2022-03-19T13:10:22+08:00
2022-03-19T15:21:52+08:00
2022-03-20T20:51:03+08:00
2022-03-31T20:51:03+08:00
VARCHAR and Date Time Literals Explanation
When the column data-type is VARCHAR using a valid date time string literal format such as YYYY-MM-DDTHH:MM:SS+08:00, MySQL will automatically interpret the column value format of YYYY-MM-DDTHH:MM:SS+MM:HH as a DATETIME data-type appropriately when provided a criteria value in the DATE or DATETIME data-type context.Please see the String and Numeric Literals in Date and Time Context for more details.
DATETIME context and time zone offsets
For MySQL 5.6+ to specify the inclusion of a time value, use TIMESTAMP(DATE_FORMAT(LAST_DAY(NOW()), '%Y-%m-%d 23:59:59')) to force a DATETIME context as opposed to using DATE().
For MySQL 5.5 and earlier db<>fiddle, when specifying a DATETIME context, the time zone offset in the column value is not parsed correctly and produces a different result. Using a string context of DATE_FORMAT(LAST_DAY(NOW()), '%Y-%m-%dT23:59:59') resolves the issue but may produce unexpected results, due to the string context comparison eg: '10' > '2' = false.
Note: the T is required for MySQL to parse the YYYY-MM-DDTHH:MM:SS formatted column value correctly.
For example the following conditions will all return true due to the DATETIME context. While MySQL 8.0+ will process the time zone offset when it is included in the string.
SELECT
'2022-02-19T15:21:52+08:00' = TIMESTAMP('2022-02-19T15:21:52'),
'2022-02-19T15:21:52+08:00' < TIMESTAMP('2022-02-20T15:21:52'),
'2022-02-19T15:21:52+08:00' > TIMESTAMP('2022-02-18T15:21:52'),
'2022-02-19T00:00:00+08:00' = TIMESTAMP('2022-02-19'),
'2022-02-19T15:21:52+08:00' < TIMESTAMP('2022-02-20'),
'2022-02-19T15:21:52+08:00' > TIMESTAMP('2022-02-18');
As opposed to the following conditions comparing strings that all return unexpected results.
SELECT
'2022-02-19T15:21:52+08:00' = '2022-02-19T15:21:52', # false
'2022-02-19T15:21:52+08:00' <= '2022-02-19T15:21:52', # false
'2022-02-19T15:21:52+08:00' > '2022-02-19T15:21:52'; # true
querying based on function calls such as extract(), or others datepart(), etc. are not Sargeable
What you would be better doing is something like
where
created_time >= '2022-03-01'
AND created_time < '2022-04-01'
This way, it gets the entire month in question including time portion up to 2022-03-31 # 11:59:59pm.
Now, to compare automatically against whatever the current date IS, you can do with MySQL Variables to compute the first of the month and beginning of next month for your from/to range.
select
...
from
( select #FirstOfMonth := DATE_FORMAT(CURDATE(), '%Y-%m-01'),
#FirstOfNextMonth := date_add( #FirstOfMonth, interval 1 month )) sqlvars,
`freemark-prod-zohocrm`.patients p
where
p.created_time >= #FirstOfMonth
AND p.created_time < #FirstOfNextMonth

Why is Between not finding the end date

I am using the following query:
SELECT *
FROM vaccines
WHERE (initial_date BETWEEN '1/1/2018' AND '10/31/2018')
AND customer_id='110607'
AND item_description= 'FLUZONE .QUAD MDV';
I get no results and there are entries for that date range, however, results are display till month 9, from 10 -12 I get nothing.
Q: Why is BETWEEN not finding the end date?
A: Likely because MySQL is doing string comparison; comparing string values not date values.
If we are comparing strings, note that any string starting with '9'
is going to be greater than a string starting with '1'.
'9........' is greater than '1........'
likewise
'9/23/1999' is greater than '10/31/2001'
Even if we include leading zeros, comparing as strings, we are still going to run into problems with comparing the year:
'09/23/2018' is less than '10/31/2001'
^^^^ ^^^^
If we want to make use of MySQL ability to compare date and datetime values, then we can use MySQL native DATE and DATETIME types, and avoid doing string comparisons.
If initial_date column is defined as DATE or DATETIME datatype, then specify date values in the query, e.g.
WHERE initial_date >= '2018-01-01' + INTERVAL 0 DAY
AND initial_date < '2018-11-01' + INTERVAL 0 DAY
(Note: MySQL date literals can be represented as 'YYYY-MM-DD'.)
If initial_date columns is defined as string type (e.g. CHAR or VARCHAR), then we can convert the string values into a date value using the STR_TO_DATE function e.g.
WHERE STR_TO_DATE(initial_date,'mm/dd/yyyy') >= '2018-01-01' + INTERVAL 0 DAY
AND STR_TO_DATE(initial_date,'mm/dd/yyyy') < '2018-11-01' + INTERVAL 0 DAY
(Note that the second argument is a format model; the string will be interpreted according to that model.)
Try
SELECT *
FROM vaccines
WHERE (initial_date BETWEEN CAST('2018-01-01' AS DATE) AND CAST('2018-10-31' AS DATE))
AND customer_id='110607'
AND item_description= 'FLUZONE .QUAD MDV';

Getting the date from DATETIME. mySQL

I wish to query for
MyDate= '2013-07-08'
From the following records
MyDate
2013-07-08 09:15:21
2013-07-08 09:15:48
2013-07-09 09:20:39
I have come up with some ugly stuff :
MyDate > '2013-07-07 23:59:59' AND MyDate < '2013-07-09 00:00:01'
Is there a better/simple/elegant way to do this?
Use DATE() to isolate the date portion of the datetime expression.
WHERE DATE(MyDate) = '2013-07-08'
If your trying to compare dates use this. If not disregard.
This may not be the most perfect way but, i have used this in the past. Basically i would format both dates so they can be used with a greater than or equal to statement(YEAR/MONTH/DAY).
SELECT * FROM table
WHERE MyDate > DATE_FORMAT(2013-07-07 23:59:59, '%Y%m%y')
AND MyDate < DATE_FORMAT(2013-07-09 00:00:01, '%Y%m%y')
The normative pattern to matching the date portion of a DATETIME in a predicate (e.g. a WHERE clause) is:
WHERE MyDate >= '2013-07-08'
AND MyDate < '2013-07-08' + INTERVAL 1 DAY
When no time component is supplied, MySQL uses midnight as the time component, so there's no need to supply a time component of midnight. The bare column references in the predicate allow for MySQL to consider making efficient range scan on an index on the MyDate column.
For completeness, we'll note that it's also possible to use a ETWEEN operator. But because the "high side" comparison with the BETWEEN is a "less than or equal to", to get just values with date component of a single day, we'd need to back up the smallest fraction of time, which for a DATETIME is a single second:
WHERE MyDate BETWEEN '2013-07-08'
AND '2013-07-08' + INTERVAL 1 DAY + INTERVAL -1 SECOND
(If we had a datatype that had a finer resolution, we'd want to step back from the next day by that smallest unit of resolution.)
To avoid that issue, how fine a resolution is on a given datetime/timestamp datatype (more of an issue with other databases such as SQL Server than MySQL), I just have a preference of the former pattern, using a predicate like:
dateexpr >= midnight and dateexpr < midnight of next day
That's unambiguous, and there's no possible way to have a time value of 23:59:59.997 to be missed, and no possibility of getting exactly at midnight of the next day included.
Because the default time component, when none is supplied, is midnight, the first query predicate is equivalent to:
WHERE MyDate >= '2013-07-08 00:00:00'
AND MyDate < '2013-07-08 00:00:00' + INTERVAL 1 DAY
I think all those extra zeros to explicitly specify a time value of midnight are unnecessary clutter.

MySQL Select rows <= now(), using separated time fields

I have a table 't' with date(yyyy-mm-dd), hour(1-12), minute(00-59), ampm(a/p), and timezone(pst/est) fields.
How can I select the rows that are <= now()? (ie. already happened)
Thank you for your suggestions!
edit: this does it without attention to the hour/minute/ap/tz fields:
SELECT * FROM t.date WHERE date <= now()
Here's one way to do it - combine all your seconds, minutes, etc into a date and compare to NOW(), making sure you do the comparison in the same time-zone. (Untested):
SELECT *
FROM t
LEFT JOIN y ON t.constant=y.constant
WHERE CONVERT_TZ(STR_TO_DATE(CONCAT(date,' ',hour,':',minute,' 'ampm),
'%Y-%m-%d %l:%i %p' ),
timezone,"SYSTEM") < NOW();
If your hour is 01 - 12 not 1-12 then use %h instead of %l in the STR_TO_DATE.
The STR_TO_DATE tries to stick your date and time columns together and convert them into a date.
The CONVERT_TZ(...,timezone,"SYSTEM") converts this date from whatever timezone is specified in the timezone column to system time.
This is then compared to NOW(), which is always in system time.
As an aside, perhaps you should make a single column date using MySQL's date datatype, as it's a lot easier to do arithmetic on that!
For reference, here is a summary of very useful mysql date functions where you can read up on those featuring in this answer.
Good luck!
SELECT * FROM t
WHERE `date`<=DATE_SUB(curdate(), INTERVAL 1 DAY)
OR (
`date`<=DATE_ADD(curdate(), INTERVAL 1 DAY)
AND
CONVERT_TZ(CAST(CONCAT(`date`,' ',IF(`hour`=12 AND ampm='a',0,if(ampm='a',`hour`,`hour`+12)),':',`minute`,':00') AS DATETIME),'GMT',`timezone`)<=NOW()
)
Rationale for date<=DATE_[ADD|SUB](curdate(), INTERVAL 1 DAY):
The fancy conversion is quite an expensive operation, so we don't want it to run on the complete table. This is why we pre-select against an UNCHANGED date field (possibly using an index). In no timezone can an event being more than a day in current timezone's past be in the future, and in no timezone can an event more than a day in the curent timezone's future be in the past.

MySQL - return only entries from the past X days

I'm working with a database that has date information stored as a Unix timestamp ( int(11) ) and what I want to do is only return entries from the past X days, the past 90 days for example.
What I've come up with is:
SELECT * FROM mytable WHERE category=1 AND
FROM_UNIXTIME( time ) > DATE_SUB(now(), INTERVAL 91 DAY)
Where 'time' is the int(11) in the db. This seems to be working fine, but just wondering what others think of this.
SELECT * FROM mytable WHERE category=1 AND
time > (UNIX_TIMESTAMP() - ((60*60*24)*90))
or simply
SELECT * FROM mytable WHERE category=1 AND
time > (UNIX_TIMESTAMP() - (86400*90))
this is just comparing a number (seconds in this case)
This query is bound to cause you headaches down the way as MySQL needs to do the conversion of dates for every row making use of indexes impossible. Unix timestamps are numbers, so instead of converting a timestamp to another date format, convert your lookup dates to unix timestamps.
What is the reason for storing the timestamp as an int ? I would use the mysql DATETIME data type because you can use the many Date Time functions mysql has.
If you do not have control over the data type of this field I would convert your date to the unix timestamp int before you do your query and compare it that way.
Just thinking aloud... wouldn't doing it the other way around cause less work for the DB?
time > UNIX_TIMESTAMP( DATE_SUB(NOW(), INTERVAL 91 DAY) )