Cannot filter null Datetime values - oracle

I have a problem that is driving me crazy. I have to query an oracle view that returns some DATETIME values.
The incredible problem is that even if I set the "IS NOT NULL" on the WHERE clause and even if I set the NVL(FECHA_HASTA, FECHA_DESDE), I´m still getting null values!!. How is that possible???
This is the query:
SELECT CUIL as Cuil,
COD_TIPO_CAUSAL as CodTipoCausal,
COD_CONVENIO as CodConvenio,
FECHA_DESDE as FechaDesde,
NVL(FECHA_HASTA, FECHA_DESDE) as FechaHasta
FROM ORGANISMO.VCAUSAL_AUSENCIA
WHERE FECHA_HASTA IS NOT NULL
AND FECHA_HASTA > (SELECT SYSDATE - 180 FROM SYS.DUAL)
AND CUIL IN (SELECT CUIL FROM ORGANISMO.VEMPLEADO WHERE FECHA_EGRESO IS NULL OR FECHA_EGRESO > (SELECT SYSDATE FROM SYS.DUAL))
EDIT:
Here is dump(fecha_hasta, 1016) added:

The dumped values show that the data is corrupt. The internal date format is well-known:
byte 1 - century (excess 100)
byte 2 - year (excess 100)
byte 3 - month
byte 4 - day
byte 5 - hour (excess 1)
byte 6 - minute (excess 1)
byte 7 - seconds (excess 1)
so the fourth byte in the two values that SQL Developer is reporting as null (even though they clearly are not actually null) should not be zero, as there is no day zero.
Based on those rules, 79,9d,2,0,18,3c,3c in hex, which is 121,157,2,0,24,60,60 in decimal, should convert as:
century: 121 - 100 = 21
year: 157 - 100 - 57
month: 2
day: 0
hour: 24 - 1 = 23
minute: 60 - 1 = 59
second: 60 - 1 = 59
or 2157-02-00 23:59:59. Similarly 78,b8,1,0,18,3c,3c converts to 2084-01-00 23:59:59.
Version 18.3 of SQL Developer displays those values, in both the script output and query results windows, as the previous day:
DT DUMPED
------------------- -----------------------------------
01-07-2020 23:59:59 Typ=12 Len=7: 78,78,7,1,18,3c,3c
31-01-2157 23:59:59 Typ=12 Len=7: 79,9d,2,0,18,3c,3c
31-12-2083 23:59:59 Typ=12 Len=7: 78,b8,1,0,18,3c,3c
01-07-2018 00:00:00 Typ=12 Len=7: 78,76,7,1,1,1,1
whereas db<>fiddle shows the zero-day values.
So, since they are not actually null, it's reasonable that is not null and nvl() didn't affect them, and then it's up to the client or application as to how to present them.
The real issue is that you seem to have corrupted data in the tables underlying the view you're querying, so that needs to be investigated and fixed - assuming the invalid values can be safely identified, and you can find out what they should have been in the first place, which might be a struggle. Just filtering them out, either as part of the view or in your query, won't be simple though - unless you can filter out dates in the future. And assuming all the corruption is both that obvious and pushing dates into the future; on some level you have to question the validity of all of those dates... there could be much more subtle corruptions that look OK.
And then whatever process or tool caused the corruption needs to be tracked down and fixed so it doesn't happen again. Lots of things can cause corruption of course, but I believe imp used to have a bug that could corrupt dates and numbers, and OCI programs can too.

Related

Oracle - Date - Date Issue

I am trying to calculate the difference between dates. I only need the decimal of a day so date - date is all I need.
The issue I have is, this works for some records but not others, with no real pattern. Using the below:
select (case when c.call_answered_date is null then c.call_ended_date - c.call_entered_queue_date else 0 end),
C.CALL_ENTERED_SYSTEM_DATE, C.CALL_ENTERED_QUEUE_DATE, C.CALL_ENDED_DATE
I get
abd_wait call_entered_system_date call_entered_queue_date call_ended_date
-------- ------------------------ ----------------------- ---------------
5.78703703703704E-5 06/01/2020 12:30:00 06/01/2020 12:30:11 06/01/2020 12:30:16
0.000844907407407407 06/01/2020 12:35:38 06/01/2020 12:35:49 06/01/2020 12:37:02
So the second line works as expected, the first does not. But I do not know why.
I need them to all be a decimal of a day like the second line.
Please help.
The first line has the correct value and is just in scientific notation: 5.78703703703704E-5
is the same as: 0.0000578703703703704 which is 5 seconds expressed as a fraction of a day: (16-11)/24/60/60. The value is still a (decimal) number it is just displayed in a slightly different format.
If you want it as a fixed formatted decimal string then use TO_CHAR to give the number an explicit format:
select case
when c.call_answered_date is null
then TO_CHAR(
c.call_ended_date - c.call_entered_queue_date,
'0.000000000000000000'
)
else '0'
end,
C.CALL_ENTERED_SYSTEM_DATE,
C.CALL_ENTERED_QUEUE_DATE,
C.CALL_ENDED_DATE
FROM your_table c

Date manipulation in Oracle

Could someone explain to me why the result of
SELECT CEIL(MONTHS_BETWEEN(CURRENT_DATE, TO_DATE('30/11/47'))/12) AS AGE FROM DUAL;
Give a negative number?? (AGE =-29)
In the other hand, the following result is OK (AGE = 61)
SELECT CEIL(MONTHS_BETWEEN(CURRENT_DATE, TO_DATE('30/11/57'))/12) AS AGE FROM DUAL;
I tried to change the default nls_date_format as suggested!
Initially I had
After altering session with nls_date_format, no change!
It gives a negative number because it has incorrectly guessed the century.
Be more specific with your date format and the result will be predicable and correct.
ALTER SESSION SET nls_date_format ='DD-MON-YYYY';
SELECT CEIL(MONTHS_BETWEEN(CURRENT_DATE, TO_DATE('30-NOV-1947'))/12) AS AGE FROM DUAL;
AGE
----------
71
1 row selected.
The reason the 1957 date works as you expect, I think, is because you have your NLS_DATE_FORMAT set to DD\MM\RR which guesses the century differently (iirc it makes it 20 for years less than 50 and 19 from year 50 onwards, instead of YY which always uses 20. Use YYYY and then Oracle doesn't need to guess what you mean.
You must specify the format, e.g. TO_DATE('30/11/47', 'DD-MM-RR')
However does 47 mean 1947?
TO_DATE('30/11/47', 'DD-MM-RR') and TO_DATE('30/11/47', 'DD-MM-YY') will result to 2049-11-30 because 47 is lower than 50 (for RR), resp. YY means current century.
Better use TO_DATE('30/11/1947', 'DD-MM-YYYY')

MONTHS_BETWEEN Function

Can someone help me understand the working of Oracle Months_Between Function?
If I query select MONTHS_BETWEEN('02-28-2015', '01-28-2015')
I get an integer value of 1 but if I query
select MONTHS_BETWEEN('02-28-2015', '01-29-2015') I get 0.96.
Refer to the documentation. https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions089.htm
Note - the "31 day month" convention may cause weird results around month-ends. Consider:
select months_between(date '2016-07-02', date '2016-07-01') as one_day,
months_between(date '2016-07-01', date '2016-06-30') as another_day
from dual;
ONE_DAY ANOTHER_DAY
---------- -----------
.032258065 .064516129
1 row selected.
As if June had 31 days. It doesn't, but months_between treats it as though it did.
If you're working with just trying to determine the number of months in a set of months and don't care about the days. I find myself in this situation often... You can do a bit of date manipulation which is rather reliable for determining the number of months in a set of months. Say for instance Jul - Sep while starting with dates.
Thusly:
WITH MONTHS AS (
SELECT
SYSDATE DATE_ONE
, SYSDATE+57 DATE_TWO
FROM DUAL
)
SELECT
m.*
,TO_CHAR(m.DATE_ONE,'MON') START_MONTH
,TO_CHAR(m.DATE_TWO,'MON') END_MONTH
,MONTHS_BETWEEN(m.DATE_TWO,m.DATE_ONE) UNEXPECTED_RESULT
,MONTHS_BETWEEN(LAST_DAY(m.DATE_TWO),LAST_DAY(ADD_MONTHS(m.DATE_ONE,-1))) EXPECTED_RESULT
FROM MONTHS m
;

Meaning of Oracle's dump(systimestamp) bytes

I'm trying to understand what the bytes from the timestamp set on my DB mean. How do they get computed to generate the more readable date?
I'm using the below query to get the data that I need:
SELECT systimestamp
,DUMP (systimestamp)
,sessiontimezone
FROM dual;
And the output of my above query is:
+-------------------------------------+-----------------------------------------------------------------+------------------+
| systimestamp | dump(systimestamp) | sessiontimezone |
+-------------------------------------+-----------------------------------------------------------------+------------------+
| 31-JUL-15 08.55.06.157047000 +00:00 | Typ=188 Len=20: 223,7,7,31,8,55,6,0,216,88,92,9,0,0,5,0,0,0,0,0 | Europe/Bucharest |
+-------------------------------------+-----------------------------------------------------------------+------------------+
I have found a few resources online explaining what the bytes mean (here) but the rules don't match in my scenario.
For example: 223 is not the century + 100 etc.
The reason I'm trying to do this is because of a problem I'm facing when comparing the values in a timestamp(3) column with systimestamp and I'm trying to write a script to verify if my issue/solution is the same as explained here.
Any help is appreciated.
There a various superficially similar but internally different datetime datatypes. systimestamp is type 188 (and has timezone information); a timestamp literal is type 187 without time zone info and 188 with it; and a plain timestamp column is type 180:
select dump(systimestamp) from dual;
DUMP(SYSTIMESTAMP)
--------------------------------------------------------------------------------
Typ=188 Len=20: 223,7,7,31,9,50,28,11,128,203,79,35,1,0,5,0,0,0,0,0
select dump(timestamp '2015-07-31 08:55:06.157047 +00:00') from dual;
DUMP(TIMESTAMP'2015-07-3108:55:06.157047+00:00')
---------------------------------------------------------------
Typ=188 Len=20: 223,7,7,31,8,55,6,0,216,88,92,9,0,0,5,0,0,0,0,0
select dump(timestamp '2015-07-31 08:55:06.157047') from dual;
DUMP(TIMESTAMP'2015-07-3108:55:06.157047')
---------------------------------------------------------------
Typ=187 Len=20: 223,7,7,31,8,55,6,0,216,88,92,9,0,0,3,0,0,0,0,0
create table t (ts timestamp);
insert into t (ts) values (timestamp '2015-07-31 08:55:06.157047');
select dump(ts) from t;
DUMP(TS)
--------------------------------------------------------------------------------
Typ=180 Len=11: 120,115,7,31,9,56,7,9,92,88,216
Of those, only a timestamp column uses the internal format in the article you linked to, using excess-100 notation for the year.
For the others, the first byte is a base-256 modifier, and the second byte is the base 256 year; so you would interpret it as
223 + (7 * 256) = 2015
You can read more about the internal storage in My Oracle Support document 69028.1. That, and the earlier answer linked to in comments, refer to the two date types, but timestamps are treated the same down to the seconds, and some of the rest can be inferred for type 187/188 - the fractional-seconds part anyway:
Byte 1 - Base 256 year modifier: 223
2 - Base 256 year: 7 (256 * 7 = 1792 + 223 = 2015)
3 - Month: 7
4 - Day: 31
5 - Hours: 8
6 - Minutes: 55
7 - Seconds: 6
8 - Unused?
9 - Base 256 nanoseconds: 216
10 - Base 256 ns modifier 1: 256 * 88 = 22528
11 - Base 256 ns modifier 2: 256 * 256 * 92 = 6029312
12 - Base 256 ns modifier 3: 256 * 256 * 256 * 9 = 150994944
=> actual nanoseconds = 216 + 22528 + 6029312 + 150994944
=> 157047000
13-20 - Time zone data?
For type 120 the fractional seconds are the same but with the bytes reversed.

Oracle - Date substraction in where clause

I'm trying to figure out how to compare the result of a date substraction in a where clause.
Clients subscribed to a service and therefore are linked to a subscription that has an end date. I want to display the list of subscriptions that will come to an end within the next 2 weeks.
I did not designed the databse but noticed that the End_Date column type is a varchar and not a date.. I can't change that.
My problem is the following:
If I try to select the result of the substraction for example with this request:
SELECT(TO_DATE(s.end_date,'YYYY-MM-DD') - TRUNC(SYSDATE)) , s.name
from SUBSCRIPTION s WHERE s.id_acces = 15
This will work and give me the number of days between the end of the subscription and the current date.
BUT now, if I try to include the exact same request in a clause where for comparison:
SELECT s.name
from SUBSCRIPTION S
WHERE (TO_DATE(s.end_date,'YYYY-MM-DD') - TRUNC(SYSDATE)) between 0 and 16
I will get an error: "ORA-01839 : date not valid for month specified".
Any help would be appreciated..
Somewhere in the table you have your date formatted in a different way from YYYY-MM-DD. In your first query you check a certain row (or a set of rows, s.id_acces = 15), which is probably ok, but in the second you scan through all the table.
Try finding these rows with something like,
select end_date from subscription
where not regexp_like(end_date, '[0-9]{4}-[0-9]{2}-[0-9]{2}')
Check your DD value (ie: day of the month). This value must be between 1 and the number of days in the month.
January - 1 to 31
February - 1 to 28 (1 to 29, if a leap year)
March - 1 to 31
April - 1 to 30
May - 1 to 31
June - 1 to 30
July - 1 to 31
August - 1 to 31
September - 1 to 30
October - 1 to 31
November - 1 to 30
December - 1 to 31
" the End_Date column type is a varchar and not a date.. I can't
change that."
If you can't change the date you'll have to chang3 the data. You can identify the rogue values with this function:
create or replace check_date_format (p_str in varchar2) return varchar2
is
d date;
begin
d := to_date(p_str,'YYYY-MM-DD');
return 'VALID';
exception
when others then
return 'INVALID';
end;
You can use this function in a query:
select sid, end_date
from SUBSCRIPTION
where check_date_format(end_date) != 'VALID';
Your choices are:
fix the data so all the dates are in the same format
fix the data and apply a check constraint to enforce future validity
write a bespoke MY_TO_DATE() function which takes a string and applies lots of different date format masks to it in the hope of getting a successful conversion.

Resources