Questions on To_Date function - sql

I have two questions,
1.
Why can't I get HH24:MI:SS when using To_date function?
select To_date(fn_adjusted_date(SUBMIT_DATE),'DD-MON-YY-HH24:MI:SS')
from HPD_Help_Desk;
16-NOV-08
select To_char(fn_adjusted_date(submit_date),'DD-MON-YY-HH24:MI:SS')
from HPD_Help_Desk;
16-NOV-08-06:01:10
2.
Why am I getting an error when using:
To_date(fn_adjusted_date(SUBMIT_DATE),'DD-MON-YY-HH24:MI:SS')
but changing it works fine when I change it to:
To_date(fn_adjusted_date(SUBMIT_DATE),'DD-MM-YY-HH24:MI:SS')
To demonstrate:
select sysdate from dual;
03-MAR-15
alter session set nls_date_format = 'dd-mm-yyyy hh24:mi:ss';
select sysdate from dual;
03-03-2015 11:29:22
select To_date(fn_adjusted_date(SUBMIT_DATE),'DD-MON-YY-HH24:MI:SS')
from HPD_Help_Desk;
ORA-01843: not a valid month 01843. 00000 - "not a valid month"
select To_char(fn_adjusted_date(submit_date),'DD-MON-YY-HH24:MI:SS')
from HPD_Help_Desk;
16-NOV-08-06:01:10

1.
Because to_date() gives you a date object, and you're leaving it up to your client to decide how to display that as a string; it's likely to be using your NLS_DATE_FORMAT settings.
Since your fn_adjusted_date() function returns a date not a string, do not then call to_date() on that; you're doing an implicit conversion to a string and then back to a date, both using NLS_DATE_FORMAT, and from how your first query is displayed - as DD-MON-YY? - that is losing the time portion anyway. So you're really doing:
select to_date(to_char(fn_adjusted_date(SUBMIT_DATE), 'DD-MON-YY'),
'DD-MON-YY-HH24:MI:SS') from HPD_Help_Desk;
2.
Because MON is the abbreviated month name in your date language. This follows on from the first point; now in the first of those you're doing an implicit to_char() of your value using the new NLS_DATE_FORMAT, which specifies the month number with MM, but then you try to convert that back to a date with MON. So this time you're really doing:
select to_date(to_char(fn_adjusted_date(SUBMIT_DATE), 'dd-mm-yyyy hh24:mi:ss'),
'DD-MON-YY-HH24:MI:SS') from HPD_Help_Desk;
And 11 is not a valid month name. Oracle is quite flexible with date formats when it can be; it can interpret 'NOV' using the MM model even though that doesn't make sense really since it isn't a number, but the meaning is pretty obvious; from your example in a comment:
select to_date('16-Nov-2008', 'DD-MM-YY') from dual;
TO_DATE('16-NOV-2008','DD-MM-YY')
---------------------------------
16-NOV-2008 00:00:00
It doesn't work the other way though; it can't interpret 11 using MON. That flexibility can appear inconsistent, and it sometimes seems to be too forgiving.
In the second query you're doing an explicit to_char() with a format model specified, which is the correct way to display a date as a string.
The underlying messages are the same for both: don't call to_date() when you already have a date object, don't ever rely on implicit conversion, and don't convert a date to a string while you're still processing it - only if you want it as a string in a specific format in your final result set.

Related

Oracle regexp to validate time

I am trying to validate a regular expression in oracle SQL to check Time format:
The desired format I want to check is:
2/27/2020 3:53:02 PM
I have already created a regexp for a date format such as:
20200227 --> using REGEXP_LIKE('20190222', '^\d{4}(0[1-9]|(1[0-2]))(0[1-9]|[1-2][0-9]|3[0-1])$')
Could someone give me a hint for the time format?
To me, it looks as if you started it wrong. If you want to validate date (time) format, you're storing it as a string, which is a big mistake.
If you set that column (or whatever it is) as DATE, which - in Oracle - contains both date and time, then database will take care that you can enter only valid values.
Format you mentioned, or any other, is matter of display, not storage.
One option is to force TO_DATE conversion; something like this:
SQL> alter session set nls_date_format = 'dd.mm.yyyy hh24:mi:ss';
Session altered.
SQL> set ver off
SQL> select to_date('&date_value', 'mm/dd/yyyy hh:mi:ss am') result from dual;
Enter value for date_value: 2/27/2020 3:53:02 pm
RESULT
-------------------
27.02.2020 15:53:02
SQL> /
Enter value for date_value: 13/54/2020 x:23:83 am
select to_date('13/54/2020 x:23:83 am', 'mm/dd/yyyy hh:mi:ss am') result from dual
*
ERROR at line 1:
ORA-01843: not a valid month
SQL>
So: if conversion works, then format is OK. Otherwise, you entered something stupid and conversion won't pass. Such a code can be used as a function which returns e.g. Boolean.
Don't use a regular expression as the edge cases will make the expression long and complicated (i.e. months can have 28-31 days, leap years every 4 years ... except for multiples of 100 ... except for multiples of 400).
For example, your date regular expression ^\d{4}(0[1-9]|(1[0-2]))(0[1-9]|[1-2][0-9]|3[0-1])$ would validate 20190229 or 20200230 or 20200931 neither of which are valid dates.
A time regular expression is easier as you only have hours 0-23, minutes 0-59, seconds 0-59 (assuming you are ignoring leap seconds) and possibly fractional seconds and would be:
([01]\d|2[0-3]):[0-5]\d:[0-5]\d(\.\d+)?
However, a simpler way is just to try to perform a conversion to a DATE and if it fails then you know it isn't valid. If you are on Oracle 12.2 or later then you can use the built-in VALIDATE_CONVERSION function:
SELECT validate_conversion( '20200230000000' AS DATE, 'YYYYMMDDHH24MISS' )
FROM DUAL
If you are on an earlier version then you can create a custom function to try and perform the conversion and if an exception occurs then you know the input is invalid:
CREATE FUNCTION isValidDate(
date_string IN VARCHAR2,
format_model IN VARCHAR2 DEFAULT 'FXYYYYMMDDHH24MISS'
) RETURN NUMBER
IS
d DATE;
BEGIN
d := TO_DATE( date_string, format_model );
RETURN 1;
EXCEPTION
WHEN OTHERS THEN
RETURN 0;
END;
/
While looking into string Regex, I found that this one does the job that I'm looking for:
REGEXP_LIKE('2/27/2020 3:53:02 PM','^\d{1,2}\/\d{1,2}\/\d{4} \d{1,2}:\d{1,2}:\d{1,2} [AP]M\z')

Converting timestamp to date in Oracle 12c

I want to convert a given timestamp in such format: 2019-04-08 00:00:00.0 to a date in this format: 2019-04-08.
I have already tried using:
SELECT TO_DATE('2019-04-20 00:00:00.0','YYYY-MM-DD') from dual;
But I got prompted with:
ORA-01830: date format picture ends before converting entire input
string
I think you may have some conceptual misunderstanding about how the TO_DATE function works, and also about how dates are processed by the DBMS.
YYY-MM-DD does not match the format of the actual string you're importing (2019-04-20 00:00:00.0) That's what the error is telling you. You must tell the TO_DATE function what to expect in the date string you input into it. You do that by means of the format string. if you don't specify a format string which matches the format you're actually going to supply, then the function will fail to process the string.
Next, you say you want to convert it "to a date in this format"...but this does not entirely make sense. TO_DATE converts a string into a variable of type DATETIME - i.e. a date object. A date object does not not exist in any particular format, it exists as an object. Internally it will store the date information in a way which is independent of any human-readable date format. The format relates entirely to the presentation of the date when seen as a string. Once you have a date object, you can then output the date in a particular format if you want to a human to be able to read it in the style that their culture is familiar with.
So, firstly to import your date string correctly as a date object, you can use an accurate format string, an also use TO_TIMESTAMP instead of TO_DATE so that it captures the sub-seconds value:
SELECT TO_TIMESTAMP('2019-04-20 00:00:00.0','YYYY-MM-DD HH24:MI:SS.FF5') from dual;
If you run this in a console the SELECT will then automatically re-format that date object (the result of the TO_DATE function) into the default date format configured in your server / session.
However if you actually want to see it on screen in a particular format, you can explicitly say so - a sensible way is using the TO_CHAR function:
SELECT TO_CHAR(TO_TIMESTAMPT('2019-04-20 00:00:00.0','YYYY-MM-DD HH24:MI:SS.FF5'), 'YYYY-MM-DD') from dual;
The full list of format specifiers can be found here (and in other places online as well).
Live demo of the above here: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=619d918ea73953e11b3150c6b560112c
Assuming the input is actual text, and not a real timestamp, you could try just truncating the text before you call TO_DATE:
WITH cte AS (
SELECT '2019-04-20 00:00:00.0' AS ts FROM dual
)
SELECT TO_DATE(SUBSTR(ts, 1, 10), 'YYYY-MM-DD')
FROM cte;
If your input is an actual Oracle timestamp, and you want to convert it to a date, then you may use CAST:
SELECT CAST(ts AS DATE) dt
FROM cte;
Would CAST do any good?
I'm setting date format so that it displays time component, although it is 00:00:
SQL> alter session set nls_date_format = 'dd.mm.yyyy hh24:mi:ss';
Session altered.
SQL> select cast(timestamp '2019-04-20 00:00:00.0' as date) result from dual;
RESULT
-------------------
20.04.2019 00:00:00
Another format (without time component):
SQL> alter session set nls_date_format = 'dd.mm.yyyy';
Session altered.
SQL> select cast(timestamp '2019-04-20 00:00:00.0' as date) result from dual;
RESULT
----------
20.04.2019
SQL>
Or, using TO_CHAR function (so that session's date format doesn't matter):
SQL> select to_char(timestamp '2019-04-20 00:00:00.0', 'dd.mm.yyyy') result from dual;
RESULT
----------
20.04.2019
SQL>

What is Oracle's Default Date Format?

I have an Oracle DB, and I don't control the date format. I want to know what the date format is to ensure that searches like
select * from search where search_date>='03/16/2016 00:00:00'
work as expected.
Don't do that - you are relying on implicit data type conversion which is going to fail at some point.
You have two options:
1) Use a proper ANSI SQL date literal:
select *
from search
where search_date >= timestamp '2016-03-16 00:00:00';
2) use to_date() (or to_timestamp()) and use a custom format.
select *
from search
where search_date >= to_date('03/16/2016 00:00:00', 'mm/dd/yyyy hh24:mi:ss');
With to_date() you should avoid any format that is language dependent. Use numbers for the month, not abbreviations (e.g. 'Mar' or 'Apr') because they again rely on the client language.
More details can be found in the manual: https://docs.oracle.com/cd/E11882_01/server.112/e41084/sql_elements003.htm#SQLRF51062
Never rely on implicit data type conversion.
You can get all the NLS session parameters with the query:
SELECT * FROM NLS_SESSION_PARAMETERS;
or, if you have the permissions GRANT SELECT ON V_$PARAMETER TO YOUR_USERNAME;, you can use the command:
SHOW PARAMETER NLS;
If you just want the date format then you can do either:
SELECT * FROM NLS_SESSION_PARAMETERS WHERE PARAMETER = 'NLS_DATE_FORMAT';
or
SHOW PARAMETER NLS_DATE_FORMAT;
However, you could also use ANSI date (or timestamp) literals which are format agnostic. An ANSI date literal has the format DATE 'YYYY-MM-DD' and a timestamp literal has the format TIMESTAMP 'YYYY-MM-DD HH24:MI:SS.FF9'. So your query would be:
select * from search where search_date>= DATE '2016-03-16'
or
select * from search where search_date>= TIMESTAMP '2016-03-16 00:00:00'
What is Oracle's Default Date Format?
A DATE doesn't have any format. Oracle does not store dates in the format you see. It stores it internally in 7 bytes with each byte storing different components of the datetime value.
Byte Description
---- -------------------------------------------------
1 Century value but before storing it add 100 to it
2 Year and 100 is added to it before storing
3 Month
4 Day of the month
5 Hours but add 1 before storing it
6 Minutes but add 1 before storing it
7 Seconds but add 1 before storing it
To display, use TO_CHAR with proper FORMAT MODEL.
For comparing, use TO_DATE with proper FORMAT MODEL.
What you see as a format by default, is your locale specific NLS settings.
SQL> select parameter, value from v$nls_parameters where parameter='NLS_DATE_FORMAT';
PARAMETER VALUE
--------------- ----------------------------------------------------------------
NLS_DATE_FORMAT DD-MON-RR
SQL> select sysdate from dual;
SYSDATE
---------
17-MAR-16
SQL> select to_char(sysdate, 'mm/dd/yyyy hh24:mi:ss') from dual;
TO_CHAR(SYSDATE,'MM
-------------------
03/17/2016 12:48:41
SQL>
search_date>='03/16/2016 00:00:00'
You are comparing a DATE with a string literal. Always, explicitly convert the string into date using TO_DATE and proper format mask.
TO_DATE('03/16/2016', 'MM/DD/YYYY')
Or, if you dealing only with the date part and not concerned with the time portion, then use the ANSI date literal which uses a fixed format DATE 'YYYY-MM-DD'
DATE '2016-03-16'
You might just be lucky to get an output due to an implicit datatype conversion based on your locale specific NLS settings. Never ever rely on implicit datatype conversion, it might work for you, might fail for others where the nls settings are different.

SQL BETWEEN not working

I have the following statement being run on an oracle database.
SELECT br.Number
FROM Billing_History br
WHERE TRUNC(br.History_Date) BETWEEN to_date('01-Jan-99', 'DD-Mon-YY HH:MI:SS')
AND to_date('11-May-99', 'DD-Mon-YY HH:MI:SS')
There are definitely records in that table that fall between those dates. And they all have a Number that goes with them, but for some reason this isn't returning any Numbers. It's returning nothing at all.
The dates in the database are in this format '01-Jan-11'. So it seems like I'm putting the dates in the correct format too. Do you see anything wrong with the SQL I wrote?
The problem is not the time component of the format model, it's the 'YY' component, which would mean in your year is converted to 2099, not 1999. Try this to illustrate:
SQL> SELECT to_char(to_date('01-Apr-99','DD-Mon-YY'),'DD-Mon-YYYY') thedate
FROM dual;
THEDATE
-----------
01-Apr-2099
SQL>
Either use RR or YYYY as a format model component for year when using 20th century dates.
Edit:
You make the statement "The dates in the database are in this format '01-Jan-11'." This is a common, but incorrect, interpretation of dates in Oracle. DATE fields are always stored in the same internal format. It's all about how you use the format model in conversion functions that dictates how the data is converted to/from internal format.
Use RR in your date format instead of YY. It is probably picking up those dates as 2099 instead of 1999.
SELECT br.Number FROM Billing_History br WHERE
TRUNC(br.History_Date) BETWEEN to_date('01-Jan-99', 'DD-Mon-RR HH:MI:SS')
AND to_date('11-May-99', 'DD-Mon-RR HH:MI:SS')
Try removing the time part from the second to_date parameter:
to_date('11-May-99', 'DD-Mon-YY')
Or even better:
to_date('11-05-1999', 'DD-MM-YYYY')
This is more robust as it is language agnostic and doesn't need to guess the century.

store dates in oracle

I have a table as
create table Dummy (date_created date)
in oracle.I want to store date in 'dd-mon-yyyy' (12-dec-2010) format.
How should i do this.
Please help.
In Oracle a column created with the DATE datatype just stores the date. It doesn't have a particular format, it just stores the day, month, year, hour, minute, and second. You need to convert from whatever format you have using the TO_DATE function. If you have a text string with the date in 'dd-mon-yyyy' format and you want to put this date into your table you'd use something like
INSERT INTO DUMMY (DATE_CREATED)
VALUES (TO_DATE('01-FEB-2011', 'DD-MON-YYYY');
Going the other way (from DATE column value to character string) you'd use the TO_CHAR function. If you were retrieving a value from your table and wanted to convert it to 'DD-MON-YYYY' format you'd use something like
SELECT TO_CHAR(DATE_CREATED, 'DD-MON-YYYY')
FROM DUMMY;
Share and enjoy.
Use to_date() function. In your case, the syntax would be
insert into Dummy values (to_date('08-09-2010', 'dd-mm-yyyy'));
Here is a link to the detailed help.
The DATE datatype will store date and time information (century, year, month, day, hours, minutes, and seconds) in an internal format in the database. When you get it out of the database, you can choose to display it in whatever format you like.
This information is either created using implicit conversion from a string or explicitly using either the TO_DATE function or the ANSI date literal. If you look in the v$nls_parameters view, this will tell you what the NLS_DATE_FORMAT is which is generally used for the implicit conversion. This may often be defined as DD-MON-RR, which might be why the date will come out as 23-DEC-10 when the query select sysdate from dual is run. (Not entirely sure I'm right about the nls stuff. Correct me if I'm wrong.)
However, all the date information is available if you know how to get it. The query select to_char(sysdate, 'dd-mon-yyyy hh24:mi:ss') from dual will return all the date fields.
Likewise, the insert statement shown below will create a row with a date value in it.
insert into dummy (date_created)
values (to_date('12-dec-2010 12:34:56', 'dd-mon-yyyy hh24:mi:ss'))`
This data can then be retrieved.
select date_created from dummy
This will implicitly convert the date to a character string using the NLS_DATE_FORMAT, providing the output below.
DATE_CREA
---------
23-DEC-10
The full date information is available by explicitly converting the date to a character string.
select to_char(date_created, 'DD-MON-YYYY') as date_created from dummy;
select to_char(date_created, 'DD-MON-YYYY HH24:MI:SS') as date_created
from dummy;
This will provide output in the format you require:
DATE_CREATE
-----------
23-DEC-2010
If you always use the TO_DATE and TO_CHAR functions to convert to/from a date datatype, then you will have fewer problems. Implicit conversion is useful but can cause some confusion or problems.
You can keep and eye here
http://www.techonthenet.com/oracle/functions/to_date.php
use to_date function to save a data with the format you need. I suggest to use SYSDATE updating table and when you need to read data from table use something like that:
dbms_output.put_line(TO_CHAR(SYSDATE, 'DD-MON-YYYY HH24:MI:SS'));
to solve your problem use:
to_date('08/JAN/2010', 'DD/MON/YYYY')
Just use TRUNC(YourDate) if date have time part, it will be truncate time part. Oracle have not just 'DATE' type, 'DATE' always have time part.
However if you do not specify time - it will 00:00:00.
SELECT TRUNC(SYSDATE) from dual
Result:
23-12-2010
Oracle does not support DATE without time part.
You can make it always be an integer date by adding a CHECK constraint:
CREATE TABLE dummy (date_created date CHECK (date_created = TRUNC(date_created)))
, insert it in any format you want:
INSERT
INTO dummy (date_created)
VALUES (TO_DATE('23-DEC-2010', 'dd-mon-yyyy'))
and select it in any format you want:
SELECT TO_CHAR(date_created, 'dd-mon-yyyy')
FROM dummy