Oracle SQL: Is There a Maximum Date Difference Oracle Can Interpret - sql

I'm working on sql that looks for rows in a table where the rows 'last_run' date + 'frequency' (in minutes), is greater than the current date/time. I've noticed that there appears to be an upper bound for date comparisons Oracle can make sense of.
For example this query;
with tests as
(
select
'TEST 1' as code,
99999999 as frequency,
sysdate as last_run
from dual
union
select
'TEST 2' as code,
99999999999 as frequency,
sysdate as last_run
from dual
)
select
p.*,
(p.last_run + p.frequency / 24 / 60 ) as next_run
from tests p
where (p.last_run + p.frequency / 24 / 60 < sysdate or p.last_run is null)
I would expect this query to return null but instead it returns;
CODE
FREQUENCY
LAST_RUN
NEXT_RUN
TEST 2
99999999999
05-OCT-2021 10:15:46 AM
15-APR-4455 08:54:46 PM
I can solve the problem by setting frequency = null and my other code will recognize that the row need not be considered, but it seems strange to me that Oracle can't recognize that the year 4455 > 2021.
Is there some maximum conceivable date in Oracle that I'm unaware of?
I'm running this in Oracle SQL Developer Version 18.2.0.183 and Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production.

it seems strange to me that Oracle can't recognize that the year 4455 > 2021
It can. The problem is that your year isn't 4455; it's -4455. See this db<>fiddle, showing the result (in a different timezone) with default DD-MON-RR format, your output format, and ISO format with the year sign included (S format element).
CODE
FREQUENCY
LAST_RUN
NEXT_RUN
TEST 2
99999999999
2021-10-05 17:16:21
-4454-03-12 03:55:21
With your frequency of 99999999999 the value you are adding to the current date is 69444444 days, which is (very roughly) 190128 years - clearly that's going to put you well past the maximum date of 9999-12-31; and indeed with a different value like 9999999999 (one less digit), which is 6944444 days or roughly 19012 years, you get an error - also shown in that db<>fiddle.
The issue seems to be how Oracle manipulates its internal representation when it does the calculation; in adding that large value it appears that the year - which is stored in two bytes - is overflowing and wrapping.
Using the type-13 version, 190128+2021 = 192149, which is (256 * 750) + 149. 750 doesn't fit in one byte, so you get the modulus, which is 238. That would make the first two bytes of the calculated date come out as 149,238. That actually corresponds to year -4459:
select dump(date '-4459-01-01') from dual;
Typ=13 Len=8: 149,238,1,1,0,0,0,0
which is close enough to demonstrate that's what's happening - given that the calculation is outside the expected range and it's probably trying to do invalid leap day calculations in there somewhere. The point, though, is that the generated, wrapped, value represents a valid year in that internal notation.
With the lower value, 19012+2021 = 20133, which is (256 * 82) + 41. Now there is no wrapping, so the first two bytes of the calculated date come out as 41,82. That is now not a valid year, so Oracle knows to throw the ORA-01841 exception.
So, you need to limit the frequency value to a number that won't ever go past 9999-12-31, or test it at run time against 9999-12-31 minus the current date - and if it's too big, ignore it. That's if you want what appears to be a magic number at all.

There is a maximum date in Oracle, it is 9999-12-31 23:59:59 in YYYY-MM-DD HH24:MI:SS format. Here is a screenshot of the Oracle Documentation:
Here is the Oracle Documentation which talks about the valid date values (LINK)
The problem is that you are adding ~190,258 years with your second query. Likely overflowing the buffer many times over. It just so happened that you ended up back at the value you did.

Related

Ingres multiplication gives wrong result

I have an Ingres table with following columns
from_date ingresdate
to_date ingresdate
model_amt money
The dates can reflect a period of any number of days, and the model_amt is always a weekly figure. I need to work out the the total model_amt for the period
To do this I need to know how many days are covered by the period, and then divide model_amt by 7, and multiply it by the number of days
however, I am getting incorrect results using the code below
select model_amt, date_part('day',b.to_date - b.from_date),
model_amt / 7 * int4( (date_part('day',b.to_date - b.from_date)) )
from table
For example, where model_amt = 88.82 and the period is for 2 weeks, I get the following output
+-------------------------------------------------------+
¦model_amt ¦col2 ¦col3 ¦
+--------------------+-------------+--------------------¦
¦ #88.82¦ 14¦ #177.66¦
+-------------------------------------------------------+
But 88.82 / 7 * 14 = 177.64, not 177.66?
Any ideas what is going on? The same issue happens regardless of whether I include the int4 function around the date_part.
* Update 15:28 *
The solution was to add a float8 function around the model_amt
float8(model_amt)/ 7 * interval('days', to_date - from_date)
Thanks for the responses.
In computers, floating point numbers are notoriously inaccurate. You can multiply do all kinds of basic mathematics calculations on floating point numbers and they'll be off by a few decimals.
Some information can be found here; but its very googleable :). http://effbot.org/pyfaq/why-are-floating-point-calculations-so-inaccurate.htm
Generally to avoid inaccuracies, you need to use a language specific feature (e.g. BigDecimal in Java) to "perfectly" store the decimals. Alternatively, you can represent decimals as separate integers (e.g. main number is one integer and the decimal is another integer) and combine them later.
So, I suspect this is just ingres showing the normal floating point inaccuracies and that there are known workarounds for it in that database.
Update
Here's a support article from Actian specifically about ingres floating point issues which seems useful: https://communities.actian.com/s/article/Floating-Point-Numbers-Causes-of-Imprecision.

SQL Server adding two time columns in a single table and putting result into a third column

I have a table containing two time columns like this:
Time1 Time2
07:34:33 08:22:44
I want to add the time in both these columns and put the result of addition into a third column may be Time3
Any help would be appreciated..Thanks
If the value you expect as the result is 15:57:17 then you can get it by calculating for instance the number of seconds from midnight for Time1 and add that value to Time2:
select dateadd(second,datediff(second,0,time1),time2) as Time3
from your_table
I'm not sure how meaningful adding two discrete time values together is though, unless they are meant to represent duration in which case the time datatype might not be the best as it is meant for time of day data and only has a range of 00:00:00.0000000 through 23:59:59.9999999 and an addition could overflow (and hence wrap around).
If the result you want isn't 15:57:17 then you should clarify the question and add the desired output.
The engine doesn't understand addition of two time values, because it thinks you can't add two times of day. You get:
Msg 8117, Level 16, State 1, Line 8
Operand data type time is invalid for add operator.
If these are elapsed times, not times of day, you could take them apart with DATEPART, but in SQL Server 2008 you will have to use a CONVERT to put the value back together, plus have all the gymnastics to do base 60 addition.
If you have the option, it would be best to store the time values as NUMERIC with a positive scale, where the unit of measure is hours, and then break them down when finally reporting them. Something like this:
DECLARE
#r NUMERIC(7, 5);
SET #r = 8.856;
SELECT FLOOR(#r) AS Hours, FLOOR(60 * (#r - FLOOR(#r))) AS Minutes, 60 * ((60 * #r) - FLOOR(60 * #r)) AS Seconds
Returns
Hours Minutes Seconds
8 51 21.60000
There is an advantage to writing a user-defined function to do this, to eliminate the repeated 60 * #r calculations.

Detecting Invalid Dates in Oracle 11g database (ORA-01847 )

I am querying an Oracle 11.2 instance to build a small data mart that includes extracting the date of birth and date of death of people.
Unfortunately the INSERT query (which takes its data from a SELECT) fails due to ORA-01847 (day of month must be between 1 and last day of month).
To find my bad dates I first did:
SELECT extract(day FROM SOME_DT_TM),
extract(month FROM SOME_DT_TM),
COUNT(*)
FROM PERSON
GROUP BY extract(day FROM SOME_DT_TM), extract(month FROM SOME_DT_TM)
ORDER BY COUNT(*) DESC;
It gave me 367 rows, one for each day of the year including NULL and February-29th (leap year). True for the other date column as well, so it looks like the data is fine from a SELECT perspective.
However if I set logging up on my insert
create table registry_new_dates
(some_dob date, some_death_date date);
exec dbms_errlog.create_error_log('SOME_NEW_DATES');
And then run my long insert query:
SELECT some_dob,some_death_date,ora_err_mesg$ FROM ERR$_SOME_NEW_DATES;
I get the following weird results (first 3 rows shown) which makes me think that zip codes have been somehow inserted instead of dates for the 2nd column.
31-DEC-25 35244 "ORA-01847: day of month must be between 1 and last day of month"
13-DEC-33 35244-3402 "ORA-01847: day of month must be between 1 and last day of month"
23-JUN-58 35235 "ORA-01847: day of month must be between 1 and last day of month"
My question is - how do I detect these bad rows (there are 11 apparentlyh) with an SQL statement so I can fix or remove them. Fixing them in the originating table is not an option (no write privileges). I tried using queries like this:
SELECT DECEASED_DT_TM
FROM WH_CLN_PERSON
WHERE DECEASED_DT_TM LIKE '35%'
AND rownum<3;
But it did not find the offending rows.
Not sure if you are still actively researching this (or if you got an answer already).
To find the rows with the bad data, can't you instead select the DOB and the date of death, and express the WHERE clause in terms of DOB - like so:
...WHERE some_dob = to_date('31-DEC-25')
? After you find those rows, you may want to do another query on just one or two of those rows, including a calculated column: dump(date of death). Then post that. We can learn a lot from the dump - the internal representation of the so-called "date" (which may very well be a ZIP code instead). With that in hand we may be able to figure out what's stored, and how to hunt for it.

sqlalchemy select by date column only x newset days

suppose I have a table MyTable with a column some_date (date type of course) and I want to select the newest 3 months data (or x days).
What is the best way to achieve this?
Please notice that the date should not be measured from today but rather from the date range in the table (which might be older then today)
I need to find the maximum date and compare it to each row - if the difference is less than x days, return it.
All of this should be done with sqlalchemy and without loading the entire table.
What is the best way of doing it? must I have a subquery to find the maximum date? How do I select last X days?
Any help is appreciated.
EDIT:
The following query works in Oracle but seems inefficient (is max calculated for each row?) and I don't think that it'll work for all dialects:
select * from my_table where (select max(some_date) from my_table) - some_date < 10
You can do this in a single query and without resorting to creating datediff.
Here is an example I used for getting everything in the past day:
one_day = timedelta(hours=24)
one_day_ago = datetime.now() - one_day
Message.query.filter(Message.created > one_day_ago).all()
You can adapt the timedelta to whatever time range you are interested in.
UPDATE
Upon re-reading your question it looks like I failed to take into account the fact that you want to compare two dates which are in the database rather than today's day. I'm pretty sure that this sort of behavior is going to be database specific. In Postgres, you can use straightforward arithmetic.
Operations with DATEs
1. The difference between two DATES is always an INTEGER, representing the number of DAYS difference
DATE '1999-12-30' - DATE '1999-12-11' = INTEGER 19
You may add or subtract an INTEGER to a DATE to produce another DATE
DATE '1999-12-11' + INTEGER 19 = DATE '1999-12-30'
You're probably using timestamps if you are storing dates in postgres. Doing math with timestamps produces an interval object. Sqlalachemy works with timedeltas as a representation of intervals. So you could do something like:
one_day = timedelta(hours=24)
Model.query.join(ModelB, Model.created - ModelB.created < interval)
I haven't tested this exactly, but I've done things like this and they have worked.
I ended up doing two selects - one to get the max date and another to get the data
using the datediff recipe from this thread I added a datediff function and using the query q = session.query(MyTable).filter(datediff(max_date, some_date) < 10)
I still don't think this is the best way, but untill someone proves me wrong, it will have to do...

how to display number value in words

Q. Display the number value in Words and output should look like this
SAL In_Words
--------- -----------------------------------------------------
800 eight hundred
1600 one thousand six hundred
1250 one thousand two hundred fifty
And, I'm still didn't figure out, how this query is the solution for the above output.
select sal, to_char(to_date(sal,'j'),'Jsp') in_words from emp
What to_date is doing here ? Anyone have any idea about this query ?
So how the query works? Well here’s why:
select to_char(to_date(:number,'j'),'jsp') from dual;
If you look into the inner most part of the query to_date(:number,'j') the ‘j’ or J is the Julian Date (January 1, 4713 BC), basically this date is been used for astronomical studies.
So to_date(:number,'j') it take the number represented by number and pretend it is a julian date, convert into a date.
If you pass 3 to number, so it will convert date to 3rd Jan 4713 BC, it means 3 is added to the Julian date.
select to_char(to_date(3,'j'),'jsp') from dual;
Now to_char(to_date(3,'j'),'jsp'), jsp = Now; take that date(to_date(3,'j')) and spell the julian number it represents, the output is:
TO_CH
-----
three
There is a limitation while using Julian dates ,It ranges from 1 to 5373484. That’s why if you put the values after 5373484, it will throw you an error as shown below:
ORA-01854: julian date must be between 1 and 5373484
Hi everyone, it is interesting this topic. I remember when I was learning Oracle in 2005 one of the instructor required me to write a PL/SQL code to convert numbers in words, it was a whole two pages code to reach this.
Here is some reference that could help us to understand the Julian day, that is why we use the letter 'j' or 'J' during this operation.
First there is a website that has the example and explanation about "How To Convert Number Into Words Using Oracle SQL Query":
http://viralpatel.net/blogs/convert-number-into-words-oracle-sql-query/
Second if you want to know more about "Julian day" go to:
http://en.wikipedia.org/wiki/Julian_day
Third if you want to know more about who proposed the Julian day number in 1583, it was by "Joseph Scaliger":
http://en.wikipedia.org/wiki/Joseph_Justus_Scaliger
It is not make sence for me continue repiting what another author in these websites have made, that is why I just posted the link you can access them and read what you need to understand how query like this works:
SELECT TO_CHAR (TO_DATE (2447834, 'j'), 'jsp') FROM DUAL;
//Output: two million four hundred forty-seven thousand eight hundred thirty-four
I've never heard of a DBMS with a built-in function to do as you ask. You'll need a table of number-names, to join to that once per digit using modulo arithmetic, and string concatenation to produce one In_Words column. Plus some logic to eliminate leading zeros. It will take time to write.
J stands for Julian day - the number of days since January 1, 4712 BC. The numbers in your table converted to Julian date. JSP spells out the date:
SELECT to_char(SYSDATE,'JSP') AS number_of_days_sinse_4712_BC
FROM dual
/
to convert decimal number into words, you can follow below code
SELECT TO_CHAR(to_date(TRUNC(num),'J'),'Jsp')
||' and '
|| TO_CHAR(to_date(to_number(SUBSTR(num-TRUNC(num),instr(num-TRUNC(num),'.')+1)),'J'),'Jsp') Indicator
FROM
(SELECT &enter_numbr num FROM dual
);
Hope this will help!!!