Using date in a check constraint, Oracle - oracle

I am trying to check add the following constraint but Oracle returns the error shown below.
ALTER TABLE Table1
ADD (CONSTRAINT GT_Table1_CloseDate
CHECK (CloseDate > SYSDATE),
CONSTRAINT LT_Table1_CloseDate
CHECK (CloseDate <= SYSDATE + 365)),
CONSTRAINT GT_Table1_StartDate
CHECK (StartDate > (CloseDate + (SYSDATE + 730))));
Error:
Error report:
SQL Error: ORA-02436: date or system variable wrongly specified in CHECK constraint
02436. 00000 - "date or system variable wrongly specified in CHECK constraint"
*Cause: An attempt was made to use a date constant or system variable,
such as USER, in a check constraint that was not completely
specified in a CREATE TABLE or ALTER TABLE statement. For
example, a date was specified without the century.
*Action: Completely specify the date constant or system variable.
Setting the event 10149 allows constraints like "a1 > '10-MAY-96'",
which a bug permitted to be created before version 8.

A check constraint, unfortunately, cannot reference a function like SYSDATE. You would need to create a trigger that checked these values when DML occurs, i.e.
CREATE OR REPLACE TRIGGER trg_check_dates
BEFORE INSERT OR UPDATE ON table1
FOR EACH ROW
BEGIN
IF( :new.CloseDate <= SYSDATE )
THEN
RAISE_APPLICATION_ERROR( -20001,
'Invalid CloseDate: CloseDate must be greater than the current date - value = ' ||
to_char( :new.CloseDate, 'YYYY-MM-DD HH24:MI:SS' ) );
END IF;
IF( :new.CloseDate > add_months(SYSDATE,12) )
THEN
RAISE_APPLICATION_ERROR( -20002,
'Invalid CloseDate: CloseDate must be within the next year - value = ' ||
to_char( :new.CloseDate, 'YYYY-MM-DD HH24:MI:SS' ) );
END IF;
IF( :new.StartDate <= add_months(:new.CloseDate,24) )
THEN
RAISE_APPLICATION_ERROR( -20002,
'Invalid StartDate: StartDate must be within 24 months of the CloseDate - StartDate = ' ||
to_char( :new.StartDate, 'YYYY-MM-DD HH24:MI:SS' ) ||
' CloseDate = ' || to_char( :new.CloseDate , 'YYYY-MM-DD HH24:MI:SS' ) );
END IF;
END;

You cannot use SYSDATE in check constraint. According to documentation
Conditions of check constraints cannot
contain the following constructs:
Subqueries and scalar subquery expressions
Calls to the functions that are not deterministic (CURRENT_DATE,
CURRENT_TIMESTAMP, DBTIMEZONE,
LOCALTIMESTAMP, SESSIONTIMEZONE,
SYSDATE, SYSTIMESTAMP, UID, USER, and
USERENV)
Calls to user-defined functions
Dereferencing of REF columns (for example, using the DEREF function)
Nested table columns or attributes
The pseudocolumns CURRVAL, NEXTVAL, LEVEL, or ROWNUM
Date constants that are not fully specified
For 10g Release 2 (10.2), see constraint, and for 11g Release 2 (11.2) see constraint.
Remember that an integrity constraint is a statement about table data that is always true.
Anyway: I don't know exactly what you are trying to achieve but I think you can use triggers for this purpose.

Each and every time the record is updated SYSDATE will have a different value. Therefore the constraint will validate differently each time. Oracle does not allow sysdate in a constraint for that reason.
You may be able to solve your problem with a trigger that checks if CloseDate has actually changed and raise an exception when the new value is not within range.
And: What is (StartDate > (CloseDate + (SYSDATE + 730))))? You cannot add dates.
And: StartDate needs to be after CloseDate? Is that not weird?

Write sysdate into a column and use it for validation. This column might be your audit column (For eg: creation date)
CREATE TABLE "AB_EMPLOYEE22"
(
"NAME" VARCHAR2 ( 20 BYTE ),
"AGE" NUMBER,
"SALARY" NUMBER,
"DOB" DATE,
"DOJ" DATE DEFAULT SYSDATE
);
Table Created
ALTER TABLE "AB_EMPLOYEE22" ADD CONSTRAINT
AGE_CHECK CHECK((ROUND((DOJ-DOB)/365)) = AGE) ENABLE;
Table Altered

I don`t recommend sing triggers as constraint and to raise exceptions, Instead you can use a column to store SYSDATE as register date(if you already have it then you can use it) and then your constraint compares this column instead of SYSDATE
ALTER TABLE Table1
ADD (REGISTER_DATE DATE);
CREATE OR REPLACE TRIGGER trg_check_dates
BEFORE INSERT OR UPDATE ON table1
FOR EACH ROW
BEGIN
:new.REGISTER_DATE := SYSDATE;
END;
ALTER TABLE Table1
ADD (CONSTRAINT GT_Table1_CloseDate
CHECK (CloseDate > REGISTER_DATE),
CONSTRAINT LT_Table1_CloseDate
CHECK (CloseDate <= REGISTER_DATE + 365)),
CONSTRAINT GT_Table1_StartDate
CHECK (StartDate > (CloseDate + (REGISTER_DATE + 730))));

Related

Update trigger column based on date in ddmmyyy format in Oracle

I have created a trigger based on which I want to assign column with some value.
For example: I have a column name BILL_CALCULATED_DATE with value 19-10-21 which is in dd-mm-yy format. I want to extract mm and if it is 10 then I want to assign it as October
Below is my trigger
CREATE OR REPLACE TRIGGER TRG_UPD_GTL_BILL
BEFORE INSERT OR UPDATE ON IPFEE_MST_INSRT_GTL
FOR EACH ROW
BEGIN
:new.BILL_CALCULATED_DATE := case :NEW.UPLOADED_MONTH
when '01' then 'January'
when '02' then 'February'
when '03' then 'March'
when '04' then 'April'
when '05' then 'May'
when '06' then 'June'
when '07' then 'July'
when '08' then 'August'
when '09' then 'September'
when '10' then 'October'
when '11' then 'November'
when '12' then 'December'
end;
end;
But the problem is, before assigning month in the trigger I want to extract the month. How should I achieve it ?
Assuming that your BILL_CALCULATED_DATE column is of the DATE data type and you want the UPLOADED_MONTH column to be the name of the month corresponding to BILL_CALCULATED_DATE's month then:
Do not use a trigger; use a virtual column then the column values will always be synchronised.
Either create the table using:
CREATE TABLE IPFEE_MST_INSRT_GTL(
BILL_CALCULATED_DATE DATE,
UPLOADED_MONTH VARCHAR2(9)
GENERATED ALWAYS AS (
CAST(
TO_CHAR(BILL_CALCULATED_DATE, 'fmMonth', 'NLS_DATE_LANGUAGE=English')
AS VARCHAR2(9)
)
)
);
Or modify the existing table to drop the non-virtual column and add a virtual column:
ALTER TABLE IPFEE_MST_INSRT_GTL DROP COLUMN UPLOADED_MONTH;
ALTER TABLE IPFEE_MST_INSRT_GTL
ADD UPLOADED_MONTH VARCHAR2(9)
GENERATED ALWAYS AS (
CAST(
TO_CHAR(BILL_CALCULATED_DATE, 'fmMonth', 'NLS_DATE_LANGUAGE=English')
AS VARCHAR2(9)
)
);
If you must use a trigger and a real column then:
CREATE OR REPLACE TRIGGER TRG_UPD_GTL_BILL
BEFORE INSERT OR UPDATE ON IPFEE_MST_INSRT_GTL
FOR EACH ROW
BEGIN
:NEW.UPLOADED_MONTH := TO_CHAR(
:new.BILL_CALCULATED_DATE,
'fmMonth',
'NLS_DATE_LANGUAGE=English'
);
END;
/
If your BILL_CALCULATED_DATE column is a CHAR or VARCHAR2 data type then:
Change that column to a DATE.
If you cannot change it to a DATE then go and talk to someone who can change it to a DATE.
If you really, really cannot change it to a DATE then spend time despairing about the inability to follow best practice, try to convince people who may have the ability to do something about it that you should change things and then replace BILL_CALCULATED_DATE with TO_DATE(BILL_CALCULATED_DATE, 'DD-MM-YY') or :new.BILL_CALCULATED_DATE with TO_DATE(:new.BILL_CALCULATED_DATE, 'DD-MM-YY') in the snippets above.
db<>fiddle here
As per my understanding your column is not in date format. First convert this into date and then use EXTRACT function -
SELECT EXTRACT(month FROM TO_DATE(BILL_CALCULATED_DATE, 'dd-mm-yy'))
FROM IPFEE_MST_INSRT_GTL;
Something like this?
SELECT EXTRACT(month FROM SYSDATE) from dual

Different Output with same Input for ORACLE MD5 Function

At a given time I stored the result of the following ORACLE SQL Query :
SELET col , TO_CHAR( LOWER( STANDARD_HASH( col , 'MD5' ) ) AS hash_col FROM MyTable ;
A week later, I executed the same query on the same data ( same values for column col ).
I thought the resulting hash_col column would have the same values as the values from the former execution but it was not the case.
Is it possible for ORACLE STANDARD_HASH function to deliver over time the same result for identical input data ?
It does if the function is called twice the same day.
All we have about the data changing (or not) and the hash changing (or not) is your assertion.
You could create and populate a log table:
create table hash_log (
sample_time timestamp,
hashed_string varchar2(200),
hashed_string_dump varchar2(200),
hash_value varchar2(200)
);
Then on a daily basis:
insert into hash_log values
(select systimestamp,
source_column,
dump(source_column),
STANDARD_HASH(source_column , 'MD5' )
from source_table
);
Then, to spot changes:
select distinct hashed_string ||
hashed_string_dump ||
hash_value
from hash_log;

Oracle constraint - compare two string like two times

I would like to create table witj constrain which compare two varchar2(8 char) columns as time.
Is it possible ?
I've made somethink like this, but it doesn't work :(
CONSTRAINT "my_constraint" CHECK (to_number(to_char(to_date(window_stop, 'hh24:mi:ss'), 'sssss')) > to_number(to_char(to_date(window_start, 'hh24:mi:ss'), 'sssss'))) ENABLE
Thx for all help.
Paul.
Your constraint will get "ORA-02436: date or system variable wrongly specified in CHECK constraint" because if you don't provide the date elements then to_date() defaults to the first day of the current month; therefore the result of evaluating the constraint check could change after the data is inserted, which isn't allowed. It can't actually change in this specific case, but a general rule is being applied, causing the error.
You could use a nominal fixed date instead:
CONSTRAINT "my_constraint"
CHECK (
to_number(to_char(to_date('2000-01-01 ' || window_stop, 'YYYY-MM-DD hh24:mi:ss'), 'sssss'))
> to_number(to_char(to_date('2000-01-01 ' || window_start, 'YYYY-MM-DD hh24:mi:ss'), 'sssss'))
) ENABLE
db<>fiddle
There isn't much point converting to a number though, just compare the dates:
CONSTRAINT "my_constraint"
CHECK (
to_date('2000-01-01 ' || window_stop, 'YYYY-MM-DD hh24:mi:ss')
> to_date('2000-01-01 ' || window_start, 'YYYY-MM-DD hh24:mi:ss')
) ENABLE
db<>fiddle
Or - if you are confident that values are always going to be valid times and will have leading zeros - just compare the strings:
CONSTRAINT "my_constraint" CHECK (window_stop > window_start) ENABLE
db<>fiddle
You could also store the times as nominal dates, or as number of seconds past midnight, or as an interval, which would make it easier to prevent completely invalid values being used (e.g. '99:00:00'). But you may have a real date you can use instead - depends if these are tied to real dates, or e.g. shift patterns, or similar - which would allow you to handle windows crossing midnight. (What you should not do is store a related date and time as separate fields, but no indication that is what you are doing here.)
As I said, it is always a very bad idea to use varchar2 for date fields. Let me show you how your constraint is so easy when the fields are dates
SQL> create table t ( c1 date , c2 date ) ;
Table created.
SQL> alter session set nls_date_format='dd.mm.yyyy hh24:mi:ss' ;
Session altered.
SQL> insert into t values ( to_date('11.08.2020 14:00:00') , to_date('11.08.2020 14:10:00') ) ;
1 row created.
SQL> commit ;
Commit complete.
SQL> select * from t ;
C1 C2
------------------- -------------------
11.08.2020 14:00:00 11.08.2020 14:10:00
SQL> select ( c2 - c1 ) * 24 * 60 * 60 from t ;
(C2-C1)*24*60*60
----------------
600
Now, let's add the constraint
SQL> alter table t add constraint chk_test check ( c2 > c1 ) ;
Table altered.
SQL> insert into t values ( to_date('11.08.2020 14:11:00') , to_date('11.08.2020 14:10:00') ) ;
insert into t values ( to_date('11.08.2020 14:11:00') , to_date('11.08.2020 14:10:00') )
*
ERROR at line 1:
ORA-02290: check constraint (SYS.CHK_TEST) violated
You want to store times, which are dates, and you want to compare that the window_stop is greater than the window_start. Instead of making such complicated conversions, just store the fields as dates. Despite the fact that is the properly data type for the data you want to store, the constraint will work much more better.

CURRENT_DATE Oracle

I'm having hard time to add a new colomn of date of birth with the check rule age between 18 and 65.
I'm using sqplus with Oracle
Alway getting the error message ORA00920
Need your help please
ALTER TABLE Vendeur ADD (dateNaissance DATE,
dateDebutProjet DATE NOT NULL,
DateFinProjet DATE NOT NULL,
CONSTRAINT chk_date_Birth CHECK ((TRUNC(CURRENT_DATE)-dateNaissance)
BETWEEN 18 AND 65),
CONSTRAINT chk_date_Projet CHECK (DateFinProjet > dateDebutProjet));
if it can help, the solution without triggers (since we didn't learn hem at that time):
ALTER TABLE Vendeur ADD (dateNaissance DATE,
debutProjet DATE DEFAULT '01/01/1000' NOT NULL,
finProjet DATE DEFAULT '02/01/1000' NOT NULL,
dateDuJour Date DEFAULT CURRENT_DATE,
CONSTRAINT chk_date_Projet CHECK (finProjet > debutProjet),
CONSTRAINT chk_date_Birth CHECK ((dateDuJour - dateNaissance)\365 BETWEEN 18 AND 65)
);
Check constraints cannot call non-deterministic functions like CURRENT_DATE. Check constraints are supposed to always be true, weird things might happen if check constraints aged out.
The below sample code shows one of the errors you might get trying to use CURRENT_DATE in a check constraint:
SQL> create table test1(a date);
Table created.
SQL> alter table test1 add constraint test1_ck1 check(a > date '2000-01-01');
Table altered.
SQL> alter table test1 add constraint test1_ck2 check(a > current_date);
alter table test1 add constraint test1_ck2 check(a > current_date)
*
ERROR at line 1:
ORA-02436: date or system variable wrongly specified in CHECK constraint
Create a trigger to workaround this problem:
create or replace trigger test1_date_gt_today
before update or insert of a on test1
for each row
begin
if :new.a is null or :new.a < current_date then
raise_application_error(-20000, 'The date cannot be earlier than today.');
end if;
end;
/
Below is an example of one INSERT working, and one failing to meet the condition in the trigger:
SQL> insert into test1 values(sysdate + 1);
1 row created.
SQL> insert into test1 values(sysdate - 1);
insert into test1 values(sysdate - 1)
*
ERROR at line 1:
ORA-20000: The date cannot be earlier than today.
ORA-06512: at "JHELLER.TEST1_DATE_GT_TODAY", line 3
ORA-04088: error during execution of trigger 'JHELLER.TEST1_DATE_GT_TODAY'

How to find the elapse time user was logged into database using trigger

I am trying to calculate the total time user logged into the database using a trigger
my table structure is seen below:
create table stats$user_log
(
user_id varchar2(30),
session_id number(8),
host varchar2(30),
logon_day date,
logon_time varchar2(10),
logoff_day date,
logoff_time varchar2(10),
elapsed_minutes varchar2(32)
);
My trigger for logon is as follows:
create or replace trigger
logon_audit_trigger
AFTER LOGON ON DATABASE
BEGIN
insert into stats$user_log values(
user,
sys_context('USERENV','SESSIONID'),
sys_context('USERENV','HOST'),
sysdate,
to_char(sysdate, 'hh24:mi:ss'),
null,
null,
null
);
END;
/
My trigger for logoff is as follows:
create or replace trigger
logoff_audit_trigger
BEFORE LOGOFF ON DATABASE
BEGIN
UPDATE
stats$user_log
set
logoff_day = sysdate,
logoff_time = to_char(sysdate, 'hh24:mi:ss'),
elapsed_minutes = round((logoff_day - logon_day)*1440,2)
WHERE
sys_context('USERENV','SESSIONID') = session_id;
END;
/
When the user logs out everything is captured except the elapse_minutes column it remains as null.
Can anyone tell me where i'm going wrong please and thanks
At the time you do the update, the logoff_day you refer to in the right-hand side of the set expression is still null, so the expression evaluates to null.
Any column values you refer to have to be the pre-update values, or changing the order that the columns are assigned within the set clause would change how the update worked, which at best be confusing. An update that sets a column based on its old value - e.g. set salary = salary * 1.1 - would be particularly problematic.
You can refer to sysdate a second time instead:
logoff_day = sysdate,
logoff_time = to_char(sysdate, 'hh24:mi:ss'),
elapsed_minutes = round((sysdate - logon_day)*1440,2)
If session auditing is enabled, the database already does this for you. Why create that for yourself? Check dba_audit_session for the results. You might need to talk to your dba / security staff to get access but it might be worth it.

Resources