Varchar2 to Date for sysdate comparison - sql

A table I am querying has dates stored as varchar2. I need to compare the dates stored as varchar2 with sysdate using BETWEEN sysdate-1 AND sysdate-30 (to return varchar2 dates from the last month).
When specifying TO_DATE(varchar2, 'DD-MON-YYYY') I get error "literal does not match format string".
I am stuck as Oracle documentation says this is an acceptable format for TO_DATE() when converting from varchar2.
UPDATE: This is a corporate database, I did not design the DB and can only work with what I have available. The data set is enormous and is automatically updated by SCADA devices, over 10,000 devices daily.
SELECT device_name, read_date, sysdate
FROM oracle_database
------- Data Returned by query --------
device_name read_date sysdate
Device 1 5/14/2013 22-Sep-14
Device 2 5/14/2013 22-Sep-14
Device 3 5/14/2013 22-Sep-14
Device 4 5/14/2013 22-Sep-14
Device 5 5/14/2013 22-Sep-14
Device 6 5/14/2013 22-Sep-14
Device 7 5/14/2013 22-Sep-14
Results from using TO_DATE
SELECT device_name, TO_DATE(read_date, 'DD-MON-YY'), sysdate
------- Data Returned by query ----------
ORA-01861: literal does not match format string

Given your data, the proper format is very probably MM/DD/YYYY. It will match both single and double digits months or days.
MM : month from 01 to 12 (leading 0 not mandatory by default)
\ : any punctuation sign
DD : day from 01 to 31 (leading 0 not mandatory by default)
\ : any punctuation sign
YYYY : year
See Oracle's documentation about datetime Format Models for the details.
Here is example:
-- Some test data
WITH testdata AS (
SELECT '5/14/2013' as d FROM DUAL
UNION SELECT '05/14/2013' FROM DUAL
UNION SELECT '5/1/2013' FROM DUAL
UNION SELECT '5/01/2013' FROM DUAL
UNION SELECT '6-6-2013' FROM DUAL)
-- Actual query demonstrating the use of the MM/DD/YYYY format
select d, TO_DATE(d,'MM/DD/YYYY') FROM testdata
Producing:
D TO_DATE(D,'MM/DD/YYYY')
05/14/2013 05/14/2013
5/01/2013 05/01/2013
5/1/2013 05/01/2013
5/14/2013 05/14/2013
6-6-2013 06/06/2013
If you really need to enforce the fact that your date components are separated by / (and not by any other punctuation sign), you should use fxfmMM/DD/YYYY instead:
the fx flag force strict comparison, both for punctuation and number of digits in the format
the fm flag relax that comparison to allow up to the number of specified digits

SELECT device_name, TO_DATE(read_date, 'DD/MM/YYYY'), sysdate

Firstly, your design is flawed. You should never have DATE as VARCHAR2 data type.
Secondly, while comparing the dates, and converting a date literal to a DATE, always specify the same format mask on both sides.
To convert a string to date, use TO_DATE with proper format mask.
To convert a date to string, use TO_CHAR with same format mask.
Make sure, while comparing the values in an expression, you explicitly convert the data type on either sides of the comparison operator to avoid implicit data type conversion.
And you must read this excellent article bybEd Stevens, http://edstevensdba.wordpress.com/2011/04/07/nls_date_format/

Related

Create a datetime value from separate date time columns

I have individual columns for year, month, day, hour, minute, second and millisecond. I need to combine them all together into a date timestamp. I am able to make a date value, but can't seem to make a valid time one. I am working in Oracle and I have sample below. I'd like my value to look like the createtime column.
This did the trick:
to_timestamp(XT.MONTH1||'-'||XT.DAY1||'-'||XT.YEAR||' '|| xt.hour24||'.'||xt.minute||'.'||xt.second||'.'||xt.milliseconds,'MM-DD-YYYY HH24:MI:SS.FF')
That is quite easy but not pretty. You convert the columns from datatype NUMBER to VARCHAR2 padded with leading zeros, using fm to suppress any spaces for + or -:
SELECT TO_CHAR(month,'fm00') FROM mytable;
05
Next, you glue the converted columns together with the || operator to get a single, long, string:
SELECT TO_CHAR(year,'fm0000')||TO_CHAR(month,'fm00')|| ...
20200519...
This long string can now be converted to datatype DATE, or, in your case TIMESTAMP as you have milliseconds. You need to specify the date format you used, f.i. 'YYYYMMDD...'
SELECT TO_TIMESTAMP(TO_CHAR(year,'fm0000')|| ... , 'YYYYMM')
A complete example looks like:
SELECT TO_TIMESTAMP(
TO_CHAR(year,'fm0000')||TO_CHAR(month,'fm00')||
TO_CHAR(day,'fm00')||TO_CHAR(hour24,'fm00')||
TO_CHAR(minute,'fm00')||TO_CHAR(second,'fm00')||
TO_CHAR(ms,'fm000')
, 'YYYYMMDDHH24MISSFF3')
FROM (-- your table, as a mockup, I'll use DUAL
SELECT 2020 as year, 5 as month, 19 as day,
13 as hour24, 7 as minute, 10 as second,
300 as ms
FROM DUAL);
2020-05-19 13:07:10,300000000
EDIT:
The fill mode modifier fm supresses a leading space for positive numbers (to make room for the - sign for negative numbers). All parts of a date are positive, so you get a lot of spaces in your string.
SELECT TO_CHAR(x,'99'), TO_CHAR(x,'fm99')
FROM (SELECT -10 as x FROM DUAL UNION ALL SELECT 10 FROM DUAL);
| 10|10|
|-10|-10|
The documentation is a bit hidden under Format Model Modifiers.
Come to think of it, you might as well keep the spaces and adjust your format model:
SELECT TO_TIMESTAMP(
TO_CHAR(year,'0000')||TO_CHAR(month,'00')||
TO_CHAR(day,'00')||TO_CHAR(hour24,'00')||
TO_CHAR(minute,'00')||TO_CHAR(second,'00')||
TO_CHAR(ms,'000')
,' YYYY MM DD HH24 MI SS FF3')
FROm (SELECT 2020 as year, 5 as month, 19 as day,
13 as hour24, 7 as minute, 10 as second,
300 as ms FROM DUAL);

inserting systimestamp that is precision 9

I'm trying to get a oracle timestamp(9) populated with the full 9 precision at instert with systimestamp
column MY_TS format 99999999999999999999999999
SElECT TO_NUMBER(TO_CHAR(systimestamp, 'YYYYMMDDHH24MISSSSSFF'), '99999999999999999999999999') as MY_TS FROM DUAL;
CREATE TABLE T1 (MY_TS timestamp(9));
INSERT INTO T1 VALUES(systimestamp);
SELECT TO_NUMBER(TO_CHAR(MY_TS, 'YYYYMMDDHH24MISSSSSFF'), '99999999999999999999999999') as MY_TS FROM T1;
Yeilds
MY_TS
---------------------------
20180802152155270705139
Table created.
1 row created.
MY_TS
---------------------------
20180802152155270735103000
Which seems to be loosing the last 3 digit precision.
I keep reading that the Oracle default for systimestamp is 6, but can be modified. Yet I can't seem to run into a page that says how that is done (most are concerned with formation from the value).
Which seems to be loosing the last 3 digit precision.
It is not losing the last 3 digits precision; the FF format model is showing the default precision of the data type:
FF [1..9]
Fractional seconds; no radix character is printed (use the X format element to add the radix character). Use the numbers 1 to 9 after FF to specify the number of digits in the fractional second portion of the datetime value returned. If you do not specify a digit, then Oracle Database uses the precision specified for the datetime datatype or the datatype's default precision.
SELECT TO_CHAR(systimestamp, 'YYYYMMDDHH24MISSFF') as MY_TS FROM DUAL
UNION ALL
SELECT 'YYYYMMDDHHMMSSFFFFFFFFF' FROM DUAL;
Output:
MY_TS
-----------------------
20180802230042489334
YYYYMMDDHHMMSSFFFFFFFFF
So, it is only outputting fractional seconds to microsecond precision which is the default precision:
TIMESTAMP [(fractional_seconds_precision)]
Year, month, and day values of date, as well as hour, minute, and second values of time, where fractional_seconds_precision is the number of digits in the fractional part of the SECOND datetime field. Accepted values of fractional_seconds_precision are 0 to 9. The default is 6. The default format is determined explicitly by the NLS_DATE_FORMAT parameter or implicitly by the NLS_TERRITORY parameter. The sizes varies from 7 to 11 bytes, depending on the precision. This datatype contains the datetime fields YEAR, MONTH, DAY, HOUR, MINUTE, and SECOND. It contains fractional seconds but does not have a time zone.
If you change the precision of the timestamp:
SELECT TO_CHAR(systimestamp(9), 'YYYYMMDDHH24MISSFF') as MY_TS FROM DUAL
UNION ALL
SELECT 'YYYYMMDDHHMMSSFFFFFFFFF' FROM DUAL;
Output:
MY_TS
-----------------------
20180802230708135745000
YYYYMMDDHHMMSSFFFFFFFFF
Which gives a TIMESTAMP with 9 digits precision.
You could also specify the precision in the output format using the FF9 format model (rather than just FF which uses the data type's default precision):
SELECT TO_CHAR(systimestamp, 'YYYYMMDDHH24MISSFF9') as MY_TS FROM DUAL
UNION ALL
SELECT 'YYYYMMDDHHMMSSFFFFFFFFF' FROM DUAL;
Output:
MY_TS
-----------------------
20180802230936652602000
YYYYMMDDHHMMSSFFFFFFFFF

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 How to parse text value(VARCHAR) and then get milliseconds value?

There are varchar column may contain this examples (only 3 variants of values):
Oct 15, 2013 |
15/10/2013 |
2013-10-15
Need to update column values and set the appropriate values for milliseconds: 1381723200000. Without changing type column.
Oracle Perspective:
If you want to extract Milliseconds from a string with YYYY-DD-MM format or whatever (without milliseconds)
SELECT
TO_CHAR( TO_TIMESTAMP ( '2013-10-15',
'YYYY-MM-DD' ),'FF9')
FROM
DUAL;
will give you 000000000 always in Oracle, since there is no milliseconds value stored in your string.
But, if you want to convert the date into milliseconds, then
Milliseconds since when???
SELECT
( TO_DATE ( '2013-10-16',
'YYYY-MM-DD' ) -- starting date
- TO_DATE ( '2013-10-15',
'YYYY-MM-DD' )) -- ending date
*24*60*60*1000 -- milliseconds multiplication factor
FROM
DUAL;

Subtracting Dates in Oracle - Number or Interval Datatype?

I have a question about some of the internal workings for the Oracle DATE and INTERVAL datatypes. According to the Oracle 11.2 SQL Reference, when you subtract 2 DATE datatypes, the result will be a NUMBER datatype.
On cursory testing, this appears to be true:
CREATE TABLE test (start_date DATE);
INSERT INTO test (start_date) VALUES (date'2004-08-08');
SELECT (SYSDATE - start_date) from test;
will return a NUMBER datatype.
But now if you do:
SELECT (SYSDATE - start_date) DAY(5) TO SECOND from test;
you get an INTERVAL datatype. In other words, Oracle can convert the NUMBER from the DATE subtraction into an INTERVAL type.
So now I figured I could try putting in a NUMBER datatype directly in the brackets (instead of doing 'SYSDATE - start_date' which results in a NUMBER anyways):
SELECT (1242.12423) DAY(5) TO SECOND from test;
But this results in the error:
ORA-30083: syntax error was found in interval value expression
So my question is: what's going on here? It seems like subtracting dates should lead to a NUMBER (as demonstrated in SELECT statement #1), which CANNOT be automatically cast to INTERVAL type (as demonstrated in SELECT statement #3). But Oracle seems to be able to do that somehow if you use the DATE subtraction expression instead of putting in a raw NUMBER (SELECT statement #2).
Thanks
Ok, I don't normally answer my own questions but after a bit of tinkering, I have figured out definitively how Oracle stores the result of a DATE subtraction.
When you subtract 2 dates, the value is not a NUMBER datatype (as the Oracle 11.2 SQL Reference manual would have you believe). The internal datatype number of a DATE subtraction is 14, which is a non-documented internal datatype (NUMBER is internal datatype number 2). However, it is actually stored as 2 separate two's complement signed numbers, with the first 4 bytes used to represent the number of days and the last 4 bytes used to represent the number of seconds.
An example of a DATE subtraction resulting in a positive integer difference:
select date '2009-08-07' - date '2008-08-08' from dual;
Results in:
DATE'2009-08-07'-DATE'2008-08-08'
---------------------------------
364
select dump(date '2009-08-07' - date '2008-08-08') from dual;
DUMP(DATE'2009-08-07'-DATE'2008
-------------------------------
Typ=14 Len=8: 108,1,0,0,0,0,0,0
Recall that the result is represented as a 2 seperate two's complement signed 4 byte numbers. Since there are no decimals in this case (364 days and 0 hours exactly), the last 4 bytes are all 0s and can be ignored. For the first 4 bytes, because my CPU has a little-endian architecture, the bytes are reversed and should be read as 1,108 or 0x16c, which is decimal 364.
An example of a DATE subtraction resulting in a negative integer difference:
select date '1000-08-07' - date '2008-08-08' from dual;
Results in:
DATE'1000-08-07'-DATE'2008-08-08'
---------------------------------
-368160
select dump(date '1000-08-07' - date '2008-08-08') from dual;
DUMP(DATE'1000-08-07'-DATE'2008-08-0
------------------------------------
Typ=14 Len=8: 224,97,250,255,0,0,0,0
Again, since I am using a little-endian machine, the bytes are reversed and should be read as 255,250,97,224 which corresponds to 11111111 11111010 01100001 11011111. Now since this is in two's complement signed binary numeral encoding, we know that the number is negative because the leftmost binary digit is a 1. To convert this into a decimal number we would have to reverse the 2's complement (subtract 1 then do the one's complement) resulting in: 00000000 00000101 10011110 00100000 which equals -368160 as suspected.
An example of a DATE subtraction resulting in a decimal difference:
select to_date('08/AUG/2004 14:00:00', 'DD/MON/YYYY HH24:MI:SS'
- to_date('08/AUG/2004 8:00:00', 'DD/MON/YYYY HH24:MI:SS') from dual;
TO_DATE('08/AUG/200414:00:00','DD/MON/YYYYHH24:MI:SS')-TO_DATE('08/AUG/20048:00:
--------------------------------------------------------------------------------
.25
The difference between those 2 dates is 0.25 days or 6 hours.
select dump(to_date('08/AUG/2004 14:00:00', 'DD/MON/YYYY HH24:MI:SS')
- to_date('08/AUG/2004 8:00:00', 'DD/MON/YYYY HH24:MI:SS')) from dual;
DUMP(TO_DATE('08/AUG/200414:00:
-------------------------------
Typ=14 Len=8: 0,0,0,0,96,84,0,0
Now this time, since the difference is 0 days and 6 hours, it is expected that the first 4 bytes are 0. For the last 4 bytes, we can reverse them (because CPU is little-endian) and get 84,96 = 01010100 01100000 base 2 = 21600 in decimal. Converting 21600 seconds to hours gives you 6 hours which is the difference which we expected.
Hope this helps anyone who was wondering how a DATE subtraction is actually stored.
You get the syntax error because the date math does not return a NUMBER, but it returns an INTERVAL:
SQL> SELECT DUMP(SYSDATE - start_date) from test;
DUMP(SYSDATE-START_DATE)
--------------------------------------
Typ=14 Len=8: 188,10,0,0,223,65,1,0
You need to convert the number in your example into an INTERVAL first using the NUMTODSINTERVAL Function
For example:
SQL> SELECT (SYSDATE - start_date) DAY(5) TO SECOND from test;
(SYSDATE-START_DATE)DAY(5)TOSECOND
----------------------------------
+02748 22:50:04.000000
SQL> SELECT (SYSDATE - start_date) from test;
(SYSDATE-START_DATE)
--------------------
2748.9515
SQL> select NUMTODSINTERVAL(2748.9515, 'day') from dual;
NUMTODSINTERVAL(2748.9515,'DAY')
--------------------------------
+000002748 22:50:09.600000000
SQL>
Based on the reverse cast with the NUMTODSINTERVAL() function, it appears some rounding is lost in translation.
A few points:
Subtracting one date from another results in a number; subtracting one timestamp from another results in an interval.
Oracle converts timestamps to dates internally when performing timestamp arithmetic.
Interval constants cannot be used in either date or timestamp arithmetic.
Oracle 11gR2 SQL Reference Datetime Matrix
Use extract() function to retrieve hour / minute / seconds from interval value. See below example, how to get hours from two timestamp columns. Hope this helps!
select INS_TS, MAIL_SENT_TS, extract( hour from (INS_TS - MAIL_SENT_TS) ) hourDiff from MAIL_NTFCTN;
select TIMEDIFF (STR_TO_DATE('07:15 PM', '%h:%i %p') , STR_TO_DATE('9:58 AM', '%h:%i %p'))