Varchar to Timestamp but varchar data is yyyy-mm-dd-hh:mi:ss:ff format - oracle

My source is from Oracle and the col1 is varchar2(26) but the value looks like YYYY-MM-DD-hh:mi:ss:ff (Sample rec: 2014-08-15-02.03.34.979946).
I have to extract only 6 months records based on COL1. Since there is a hypen between date part and time part - i could not consider as timestamp. Is there any idea how to have this as timestamp to lookup only 6 months data.

If it is possible at all, fix the data first. Storing timestamps in string data type is terrible. How do you know you don't have a time like 25:30:00 in the strings? Or a date like February 30? Besides, you can't really use an index on that column (so queries will be very slow), you will have to write a lot of code whenever referencing that column, etc.
Anyway - to deal with the immediate problem, use TO_TIMESTAMP(), exactly with the format model you show in your post - including the dash between the date part and the time part. Something like this:
select case when to_timestamp('2014-08-15-02.03.34.979946', 'YYYY-MM-DD-HH24:MI:SS.FF')
>= systimestamp - interval '6' month
then 'TRUE' else 'FALSE' end
as result
from dual;
RESULT
------
FALSE
EDIT: As Alex Poole points out (correctly as always) in a Comment below this Answer, interval arithmetic won't work correctly in all cases. It is better, than, to use something like
cast ( timestamp (...., format-model) as date ) <= add_months (sysdate, -6).

Maybe something like this will do:
select *
from your_table
where to_date(substr(col1,1,19),'yyyy-mm-dd-HH24.MI.SS') between add_months(sysdate,-6) and sysdate;
Assuming all the data format in col1 is always the same.
Also note that I used HH24 for hour segment, however could be not your case.

You can include the dash in your format model, as #mathguy showed, to convert your string to a timestamp:
select to_timestamp('2014-08-15-02.03.34.979946', 'YYYY-MM-DD-HH24:MI:SS.FF') from dual;
TO_TIMESTAMP('2014-08-15-02.
----------------------------
15-AUG-14 02.03.34.979946000
although unless you explicitly tell it not to be via the FX modifier, Oracle is flexible enough to allow a dash even if the model has a space (see the text below this table in the documentation:
select to_timestamp('2014-08-15-02.03.34.979946', 'YYYY-MM-DD HH24:MI:SS.FF') from dual;
TO_TIMESTAMP('2014-08-15-02.
----------------------------
15-AUG-14 02.03.34.979946000
However, converting all of the values in your col1 column and then comparing them may be a lot of work, and will prevent any index on that string column being used. Given the format, you can convert your date range to string instead, and use string comparison; e.g. to find everything in the six months up to midnight this morning:
select col1 -- or whichever columns you need
from your_table
where col1 >= to_char(cast(add_months(trunc(sysdate), -6) as timestamp), 'YYYY-MM-DD-HH24:MI:SS.FF6')
and col1 < to_char(cast(trunc(sysdate) as timestamp), 'YYYY-MM-DD-HH24:MI:SS.FF6');
or since the time part can be fixed for that example, you can use character literals instead of casting:
select col1 -- or whichever columns you need
from your_table
where col1 >= to_char(add_months(sysdate, -6), 'YYYY-MM-DD"-00:00:00.000000"')
and col1 < to_char(sysdate, 'YYYY-MM-DD"-00:00:00.000000"');
Of course, storing data in the correct native data type would be a much better solution. Any other solution only works at all if your string data actually contains what you think, and the data is all sane (or as sane as it can be in the wrong data type).

Related

Date operations in Oracle

I'm trying to run this queries (Oracle 12c):
SELECT trunc(sysdate) - '25-SEP-18' FROM dual;
SELECT 1 FROM dual WHERE trunc(sysdate) = '04-SEP-19';
CREATE TABLE my_table (order_date date);
INSERT INTO my_table (order_date) VALUES ('04-SEP-19');
I expect implicit conversion and everything is good with the 2 last queries, but for the first i get error ORA-01722: invalid number. NLS_DATE_FORMAT = 'DD-MON-RR'. What is the problem?
The question is WHY is does not work? I didn't find any explanations in documentation.
The documentation has a section on Datetime/Interval Arithmetic which explains what is allowed. The table shows that arithmetic is only allowed between dates, timestamp, intervals and numbers. When you do:
SELECT trunc(sysdate) - '25-SEP-18'
you are trying to subtract a string from a date, which isn't possible. Oracle 'helpfully' tries anyway and interprets the string as a number, effectively doing:
SELECT trunc(sysdate) - to_number('25-SEP-18')
which understandably throws the error you see, "ORA-01722: invalid number". As already said, you should explicitly convert your string to a date:
SELECT trunc(sysdate) - to_number('25-SEP-18', 'DD-MON-RR')
or preferably with a four-digit year, and since you're using a month name it's safer to specify the language that is in:
SELECT trunc(sysdate) - to_number('25-SEP-2018', 'DD-MON-YYYY', 'NLS_DATE_LANGUAGE=ENGLISH')
or more simply, if it's a fixed value, with a date literal:
SELECT trunc(sysdate) - DATE '2018-09-25'
I expect implicit conversion
You should not rely on implicit conversion, particularly where that is influenced by session NLS settins. As well as the date language I already mentioned, someone else running your statement could have a different NLS_DATE_FORMAT setting which could lead to errors or more subtle data mismatches or corruption; e.g.
alter session set nls_date_format = 'DD-MON-YYYY';
SELECT trunc(sysdate) - DATE '2018-09-25' FROM dual;
TRUNC(SYSDATE)-DATE'2018-09-25'
-------------------------------
344
SELECT trunc(sysdate) - to_date('25-SEP-18') FROM dual;
TRUNC(SYSDATE)-TO_DATE('25-SEP-18')
-----------------------------------
730831
SELECT 1 FROM dual WHERE trunc(sysdate) = '04-SEP-19';
no rows selected
CREATE TABLE my_table (order_date date);
INSERT INTO my_table (order_date) VALUES ('04-SEP-19');
The second query gets a much bigger value than expected; and the third gets no rows back from dual.
Looking at the implicitly converted date shows you why:
SELECT to_char(order_date, 'SYYYY-MM-DD HH24:MI:SS') FROM my_table;
TO_CHAR(ORDER_DATE,'
--------------------
0019-09-04 00:00:00
With a YYYY mask (and no FX modifier) a 2-digit year value like 19 is converted as 0019, not 2019. That sort of problem could go unnoticed for some time, giving you incorrect results in the meantime.
If the session's format mask had RRRR or - as you have - RR then it would be interpreted as 2019; but the point is that you usually have no control over the settings in another session that runs your code later.
You can also cause performance issues or errors by creating implicit conversions where you didn't expect, or where they behave in a way you didn't expect. Not in this example - "When comparing a character value with a DATE value, Oracle converts the character data to DATE" - but it still comes up. It's better to avoid the possibility.
When dealing with strings with dates in them you should use the to TO_DATE command, otherwise Oracle may not always figure out that the string contains a date.
SELECT trunc(sysdate) - TO_DATE('25-SEP-18') FROM dual;
Even better is to indicate the format of the date within the string
SELECT trunc(sysdate) - TO_DATE('25-SEP-18','DD-MON-RR') FROM dual;

How to solve the error of this select writing in oracle?

I want to do the select from the range 06/01/2019 to 09/01/2019, but the program returns other ranges, like show the image.
Remember that my date field in the database is varchar2, and I store it in the format dd / MM / yyyy
Expanding #user7294900's answer, as you're storing your 'date' value as a string, you have to convert both the column value and your target-range values from strings to actual dates.
If you do:
WHERE DATA_TRANSACAO BETWEEN '06/01/2019' AND '09/01/2019'
then you are doing string comparisons between the column value and those literals, and as a string anything starting with '07' or '08' (and most starting with '06') will be considered part of that range. Oracle is treating them purely as strings, it doesn't know or care that you have the notion of days and months, or that you consider the order to be something different.
If you're really stuck with strings then you need to compare both sides of the comparison to dates:
WHERE TO_DATE(DATA_TRANSACAO, 'DD/MM/YYYY') BETWEEN TO_DATE('06/01/2019', 'DD/MM/YYYY')
AND TO_DATE('09/01/2019', 'DD/MM/YYYY')
or with ANSI literals:
WHERE TO_DATE(DATA_TRANSACAO, 'DD/MM/YYYY') BETWEEN DATE '2019-06-01' AND DATE '2019-01-09'
or (though this is more useful if there can be non-midnight times, which isn't the case in your data):
WHERE TO_DATE(DATA_TRANSACAO, 'DD/MM/YYYY') >= DATE '2019-06-01'
AND TO_DATE(DATA_TRANSACAO, 'DD/MM/YYYY') < DATE '2019-01-10' -- notice one day later
You should also be aware that having to convert the string value for every row in your data before you can compare it means that if you have a simple index on that column it cannot be used, and a full table scan will be done even for a tiny target date range. (You can add a function-based index to speed this up, but that's a sticking plaster approach.)
This kind of thing is why you should be using the correct data type. There is also no need to store the date and time parts separately, and that makes other types of comparison and data extract harder and more complicated than it needs to be too.
I would really recommend that you revisit your data model and change from separate strings for DATA_TRANSACAO and HORA_TRANSACAO to a single DATE column which contains the whole date/time value as the proper data type. You'll then be able to use the simpler form, with no conversion of your table data, of something like (with a made-up combined column name):
WHERE DATAHORA_TRANSACAO >= DATE '2019-06-01'
AND DATAHORA_TRANSACAO < DATE '2019-01-10' -- notice one day later
If you expect 06/08 values to represent days, be explicit:
BETWEEN TO_DATE('06/01/2019','DD/MM/YYYY')
AND TO_DATE('09/01/2019','DD/MM/YYYY')
Currently it return 06/08 values as months, In general when compare date column use dates and not strings
I am strongly ENCOURAGING you to use to_date to avoid ambiguities (compare strings to strings, numbers to numbers, dates to dates -- don't RISK confusion comparing strings to numbers, dates to strings).

Storing timestamp values stored in a varchar2 column into a date column in oracle

I have a column in a table that stores timestamp values as
"2018-01-12 16:13:51.107000000", i need to insert this column into a date column in another table, what format mask do i have to use here..
I have used the mask 'YYYY-MM-DD HH24:MI:SS.FF' but shows 'date format not recognized'.
I am assuming that you were trying to use TO_DATE on your text timestamp data. This won't work, because Oracle dates do not store anything more precise than seconds. Since your timestamps have fractional seconds, you may use TO_TIMESTAMP here, then cast that actual timestamp to a date:
SELECT
CAST(TO_TIMESTAMP('2018-01-12 16:13:51.100000',
'YYYY-MM-DD HH24:MI:SS.FF') AS DATE)
FROM dual;
12.01.2018 16:13:51
Demo
You can do this with a single call to TO_DATE(), but you must give the correct format model. Note that this solution is simpler (and possibly faster - if that matters) than converting to a timestamp and then casting to date.
If you want TO_DATE() to ignore part of the input string, you can use the "boilerplate text" syntax in the format model. That is enclosed in double quotes. For example, if your string included the letter T somewhere and it had to be ignored, you would include "T" in the same position in the format model.
This has some flexibility. In your case, you must ignore the decimal point, and up to nine decimal digits (the maximum for timestamp in Oracle). The format model will allow you to use ".999999999" (or any other digits, but 9999... is used by most programmers) to ignore a decimal point and UP TO nine digits after that.
Demo: (notice the double-quoted "boilerplate text" in the format model)
select to_date('2018-01-12 16:13:51.100000',
'YYYY-MM-DD HH24:MI:SS".999999999"') as dt
from dual;
DT
-------------------
2018-01-12 16:13:51

How to change default date,timestamp dataype for columns in oracle

I have created a table in Oracle in which I have KPI_START_DATE column which is a Date datatype, and KPI_START_TIME which is a TIMESTAMP datatype.
Now I want to modify this date dataype for
KPI_START_DATE to dd/mm/yyyy
and
KPI_START_TIME to HH:MI:SS.
So that user should always enter the date and time in this column in this proper format.
I tried below query but its was giving error:
Alter table KPI_DEFINITION MODIFY(to_char(KPI_START_DATE,'dd/mm/yyyy') )
DATE and TIMESTAMP columns do not have any inherent readable format. The values are stored in Oracle's own internal representation, which has no resemblance to a human-readable date or time. At the point to retrieve or display a value you can convert it to whatever format you want, with to_char().
Both DATE and TIMESTAMP have date and time components (to second precision with DATE, and with fractional seconds with TIMESTAMP; plus time zone information with the extended data types), and you should not try to store them separately as two columns. Have a single column and extract the information you need at any time; to get the information out of a single column but split into two fields you could do:
select to_char(KPI_START, 'dd/mm/yyyy') as KPI_START_DATE,
to_char(KPI_START, 'hh24:mi:ss') as KPI_START_TIME
but you'd generally want both together anyway:
select to_char(KPI_START, 'dd/mm/yyyy hh24:mi:ss')
Also notice the 'hh24' format model to get the 24-hour clock time; otherwise you wouldn't see any difference between 3 a.m. and 3 p.m.
You can store a value in either type of column with the time set to midnight, but it does still have a time component - it is just midnight. You can't store a value in either type of column with just a time component - it has to have a date too. You could make that a nominal date and just ignore it, but I've never seen a valid reason to do that - you're wasting storage in two columns, and making searching for and comparing values much harder. Oracle even provides a default date if you don't specify one (first day of current month). But the value always has both a date and a time part:
create table KPI_DEFINITION (KPI_START date);
insert into KPI_DEFINITION (KPI_START)
values (to_date('27/01/2015', 'DD/MM/YYYY'));
insert into KPI_DEFINITION (KPI_START)
values (to_date('12:41:57', 'HH24:MI:SS'));
select to_char(KPI_START, 'YYYY-MM-DD HH24:MI:SS') from KPI_DEFINITION;
TO_CHAR(KPI_START,'YYYY-MM-DDHH24:MI:SS')
-----------------------------------------
2015-01-27 00:00:00
2015-01-01 12:41:57
Your users should be inserting a single value with both date and time as one:
insert into KPI_DEFINITION (KPI_START)
values (to_date('27/01/2015 12:41:57', 'DD/MM/YYYY HH24:MI:SS'));
select to_char(KPI_START, 'YYYY-MM-DD HH24:MI:SS') from KPI_DEFINITION;
TO_CHAR(KPI_START,'YYYY-MM-DDHH24:MI:SS')
-----------------------------------------
2015-01-27 12:41:57
You can also use date or timestamp literals, and if using to_date() you should always specify the full format - don't rely on NLS settings as they may be different for other users.
You should understand difference between datatype and format. DATE is a datatype. TIMESTAMP is a datatype. None of them have formats, they're just numbers.
When converting character datatype to or from date datatype, format should be applied. It's an attribute of an actual conversion, nothing else.
Look at this:
SQL> create table tmp$date(d date);
Table created
SQL> insert into tmp$date values (DATE '2010-11-01');
1 row inserted
SQL> insert into tmp$date values (DATE '2014-12-28');
1 row inserted
SQL> select d, dump(d) from tmp$date;
D DUMP(D)
----------- ---------------------------------
01.11.2010 Typ=12 Len=7: 120,110,11,1,1,1,1
28.12.2014 Typ=12 Len=7: 120,114,12,28,1,1,1
There is no any 'format' here.
DISPLAYING and STORING are NOT the same when it comes to DATE.
When people say Oracle isn’t storing the date in the format they wanted, what is really happening is Oracle is not presenting the date in the character string format they expected or wanted.
When a data element of type DATE is selected, it must be converted from its internal, binary format, to a string of characters for human consumption. The conversion of data from one type to another is known as known a “conversion”, “type casting” or “coercion”. In Oracle the conversion between dates and character strings is controlled by the NLS_DATE_FORMAT model. The NLS_DATE_FORMAT can be set in any of several different locations, each with its own scope of influence.
I could go on with my leacture over DATE data type, but I am glad that someone has already got a good writeup over this. Please read this https://edstevensdba.wordpress.com/2011/04/07/nls_date_format/

Oracle - Converting Date value TO_CHAR()

Ok, so I want to convert a date value to properly format my date for comparison, however, will converting my "DATE" data type to char affect indexing when comparing on that field? Can I resolve this by doing to_date(tochar())? Any advice on this would be greatly appreciated Thanks!
EDIT - Sorry for the lack of specifics.... basically I need to eliminate the time stamp from my date, I used the following and it appears to work TO_DATE(TO_CHAR, 'YYYY-MM-DD'),'YYYY-MM-DD'), mind you I don't know if this is good practice or not, but at least (or so I think) now it's comparing a DATE against a DATE, and not a string.
If you are doing a comparison, you should not be converting the date to a string. You should be comparing to another date. Otherwise, Oracle won't be able to use a non function-based index on the date column.
In general, that is, you're much better off coding
WHERE some_indexed_date_column = to_date( :string_bind_variable,
<<format mask>> )
rather than
WHERE to_char( some_indexed_date_column,
<<format mask>> ) = :string_bind_variable
Of course, if your bind variable can be a DATE rather than a VARCHAR2, that's even better because then you don't have to do any data type conversion and the optimizer has a much easier time of estimating cardinalities.
If you are trying to do some manipulation of the date-- for example, if you want to compare the day portion while omitting the time portion of the date-- you may want to use function-based indexes. For example, if you wanted to find all the rows that were created some time today
WHERE trunc( some_date_column ) = date '2011-11-04'
you could either create a function-based index on the date column
CREATE INDEX idx_trunc_dt
ON table_name( trunc( some_date_column ) )
or you could rewrite the query to do something like
WHERE some_date_column >= date '2011-11-04'
AND some_date_column < date '2011-11-05'
You should compare dates as dates, not as strings. If comparing a date to a string, convert the string to a date to compare.
IMO, you should convert your comparison value to the format stored in the database. Otherwise, you will need to create a function based index on the DATE column to take advantage of indexing. So, if you have an input character date of, say, 11/4/2011, you could compare it in a where clause thusly:
SELECT ...
FROM your_table
WHERE the_date_column = TO_DATE('11/4/2011','MM/DD/YYYY');

Resources