Retirement Date Function in Oracle SQL - sql

Please I want to calculate the retirement of an employee from his Date of First Appointment using a function before retrieving the result using a trigger and store it in the Retirement date column: but I am not good with the syntax please can someone help:... Below is my code for the function
CREATE OR REPLACE Function EDOR_DATE
(DOFA IN date)
RETURN date
IS
NEW_EDOR_RESULT date;
BEGIN
SELECT DOFA + ((365*35) + 9) as NEW_EDOR
FROM EMPLOYEES_MASTER_DETAILS;
fetch NEW_EDOR into NEW_EDOR_RESULT;
RETURN NEW_EDOR_RESULT;
END;

First a couple comments on the changes you indicated you made to the function:
Removing the "IN" from the declaration actually accomplishes nothing.
If you do not specify "IN', "OUT", or "IN OUT" the compiler defaults to "IN". All removing it does is a change from an explicit to implicit declaration.
Placing a semi-colon (;) where indicated will generate an error.
As for the function itself you are making it way more complicated than necessary. A simple assignment is all that's needed. Also, Boneist's suggestion of add_months is the correct function for adding years as it will adjust for leap year and number of days per month (if needed).
Thus your function reduces to:
create or replace function edor_date(dofa in date)
return date
is
l_edor_date date;
begin
l_edor_date := add_months(dofa, 35*12) ;
return l_edor_date ;
end;
or even further to just:
create or replace function edor_date (dofa in date)
return date
is
begin
return add_months(dofa, 35*12) ;
end;
BTW: I actually like the idea of using a function for this as it hides the implementation details of the business rule. However, it does impose a slight overhead for each call.

CREATE OR REPLACE Function EDOR_DATE
(DOFA date)
RETURN date
IS
NEW_EDOR_RESULT date;
BEGIN
--I am converting this to a case statement that is easier to read
select
case
when dofa - dob <= 25 then dofa + ((365*35)+9)
else dob + ((365*60)+9)
end
into new_edor_result
from employees_master_details;
return new_edor_result;
end;

This can be done without needing to use a function. Also, why are you adding numbers of days? A year is not 365 days long. You should be using the add_months() feature.
Here is how I would do this, assuming all the columns in question belong to the EMPLOYEES_MASTER_DETAILS table:
select emp_id,
dob,
hire_date,
case when months_between(hire_date, dob) <= 25 * 12 then add_months(hire_date, 35*12)
else add_months(dob, 60*12)
end retirement_date
from EMPLOYEES_MASTER_DETAILS emd;
If you need to calculate the retirement upon insert, then simply use the case expression as part of the insert statement.

Related

How to convert a NUMERIC field into a DATE field and use it in a WHERE clause in DB2?

I have a field that is integer format 20220801 that needs to be converted to a date field. I then need to use this field in a WHERE clause compared against the CURRENT DATE. This is specifically for DB2.
Every time I try to do this I receive this error message:
Here are some snippets I've tried unsuccessfully, each time returning the above error
SELECT
DATE(TIMESTAMP_FORMAT(CHAR(BWDUED), 'YYYYMMDD')) AS DUE_DATE,
CURRENT DATE AS TODAY_DATE
FROM
SCHEMA.TABLE
WHERE
DATE(TIMESTAMP_FORMAT(CHAR(BWDUED), 'YYYYMMDD')) = CURRENT_DATE
SELECT
DATE(TO_DATE(CHAR(BWDUED), 'YYYYMMDD')) AS DUE_DATE,
CURRENT DATE AS TODAY_DATE
FROM
SCHEMA.TABLE
WHERE
DATE(TO_DATE(CHAR(BWDUED), 'YYYYMMDD')) = CURRENT_DATE
I've looked at many of the answers on here, but none of them have gotten me past this error. Any help on navigating this would be appreciated!
Take a look at TIMESTAMP_FORMAT. It allows to specify the input format and you get a TIMESTAMP or DATE back.
VALUES (TIMESTAMP_FORMAT('20220801','YYYYMMDD'))
The problem is because some row contains a number which can't be formatted as a timestamp with the specified pattern.
Consider the following example returning exactly the same error, if you uncomment any of the commented out lines.
SELECT
DATE (TIMESTAMP_FORMAT (CHAR(BWDUED), 'YYYYMMDD')) AS DUE_DATE,
CURRENT DATE AS TODAY_DATE
FROM
(
VALUES
--0 ,
--20221232,
INT (TO_CHAR (CURRENT DATE, 'YYYYMMDD'))
) T (BWDUED)
WHERE
DATE (TIMESTAMP_FORMAT (CHAR(BWDUED), 'YYYYMMDD')) = CURRENT_DATE
The solution would be create a "safe" formatter function eating possible errors, and use it instead of TIMESTAMP_FORMAT (or synonym TO_DATE function).
CREATE OR REPLACE FUNCTION TO_DATE_SAFE
(
P_STR VARCHAR (128)
, P_FMT VARCHAR (128)
)
RETURNS TIMESTAMP
CONTAINS SQL
NO EXTERNAL ACTION
DETERMINISTIC
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
RETURN CAST (NULL AS TIMESTAMP);
END;
RETURN TO_DATE (P_STR, P_FMT);
END

All rows with date <= 90 days in oracle based on varchar2 date string [duplicate]

I have the following query that I am attempting to use as a COMMAND in a crystal report that I am working on.
SELECT * FROM myTable
WHERE to_date(myTable.sdate, 'MM/dd/yyyy') <= {?EndDate}
This works fine, however my only concern is that the date may not always be in the correct format (due to user error). I know that when the to_date function fails it throws an exception.. is it possible to handle this exception in such a way that it ignores the corresponding row in my SELECT statement? Because otherwise my report would break if only one date in the entire database is incorrectly formatted.
I looked to see if Oracle offers an isDate function, but it seems like you are supposed to just handle the exception. Any help would be greatly appreciated. Thanks!!
Echoing Tony's comment, you'd be far better off storing dates in DATE columns rather than forcing a front-end query tool to find and handle these exceptions.
If you're stuck with an incorrect data model, however, the simplest option in earlier versions is to create a function that does the conversion and handles the error,
CREATE OR REPLACE FUNCTION my_to_date( p_date_str IN VARCHAR2,
p_format_mask IN VARCHAR2 )
RETURN DATE
IS
l_date DATE;
BEGIN
l_date := to_date( p_date_str, p_format_mask );
RETURN l_date;
EXCEPTION
WHEN others THEN
RETURN null;
END my_to_date;
Your query would then become
SELECT *
FROM myTable
WHERE my_to_date(myTable.sdate, 'MM/dd/yyyy') <= {?EndDate}
Of course, you'd most likely want a function-based index on the MY_TO_DATE call in order to make this query reasonably efficient.
In 12.2, Oracle has added extensions to the to_date and cast functions to handle conversions that error
SELECT *
FROM myTable
WHERE to_date(myTable.sdate default null on conversion error, 'MM/dd/yyyy') <= {?EndDate}
You could also use the validate_conversion function if you're looking for all the rows that are (or are not) valid dates.
SELECT *
FROM myTable
WHERE validate_conversion( myTable.sdate as date, 'MM/DD/YYYY' ) = 1
If your data is not consistent and dates stored as strings may not be valid then you have 3 options.
Refactor your DB to make sure that the column stores a date datatype
Handle the exception of string to date in a stored procedure
Handle the exception of string to date in a (complex) record selection formula
I would suggest using the first option as your data should be consistent.
The second option will provide some flexibility and speed as the report will only fetch the rows that are needed.
The third option will force the report to fetch every record in the table and then have the report filter down the records.
I have the same problem... an old legacy database with varchar fields for dates and decades of bad data in the field. As much as I'd like to, I can't change the datatypes either. But I came up with this solution to find if a date is current, which seems to be what you're doing as well:
select * from MyTable
where regexp_like(sdate, '[0-1][0-9].[0-3][0-9].[0-9][0-9][0-9][0-9]')
-- make sure it's in the right format and ignore rows that are not
and substr(sdate,7,10) || substr(sdate,1,2) || substr(sdate,4,5) >= to_char({?EndDate}, 'YYYYMMDD')
-- put the date in ISO format and do a string compare
The benefit of this approach is it doesn't choke on dates like "February 30".
Starting from Oracle 12c there is no need to define a function to catch the conversion exception.
Oracle introduced an ON CONVERSION ERROR clause in the TO_DATE function.
Basically the clause suppress the error in converting of an invalid date string (typical errors are ORA-01843, ORA-01841, ORA-011861, ORA-01840) and returns a specified default value or null.
Example of usage
select to_date('2020-99-01','yyyy-mm-dd') from dual;
-- ORA-01843: not a valid month
select to_date('2020-99-01' default null on conversion error,'yyyy-mm-dd') from dual;
-- returns NULL
select to_date('2020-99-01' default '2020-01-01' on conversion error,'yyyy-mm-dd') from dual;
-- 01.01.2020 00:00:00
Solution for the Legacy Application
Let's assume there is a table with a date column stored as VARCHAR2(10)
select * from tab;
DATE_CHAR
----------
2021-01-01
2021-99-01
Using the above feature a VIRTUAL DATE column is defined, that either shows the DATE or NULL in case of the conversion error
alter table tab add (
date_d DATE as (to_date(date_char default null on conversion error,'yyyy-mm-dd')) VIRTUAL
);
select * from tab;
DATE_CHAR DATE_D
---------- -------------------
2021-01-01 01.01.2021 00:00:00
2021-99-01
The VIRTUAL column can be safely used because its format is DATE and if required an INDEX can be set up on it.
select * from tab where date_d = date'2021-01-01';
Since you say that you have "no access" to the database, I am assuming that you can not create any functions to help you with this and that you can only run queries?
If that is the case, then the following code should get you most of what you need with the following caveats:
1) The stored date format that you want to evaluate is 'mm/dd/yyyy'. If this is not the case, then you can alter the code to fit your format.
2) The database does not contain invalid dates such as Feb 30th.
First, I created my test table and test data:
create table test ( x number, sdate varchar2(20));
insert into test values (1, null);
insert into test values (2, '01/01/1999');
insert into test values (3, '1999/01/01');
insert into test values (4, '01-01-1999');
insert into test values (5, '01/01-1999');
insert into test values (6, '01-01/1999');
insert into test values (7, '12/31/1999');
insert into test values (8, '31/12/1999');
commit;
Now, the query:
WITH dates AS (
SELECT x
, sdate
, substr(sdate,1,2) as mm
, substr(sdate,4,2) as dd
, substr(sdate,7,4) as yyyy
FROM test
WHERE ( substr(sdate,1,2) IS NOT NAN -- make sure the first 2 characters are digits
AND to_number(substr(sdate,1,2)) between 1 and 12 -- and are between 0 and 12
AND substr(sdate,3,1) = '/' -- make sure the next character is a '/'
AND substr(sdate,4,2) IS NOT NAN -- make sure the next 2 are digits
AND to_number(substr(sdate,4,2)) between 1 and 31 -- and are between 0 and 31
AND substr(sdate,6,1) = '/' -- make sure the next character is a '/'
AND substr(sdate,7,4) IS NOT NAN -- make sure the next 4 are digits
AND to_number(substr(sdate,7,4)) between 1 and 9999 -- and are between 1 and 9999
)
)
SELECT x, sdate
FROM dates
WHERE to_date(mm||'/'||dd||'/'||yyyy,'mm/dd/yyyy') <= to_date('08/01/1999','mm/dd/yyyy');
And my results:
X SDATE
- ----------
2 01/01/1999
The WITH statement will do most of the validating to make sure that the sdate values are at least in the proper format. I had to break out each time unit month / day / year to do the to_date evaluation because I was still getting an invalid month error when I did a to_date on sdate.
I hope this helps.
Trust this reply clarifies...
there is no direct EXCEPTION HANDLER for invalid date.
One easy way is given below once you know the format like DD/MM/YYYY then below given REGEXP_LIKE function will work like a charm.
to_date() also will work, when invalid_date is found then cursor will goto OTHERS EXCEPTION. given below.
DECLARE
tmpnum NUMBER; -- (1=true; 0 = false)
ov_errmsg LONG;
tmpdate DATE;
lv_date VARCHAR2 (15);
BEGIN
lv_date := '6/2/2018'; -- this will fail in *regexp_like* itself
lv_date := '06/22/2018'; -- this will fail in *to_date* and will be caught in *exception WHEN OTHERS* block
lv_date := '07/03/2018'; -- this will succeed
BEGIN
tmpnum := REGEXP_LIKE (lv_date, '[0-9]{2}/[0-9]{2}/[0-9]{4}');
IF tmpnum = 0
THEN -- (1=true; 0 = false)
ov_errmsg := '1. INVALID DATE FORMAT ';
DBMS_OUTPUT.PUT_LINE (ov_errmsg);
RETURN;
END IF;
tmpdate := TO_DATE (lv_date, 'DD/MM/RRRR');
--tmpdate := TRUNC (NVL (to_date(lv_date,'DD/MM/RRRR'), SYSDATE));
tmpnum := 1;
EXCEPTION
WHEN OTHERS
THEN
BEGIN
tmpnum := 0;
ov_errmsg := '2. INVALID DATE FORMAT ';
DBMS_OUTPUT.PUT_LINE (ov_errmsg || SQLERRM);
RETURN;
END;
-- continue with your other query blocks
END;
-- continue with your other query blocks
DBMS_OUTPUT.PUT_LINE (tmpnum);
END;

Return number of days between two dates, if DATE has a spicific format(NOT DATE)

I develop specific software that uses Oracle10g Database.
This software have it's own DATE, TIME format in DB:
CurrentDate = number of days elapsed since 01.01.0001.
This number stored in DB as integer value, For example: current date is equal 735192.
I need to convert SYSDATE to this format. How to do this via Oracle?
I tried write some functions and SQL requst:
CREATE OR REPLACE
FUNCTION MyDaysBetween(aNow IN NUMBER,aThen IN NUMBER) RETURN NUMBER;
IS BEGIN
IF(aNow > aThen) THEN
RETURN (aNow - aThen);
ELSE
RETURN (aThen - aNow);
END IF;
END MyDaysBetween;
FUNCTION QAZ_SYSDATENOW() RETURN NUMBER; /*Converting SYSDATE(20.11.2013) to number of days since 01.01.0001*/
IS
BEGIN
RETURN SELECT SYSDATE - TO_DATE('01.01.0001','DD.MM.YYYY') FROM DUAL;
END MyDaysBetween;
SELECT * FROM TESTS T WHERE (MyDaysBetween(QAZ_SYSDATENOW(),T.D2) = 5); /*T.D2 contains 15.11.2013 asinteger number of days */
But something doesn't work, I can't find what is wrong.
Well, I'm hardly a seer and I don't even have a glass ball or tea dregs and so I can't tell what you mean by 'But something doesn't work', however, your main problem is with the QAZ_SYSDATENOW function - you can't use SELECT like that in PL/SQL, you have to either SELECT ... INTO, or, in this situation, just RETURN the result of the subtraction. Check this out:
CREATE OR REPLACE
FUNCTION MyDaysBetween(aNow IN NUMBER,aThen IN NUMBER) RETURN NUMBER
IS BEGIN
IF(aNow > aThen) THEN
RETURN (aNow - aThen);
ELSE
RETURN (aThen - aNow);
END IF;
END MyDaysBetween;
/
CREATE OR REPLACE
FUNCTION QAZ_SYSDATENOW RETURN NUMBER
IS
BEGIN
RETURN SYSDATE - TO_DATE('01.01.0001','DD.MM.YYYY');
END QAZ_SYSDATENOW;
/
I got rid of the SELECT in the RETURN and just return the value of the expression.
Test:
SELECT MyDaysBetween(TRUNC(QAZ_SYSDATENOW), T.D2) AS val
FROM (SELECT TRUNC(SYSDATE) - 5 - TO_DATE('01.01.0001','DD.MM.YYYY') d2
FROM dual
) t
;
As you can see, I use your functions to calculate the days between today (QAZ_SYSDATENOW) and a day 5 days ago. I use TRUNC twice to get rid of the fractional part. If you need that, drop the TRUNC.
Output:
VAL
----------
5

Writing a function in SQL to loop through a date range in a UDF

I am trying to automate the process of running a PLPGSQL function for a range of dates.
Typically I have to run the following code that generates a single table per day per function call:
SELECT dhcp.singleday('2012-11-24'::date, '2012-11-25'::date);
SELECT dhcp.singleday('2012-11-25'::date, '2012-11-26'::date);
SELECT dhcp.singleday('2012-11-26'::date, '2012-11-27'::date);
SELECT dhcp.singleday('2012-11-27'::date, '2012-11-28'::date);
SELECT dhcp.singleday('2012-11-28'::date, '2012-11-29'::date);
SELECT dhcp.singleday('2012-11-29'::date, '2012-11-30'::date);
SELECT dhcp.singleday('2012-11-30'::date, '2012-12-01'::date);
SELECT dhcp.singleday('2012-12-01'::date, '2012-12-02'::date);
SELECT dhcp.singleday('2012-12-02'::date, '2012-12-03'::date);
SELECT dhcp.singleday('2012-12-03'::date, '2012-12-04'::date);
Is there a good way to automate this sort of thing with a simple loop or function for an arbitrary date range?
I am thinking it might be hard to handle the cases of going month to month so I suppose it is better assume the date range is for a single month.
No need for functions:
select dhcp.singleday(a::date, a::date + 1)
from generate_series(
'2012-11-24'::date,
'2012-12-03',
'1 day'
) s(a)
This will work for any date range. Not only an inside month one.
Simple plpgsql function:
CREATE OR REPLACE FUNCTION f_machine_gun_sally(date, date)
RETURNS void AS
$func$
DECLARE
d date := $1;
BEGIN
LOOP
PERFORM dhcp.singleday(d, d+1);
d := d + 1;
EXIT WHEN d > $2;
END LOOP;
END
$func$ LANGUAGE plpgsql;
Use PERFORM when you want don't care about the return value.
You can just add an integer to a date to increment. Month or year boundaries are irrelevant this way.

Oracle Time Comparisons

Is Oracle (10g) doing a proper TIME comparison here, or do I need to convert to decimal time and then compare? E.g.,
IF (SELECT TO_CHAR(sysdate,'HH24:MI:SS') from dual) <= '15:00'
THEN (...)
Thanks.
IF (sysdate <= trunc(sysdate)+15/24)
THEN (...)
should do the trick...
You can't do a select in an if statement, but you can do a direct comparison to sysdate. If you're doing it like this it would probably be better to use a number rather than relying on implicit conversion. You also don't need the extra minutes etc. Something like,
begin
if to_number(to_char(sysdate,'HH24')) <= 15 then
-- do something
end if;
end;
If you did want to use the minutes then by converting it into a string without the colon you can do a more direct comparison. As long as the date / time is converted in 24 hour format without extras and in reverse, year, month, day, hour etc comparisons will always be accurate, e.g.
begin
if to_char(sysdate,'HH24MI') <= '1515' then
-- do something
end if;
end;
However, it's often best to do date comparisons as #cagcowboy has just posted before I got there!
use the following code
SELECT TO_CHAR(SYSDATE, 'HH24MI') INTO V_SYS_TIME FROM DUAL;
IF V_SYS_TIME BETWEEN V1_TIME AND V2_TIME THEN
(....)