Oracle constraint - compare two string like two times - oracle

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.

Related

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 -- Datatype of column which can store value "13:45"

We need to store a value "13:45" in the column "Start_Time" of an Oracle table.
Value can be read as 45 minutes past 13:00 hours
Which datatype to be used while creating the table? Also, once queried, we would like to see only the value "13:45".
I would make it easier:
create table t_time_only (
time_col varchar2(5),
time_as_interval INTERVAL DAY TO SECOND invisible
generated always as (to_dsinterval('0 '||time_col||':0')),
constraint check_time
check ( VALIDATE_CONVERSION(time_col as date,'hh24:mi')=1 )
);
Check constraint allows you to validate input strings:
SQL> insert into t_time_only values('25:00');
insert into t_time_only values('25:00')
*
ERROR at line 1:
ORA-02290: check constraint (CHECK_TIME) violated
And invisible virtual generated column allows you to make simple arithmetic operations:
SQL> insert into t_time_only values('15:30');
1 row created.
SQL> select trunc(sysdate) + time_as_interval as res from t_time_only;
RES
-------------------
2020-09-21 15:30:00
Your best option is to store the data in a DATE type column. If you are going to be any comparisons against the times (querying, sorting, etc.), you will want to make sure that all of the times are using the same day. It doesn't matter which day as long as they are all the same.
CREATE TABLE test_time
(
time_col DATE
);
INSERT INTO test_time
VALUES (TO_DATE ('13:45', 'HH24:MI'));
INSERT INTO test_time
VALUES (TO_DATE ('8:45', 'HH24:MI'));
Test Query
SELECT time_col,
TO_CHAR (time_col, 'HH24:MI') AS just_time,
24 * (time_col - LAG (time_col) OVER (ORDER BY time_col)) AS difference_in_hours
FROM test_time
ORDER BY time_col;
Test Results
TIME_COL JUST_TIME DIFFERENCE_IN_HOURS
____________ ____________ ______________________
01-SEP-20 08:45
01-SEP-20 13:45 5
Table Definition using INTERVAL
create table tab
(tm INTERVAL DAY(1) to SECOND(0));
Input value as literal
insert into tab (tm) values (INTERVAL '13:25' HOUR TO MINUTE );
Input value dynamically
insert into tab (tm) values ( (NUMTODSINTERVAL(13, 'hour') + NUMTODSINTERVAL(26, 'minute')) );
Output
you may either EXTRACT the hour and minute
EXTRACT(HOUR FROM tm) int_hour,
EXTRACT(MINUTE FROM tm) int_minute
or use formatted output with a trick by adding some fixed DATE
to_char(DATE'2000-01-01'+tm,'hh24:mi') int_format
which gives
13:25
13:26
Please see this answer for other formating options HH24:MI
The used INTERVAL definition may store seconds as well - if this is not acceptable, add CHECK CONSTRAINT e.g. as follows (adjust as requiered)
tm INTERVAL DAY(1) to SECOND(0)
constraint "wrong interval" check (tm <= INTERVAL '23:59' HOUR TO MINUTE and EXTRACT(SECOND FROM tm) = 0 )
This rejects the following as invalid input
insert into tab (tm) values (INTERVAL '13:25:30' HOUR TO SECOND );
-- ORA-02290: check constraint (X.wrong interval) violated

Why oracle sql not allow sysdate in table creation time for count age?

Query....
I want to try to make check constraints on birthdate and check age should be greater than 18.
Create table emp
(
Birthdate date,
Check( MONTHS_BETWEEN(SYSDATE,Birthdate))
);
Error on above query....why?
Anyone help me...
Why oracle sql not allow sysdate in table creation time for count age?
SYSDATE is not allowed because the constraint must be either "true" or "false" at any time you look at the data. If you were able to use SYSDATE in a check constraint, you could insert a row that satisfied the constraint at that time, but the constraint would be violated later. No good!
In your example, once the constraint is satisfied at insert time, it can't become "not satisfied" later. But here you are asking Oracle to think. It can't. It just doesn't allow you to use SYSDATE in constraints. Period.
Instead, you should write a simple trigger to do the check for you. Note that you are missing the comparison to 18 * 12 in your purported check constraint; MONTHS_BETWEEN may give some weird results in some cases; and it is always best to write code that mirrors your thinking: in this case the condition (in a trigger, not a check constraint) should be ***
sysdate >= birthdate + interval '18' year
*** EDIT: As Alex Poole points out below, adding INTERVAL to a date may sometimes be as weird as MONTHS_BETWEEN. The safe way to write the check is
sysdate >= add_months ( birthdate, 18 * 12 ) -- age >= 18 years or 18 * 12 months
(That is how I would write it - with the comment to explain the purpose, and 18 * 12.)
Maybe try:
SQL> create table person
(name varchar2(100),
dob date,
created_date date default sysdate not null,
constraint dob_check check
(
dob <= add_months(trunc(created_date), (12*18)*-1)
)
)
Table created.
SQL> insert into person(name,dob) values ('Bob', to_date('19740101','YYYYMMDD'))
1 row created.
SQL> commit
Commit complete.
SQL> insert into person(name,dob) values ('Jane', to_date('20050101','YYYYMMDD'))
insert into person(name,dob) values ('Jane', to_date('20050101','YYYYMMDD'))
Error at line 17
ORA-02290: check constraint (MYUSER.DOB_CHECK) violated
This is because of Oracle limitation. The reason is SYSDATE is non-deterministic. You can get a different result every time you call it. So the outcome (true/false) can (will) change over time. So Oracle can't guarantee that the expression is always true for every row.
See also https://asktom.oracle.com/pls/apex/asktom.search?tag=sysdate-in-check-constraints

dynamically creating the partition name as sysdate?

How to give the name to the partition dynamically. I have a requirement in which i'm creating the interval partitioning on monthly basis. I want to give '1-jan-2014' , '1-Feb-2014' as partition names? Please suggest the approach?
I see some problem here because interval partitioning has system generated partition names...
So you have to change this generated names after creation, this problem is resolved here:
https://dba.stackexchange.com/questions/33225/rename-interval-partitioning-system-generated-partition-names
or here
http://bobjankovsky.org/show.php?seq=90
You could make a job which will periodically change the partition names.
If you're adding a partition to an existing table you'd need to do it dynamically. And since you want the name to have dashes in as date element separators the partition names would have to be quoted identifiers, which will make working with them explicitly a bit more complicated later.
If you have a table that initially has named partitions:
create table t42 (date_col date, other_col varchar2(10))
partition by range (date_col)
(
partition "Pre-2014" values less than (date '2014-01-01'),
partition "1-jan-2014" values less than (date '2014-02-01'),
partition "1-feb-2014" values less than (date '2014-03-01'),
partition "1-mar-2014" values less than (date '2014-04-01'),
partition "1-apr-2014" values less than (date '2014-05-01')
);
Then you can manually add a new named partition:
alter table t42 add partition "1-may-2014" values less than (date '2014-06-01');
alter table t42 add partition "1-jun-2014" values less than (date '2014-07-01');
If you want to add a new partition based on sysdate (or any date) then you'd need dynamic SQL to generate that command; here in an anonymous block but you coudl have a procedure if you want to pass a date in:
begin
execute immediate 'alter table t42 add partition "'
|| to_char(trunc(sysdate, 'MM'), 'FMdd-mon-yyyy')
|| '" values less than (date '''
|| to_char(add_months(trunc(sysdate, 'MM'), 1), 'YYYY-MM-DD')
|| ''')';
end;
/
Today that would generate and execute command:
alter table t42 add partition "1-jul-2014" values less than (date '2014-08-01')
Then you can see the partition is named as you wanted:
select partition_name from user_tab_partitions order by partition_position;
PARTITION_NAME
------------------------------
Pre-2014
1-jan-2014
1-feb-2014
1-mar-2014
1-apr-2014
1-may-2014
1-jun-2014
1-jul-2014

Using date in a check constraint, 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))));

Resources