Not a Valid Month - Working with Dates in Oracle - sql

I am finding it strenuous to work with dates in my customized environment. I have a request to add a where clause which caters to specific dates but I just cannot get oracle to budge. Any ideas anyone please.
select created_date, cast(created_date as date) as created_date_cast
from mytable;
created_date created_date_cast
04-Mar-20 05.21.15.772000 AM 3/4/2020 5:21:15 AM
04-Mar-20 05.21.15.709000 AM 3/4/2020 5:21:15 AM
04-Mar-20 05.17.14.902000 AM 3/4/2020 5:14:14 AM
28-Feb-20 01.15.25.702700 AM 2/28/2020 1:15:25 AM
When I try to add a where clause the snippet blows up with the error:
select created_date, cast(created_date as date) as created_date_cast
from mytable
where cast(created_date as date) <= '02/28/2020';
ORA-01843: not a valid month
I have also tried to_date(created_date, 'MM/DD/YYYY') in the from but proves to be erroneous with:
ORA-01858: a non-numeric character was found where a numeric was expected

Firstly cast as date which converts a timestamp value to a date value, and then don't forget to add trunc() function in order to include the boundry value (date'2020-02-28' in this case) also as
where trunc(cast(created_date as date)) <= date'2020-02-28'
Demo

Don't use CAST and don't use TRUNC (as then Oracle will not be able to use an index on your column but would, instead, require a function-based index created on TRUNC(created_date)) just add a day and use a literal:
SELECT created_date
FROM mytable
WHERE created_date < DATE '2020-02-29';
or
SELECT created_date
FROM mytable
WHERE created_date < TIMESTAMP '2020-02-29 00:00:00';
or, if you want to specify the exact date then just add a day. E.g.:
SELECT created_date
FROM mytable
WHERE created_date < DATE '2020-02-28' + INTERVAL '1' DAY;
All of those options should be able to use an index on the created_date column.
I have also tried to_date(created_date, 'MM/DD/YYYY') in the from but proves to be erroneous with:
ORA-01858: a non-numeric character was found where a numeric was expected
TO_DATE( value_string, format_model ) takes strings as its arguments but CREATED_DATE is a TIMESTAMP data type and not a string so Oracle must make an implicit TIMESTAMP-to-string conversion and it does this using the NLS_TIMESTAMP_FORMAT session parameter; so your expression is effectively:
TO_DATE(
TO_CHAR(
created_date,
( SELECT value FROM NLS_SESSION_PARAMETERS WHERE parameter = 'NLS_TIMESTAMP_FORMAT' )
),
'MM/DD/YYYY'
)
And if your NLS_TIMESTAMP_FORMAT is not MM/DD/YYYY then its highly likely that an exception will be raised (i.e. like the ORA-01858 you had).
You should never rely on implicit string conversions as any user can change their own session parameters at any time and an implicit conversion that works for one user may not work for another just because they have different parameter values (even though the queries are identical).

Use a date literal:
cast(created_date as date) <= date '2020-02-28'
I would also recommend dispensing with the cast() -- assuming that created_date is correctly stored as a date or timestamp:
created_date < (date '2020-02-28' + interval '1' day)

Related

ORACLE SQL SYSDATE and between

I am trying to get a date using between SYSDATE and SYSDATE - 300.
SELECT date_entered
FROM customer_order_join
WHERE TO_CHAR(date_entered, 'YYYYMMDD') BETWEEN
TO_DATE(sysdate, 'YYYYMMDD') AND TO_DATE(sysdate, 'YYYYMMDD') - 300
I am getting the following error :
ORA-01858: a non-numeric character was found where a numeric was expected.
SYSDATE returns a DATE value. Never run TO_DATE() on a value which is already a DATE. Apart from that, BETWEEN ... AND requires the lower date first.
You can compare the DATE values directly, I guess you are looking for this:
SELECT DATE_ENTERED
FROM CUSTOMER_ORDER_JOIN
WHERE DATE_ENTERED Between SYSDATE-300 AND SYSDATE
Most likely you like to compare just the date values, without time value. Then you could use this:
SELECT DATE_ENTERED
FROM CUSTOMER_ORDER_JOIN
WHERE TRUNC(DATE_ENTERED) Between TRUNC(SYSDATE-300) AND TRUNC(SYSDATE) -- or TRUNC(SYSDATE)+1 to cover full day
Note, the query will not utilize an index on DATE_ENTERED, unless you have a function-based index on TRUNC(DATE_ENTERED). So you may prefer
SELECT DATE_ENTERED
FROM CUSTOMER_ORDER_JOIN
WHERE DATE_ENTERED >= TRUNC(SYSDATE-300)
AND DATE_ENTERED < TRUNC(SYSDATE)
There is some strange date conversions in your where clause:
To_Char(DATE_ENTERED, 'YYYYMMDD')
If the column is already a date type just use the column without any conversions and if it is a string, use TO_DATE.
TO_DATE(SYSDATE,'YYYYMMDD')
No conversion needed
TO_DATE(SYSDATE,'YYYYMMDD')-300
No conversion needed
So maybe just:
SELECT DATE_ENTERED FROM CUSTOMER_ORDER_JOIN
WHERE TRUNC(DATE_ENTERED) Between TRUNC(SYSDATE - 300) AND TRUNC(SYSDATE);

Filtering a dataset by date and time Oracle SQL through Power BI

I'm having trouble with filtering a date and time for anything two hours before and sooner. I tried this:
SELECT *
FROM
table
where
date >= sysdate - 1
AND
TO_DATE( Time, 'HH24:MI:SS' ) >= TO_DATE( sysdate, 'HH24:MI:SS' ) - 2
But I'm getting an inconsistent type error which is what I thought I was handling with the TO_DATE() function but I guess not.
sysdate is already a date (and time), so TO_DATE( sysdate, 'HH24:MI:SS' ) doesn't make any sense.
You didn't provide your data types for your date and time columns in table, so I'm going to assume they're both varchar2(10) with formats MM/DD/YYYY and HH24:MI:SS respectively.
I'm also going to go ahead and change your example table and column names, since they're invalid names to use in a real query.
-- example data
with my_table as (select '06/13/2019' as date_column, '09:40:34' as time_column from dual)
-- your query
SELECT *
FROM
my_table
where
to_date(date_column || ' ' || time_column, 'MM/DD/YYYY HH24:MI:SS') >= sysdate - 2/24
What I'm doing here is to combine your date and time strings into one date-time string, then converting it to an Oracle date type (actually date+time). Then we compare it to sysdate - 2/24, which says to take the current time and subtract 2/24ths of a day, which is 2 hours.
For this example, you might need to change the example data date_column and time_column values to something from the past 2 hours, depending on when you run this and what time zone you're in.

TIMESTAMP To DATE in Oracle SQL

I have a column called Create_Date which has data in the format like 19-JUN-18 10.27.00.000000000 PM and data type is TIMESTAMP(6).
I am trying to look at date range like yesterday's date or between two dates in Create_Date without using TO_DATE(TO_CHAR(P.CREATE_DATE_TIME,'dd/mon/yy')) and entering the value as '19-JUN-18'.
I want to use Create_Date=SYSDATE-1 OR Create_Date=CURRENT_DATE-1 instead to filter on yesterdays date. Or Use Create_Date>=SYSDATE or Create_Date>=CURRENT_DATE to look at dates greater than or equal to today.
Can someone help?
You could use TRUNC:
SELECT *
FROM tab
WHERE Create_Date >= TRUNC(SYSDATE,'DD') -- -1
-- or between to dates (using date literals)
WHERE Create_Date >= DATE 'yyyy-mm-dd'
AND Create_Date < DATE 'yyyy-mm-dd'
As it's a timestamp I'd cast the truncated (to midnight) current date to a timestamp for clarity; Oracle will use an index on that column even if you leave it as a date, but it doesn't hurt to make it explicit:
where create_date >= cast(trunc(sysdate) as timestamp)
The trunc() function defaults to truncating to midnight; you can explicitly include 'DD' as a second argument if you prefer (for even more clarity, though some would see it as noise).
If you want a range, say yesterday:
where create_date >= cast(trunc(sysdate) - 1 as timestamp)
and create_date < cast(trunc(sysdate) as timestamp)
If you want to specify other dates then you can use timestamp literals, e.g. to see everything for May:
where create_date >= timestamp '2018-05-01 00:00:00'
and create_date < timestamp '2018-06-01 00:00:00'

Compare timestamps in Oracle

I have a column which is used to store date and time data in a VARCHAR2 column in my DB in Oracle 11g Express in the format of:
9/30/2016 14:00:00
I was trying out ways to get data between time ranges. I found the following 2 ways:
select *
from dummy
WHERE starttime > '9/30/2016 14:00:00'
AND starttime < '9/30/2016 17:00:00'
order by starttime;
select *
from dummy
WHERE to_timestamp(starttime, 'mm/dd/yyyy hh24:mi:ss') > TO_TIMESTAMP('9/30/2016 14:00:00', 'mm/dd/yyyy hh24:mi:ss')
AND TO_TIMESTAMP(starttime, 'mm/dd/yyyy hh24:mi:ss') < TO_TIMESTAMP('9/30/2016 17:00:00', 'mm/dd/yyyy hh24:mi:ss');
I was wondering how the first method works too as the column starttime is stored in VARCHAR format and without converting to a Timestamp the comparison still works. Could someone explain to me how/ why that happens or if there is some corner case for which it will not work? Thanks.
This works because you have no issue with months and years or one-digit vs. two-digit days. Think of any strings that are inside the range '9/30/2016 14:00:00' to '9/30/2016 17:00:00'. They will all have to start with '9/30/2016 1'.
If the range where, say '9/30/2016 14:00:00' to '10/30/2016 17:00:00', you wouldn't find any record at all, because the string would have to start with somthing >= '9' and <= '1' which is not possible.
So it is the narrow range within a particular day that saved you here :-)
Storing the values in a VARCHAR column means that you will do a string comparison:
SELECT *
FROM dummy
WHERE starttime > '9/30/2016 14:00:00'
AND starttime < '10/30/2016 17:00:00'
ORDER BY starttime;
This would look at the start time and consider it character-by-character and if the 1st character is greater than '9' and also less than '1' then it will return a row (since this will never be true it will not return a row). Moreover, it will not consider that the 9 and the 10 represent the months and that when doing a string comparison '09/30/2016' < '09/31/1900' < '10/30/2016'.
Even if you store the value in a TIMESTAMP column, using string literals is a bad idea:
SELECT *
FROM dummy
WHERE starttime > '9/30/2016 14:00:00'
AND starttime < '9/30/2016 17:00:00'
ORDER BY starttime;
This works as Oracle will perform an implicit cast (using TO_TIMESTAMP( time, format_mask )) using the session parameter NLS_TIMESTAMP_FORMAT as the format mask.
So your query would (assuming a TIMESTAMP data type) effectively be (although Oracle will implement it in a more efficient fashion):
SELECT *
FROM dummy
CROSS JOIN
( SELECT value AS format_mask
FROM NLS_SESSION_PARAMETERS
WHERE PARAMETER = 'NLS_TIMESTAMP_FORMAT' ) nls
WHERE starttime > TO_TIMESTAMP( '9/30/2016 14:00:00', nls.format_mask )
AND starttime < TO_TIMESTAMP( '9/30/2016 17:00:00', nls.format_mask )
ORDER BY starttime;
The NLS_TIMESTAMP_FORMAT is a session parameter - this means that each user can set their own value for this parameter in their own session and if one user changes it to YYYY-MM-DD"T"HH24:MI:SS.ZZZ"Z" (i.e. an ISO8601 format) then your query will break for that user (and not for the other users who have not changed it) without any changes having been made to your query.
Rather than using a string literal and implicit conversion, it is better to either explicitly set the format mask you are expecting or to use an ANSI TIMESTAMP literal:
SELECT *
FROM dummy
WHERE TO_TIMESTAMP( starttime, 'MM-DD-YYYY HH24:MI:SS' ) > TIMESTAMP '2016-09-30 14:00:00'
AND TO_TIMESTAMP( starttime, 'MM-DD-YYYY HH24:MI:SS' ) < TIMESTAMP '2016-09-30 17:00:00'
ORDER BY TO_TIMESTAMP( starttime, 'MM-DD-YYYY HH24:MI:SS' );
You would then benefit from a function-based index on TO_TIMESTAMP( starttime, 'MM-DD-YYYY HH24:MI:SS' ).
Even better, would be to convert your column to the correct TIMESTAMP format then you do not need a function-based index and can just use TIMESTAMP literals for the bounds without any need for conversion functions.
Storing date as varchar is less than clever...
Your first method is fine, provided you don't need to cross the boundary of a year. The numbers are compared left to right (because text). Unless you store as 'YYYY-MM-DD HH24:MI:SS' you will run into problems.
2 options, change that storage to DATE, or use a to_date or to_timestamp on the WHERE clause (I recommend to_date)
It's a bad idea to store date/times as character strings or numbers. The optimizer has no idea of the domain and so when attempting to estimate the cardinality you are not giving the optimizer the best chance. For example, consider the following two dates
Dec 31st 2016
Jan 1st 2017
If you store these as a number, you might use
20170101 and 20161231
So what is the number of days between them? Using numbers, you get
20170101 - 20161231
= 8870
However, the true (date based) answer is one.
Although you can TO_DATE() or CAST your columns, you now run the risk of not being able to use certain optimizations, such as indexing, partition pruning, bloom filtering etc.
So I highly recommend using the correct data types.
On Oracle, you could use too:
SELECT * FROM table
WHERE field BETWEEN TRUNC(SYSDATE - 6) AND SYSDATE

Sql strictly more than query

I'm in PostgreSQL.
I need to print all mailing with creation date strictly more that 2015-04-04. I tried the following queries:
SELECT *
FROM mailing.mailing
WHERE creation_date > '2015-04-04';
and
SELECT *
FROM mailing.mailing
WHERE creation_date >= '2015-04-04';
But they produced the same result set(including '2015-04-04'). Is it possible to write such a query without explicitly saying WHERE creation_date >= '2015-04-05';
UPD: The column's type is timestamp without time zone.
If your creation_date field is of type datetimetry comparing it to '2015-04-04 23:59:59' instead, as '2015-04-04 08:30:00' seems to be greater than '2015-04-04'.
Assuming your default date format for your database is 'YYYY-MM-DD' and creation_date field is a date type, your query will actually be converted automatically to something like:
SELECT *
FROM mailing.mailing
WHERE creation_date > to_date('2015-04-04', 'YYYY-MM-DD');
The date value you have provided represents the first second of that day, that's why you see no difference between your queries. (Your first query would exclude the first second of the day though.)
What you could do to avoid this is:
where creation_date >= to_date('2015-04-05 00:00:00', 'YYYY-MM-DD HH24:MI:SS')
or
where date_trunc(creation_date-1) = '2015-04-04'