Date constraint - sql

I need a check constraint for date so no data can be entered in the past. I'm using oracle live SQL for a school project and cant find a solution.
(BookingID Varchar2(6) PRIMARY KEY NOT NULL,
App_Date Date NOT NULL,
App_Time varchar2(8) NOT NULL,
Location Varchar2(15) NOT NULL,
Query Varchar(50) NOT NULL);

Oracle interprets check constraints as forever constraints on the data. That is, the check constraint is valid not only when the data changes but forever thereafter.
For this reason, Oracle does not allow volatile functions in check constraints. A volatile function is one whose values can change over time. A very good example are the date/time functions, such as sysdate (which is really a function without parentheses).
So, you cannot do what you want with a check constraint. You need to set this up using an insert trigger.

You can't use sysdate in a check constraint, because, as explained by Gordon Linoff, it is a non-deterministic function (ie the value it gives is not constant over time).
Instead, you can implement the validation with a trigger:
create or replace trigger app_date_not_in_past
before insert or update on mytable
for each row
begin
if (:new.app_date < sysdate)
then
raise_application_error( -20001, 'app_date date must not be in the past' );
end if;
end;
/

Related

Can't figure out the SQL code

CREATE TABLE empinf(
companyid varchar2(5) PRIMARY KEY,
companyname varchar2(30) NOT NULL,
emailid varchar2(20) REFERENCES usrinf(emailid),
Mobile number CONSTRAINT moc CHECK(LENGTH(Mobile)=10),
city varchar2(20),
industrytype varchar2(20),
functionalarea varchar2(20),
membershipplan varchar2(20) CONSTRAINT cmp CHECK(membershipplan in ('TRIAL','PREMIUM MONTHLY','PREMIUM YEARLY')),
dateofsignup date DEFAULT SYSDATE CONSTRAINT chd CHECK( dateofsignup>=SYSDATE ),
dateofrenewal date generated always as
(CASE
WHEN membershipplan='TRIAL' THEN SYSDATE+14
WHEN membershipplan='PREMIUM MONTHLY' THEN SYSDATE+30
WHEN membershipplan='PREMIUM YEARLY' THEN SYSDATE+365
ELSE SYSDATE
END
) virtual,
renewalstatus varchar2(20) CONSTRAINT chrs CHECK(renewalstatus in('ACTIVE','EXPIRED')),
CONSTRAINT mun UNIQUE(Mobile)
)
This code is to be generated in Oracle Express 11G, I am unable to figure out what's wrong with code, it shows 'missing paranthesis error'.
As far as I know, in a virtual column you need to define a deterministic function (a one which return same value, irrespective of when you call it.
You could try to use dateofsignup instead of SYSDATE inside case .
dateofrenewal date generated always as
(CASE
WHEN membershipplan='TRIAL' THEN dateofsignup+14
WHEN membershipplan='PREMIUM MONTHLY' THEN dateofsignup+30
WHEN membershipplan='PREMIUM YEARLY' THEN dateofsignup+365
ELSE dateofsignup
END
) virtual
Or you can remove it from table and use a view.
I forgot the other error (I was concentrating on the first one, so I apologize for the miss). As Thorsten say in other answer, you have a problem to set a check constraint for datesignup in that way.
Other that solution Torstein suggested, another solution could be to add another column (eg. DATE_INSERT DATE DEFAULT SYSDATE)
and write the constraint changing datesignup definition as
dateofsignup date DEFAULT SYSDATE
and add
ALTER TABLE empinf ADD CONSTRAINT chd CHECK( dateofsignup>=DATE_INSERT)
Your missing parenthesis error can be caused by your tries, while you commented some parts of the code.
Tip: I agree with g00dy comment above: create table basic structure with one command, then add constraint with separate commands.
It is strange you are getting a "missing paranthesis" error. Maybe the tool you are using to execute the statement swallows the last paranthesis?
I am getting "ORA-02436: date or system variable wrongly specified in CHECK constraint"
CONSTRAINT chd CHECK( dateofsignup>=SYSDATE )
You cannot use SYSDATE in a check constraint, as SYSDATE is subject to change. A check constraint must be stable and must not depend on the current time, a current session setting or the like. You'd need a trigger to do what you want to do.
After removing that check constraint I get "ORA-54002: only pure functions can be specified in a virtual column expression", which is about for the same reason.
dateofrenewal date generated always as
(CASE
WHEN membershipplan='TRIAL' THEN SYSDATE+14
...
A virtual column must be stable and can only depend on other columns, not on the current time, a current session setting or the like. You probably want dateofrenewal to be calculated based on dateofsignup instead.

i feel like i got this wrong

The commission classification column should be able to store integers up to a maximum value of 99 and be named Comm_id. The value of the Comm_id column should be set to a value of 10 automatically if no value is provided when a row is added. The benefits code column should also accommodate integer values up to a maximum of 99 and be named Ben_id.
alter table ACCTMANAGER
add (Comm_id varchar2(99),
Ben_id varchar2(99));
I dont know if this is right
alter table ACCTMANAGER add(Comm_id number(2) default 10, Ben_id number(2));
Basically for number data type you have precision and scale. and if scale is not specified scale is 0 which means no decimal places after the number. number(2) means you can only store up to two digit number here and default keyword set the value automatically if column was not specified.
BTW try using oracle documentation for this homework type of stuff. here is with good examples.
https://docs.oracle.com/cd/B28359_01/server.111/b28318/datatype.htm#i16209
EDITED
alter table ACCTMANAGER add(Comm_id number(2) default 10 constraint lowchk1 check(comm_id>=0) , Ben_id number(2) constraint lowchk2 check(ben_id>=0));
Sorry I can't check syntax for sure as I don't have Oracle installed at home. I only work at it at office.
#MSStp provided a good answer, but you still need constraints to make sure you don't get bad data in the table (such as negative numbers). If the constraint is that the commission and the benefit columns must contain integers between 0 and 99, and you want to make sure Oracle will not accept an input of 2.2 (which it WILL accept in MS's solution, it will just truncate it to 2 and store 2 in the database), you need to add constraints as Abdul Rehman Sayed suggested in a Comment to your question.
alter table acctmanager
add ( comm_id number(2) default 10
constraint check_comm ( comm_id >= 0 and comm_id = trunc(comm_id) ),
ben_id number(2)
constraint check_ben ( ben_id >= 0 and ben_id = trunc(ben_id) )
)
;
However: Just a thought..... What are comm_id and ben_id? If they are some sort of codes to specific commission and benefit descriptions/levels/whatever, do you really need check constraints? Do you have different tables explaining these codes, where comm_id and ben_id are (or should be) primary keys? In which case you need foreign key constraints, NOT check constraints?
ALTER TABLE ACCTMANGER ADD(Comm_id NUMBER(2) DEFAULT 10 NOT NULL, Ben_id NUMBER(2));
After that, you can see that the table is altered.
To see the Output write this:
DESC ACCTMANAGER;
You will see the whole table with the updated column.

Invalid Datatype trying to alter table [duplicate]

This question already has answers here:
Set ORACLE table fields default value to a formular
(2 answers)
Closed 8 years ago.
I got a table which I used the below code to create.
create table Meter (MeterID CHAR(8) CONSTRAINT MeterPK PRIMARY KEY,
Value CHAR(8) CONSTRAINT ValueNN NOT NULL,
InstalledDate Date CONSTRAINT InDateNN NOT NULL);
Then I tried adding a derived column that adds 6 months to the installeddate.
alter table meter add ExpiryDate as (add_months(installedDate,6)) not null;
This returns an error of invalid datatype.
I read somewhere that I do not have to specify the datatype of ExpiryDate as it can be derived from the function. So where did I go wrong?
EDIT: Turns out Mike was right. I used the trigger method to get things going, but I was confused whether I'm using mysql or oracle. Think in the end I'm using oracle actually. Have problems with the trigger but turns out I do not need to have the command "set" in the trigger. Below is the code that works.
CREATE OR REPLACE
TRIGGER trigexpdate1
BEFORE INSERT ON Meter
FOR EACH ROW
BEGIN
:NEW.ExpiryDate := ADD_MONTHS(:NEW.InstalledDate, 6);
END;
If I don't have the begin and end in the statement, it will throw an error saying illegal trigger specification.
MySQL doesn't support
derived columns in table definitions,
a function named add_months(), or
inline constraints.
This is a more or less standard way to write that statement in MySQL.
create table `Meter` (
`MeterID` CHAR(8) NOT NULL,
`Value` CHAR(8) NOT NULL,
`InstalledDate` Date NOT NULL,
primary key (`MeterID`)
);
You have two options for a derived column like "ExpiryDate".
Create a view, and do the date arithmetic in the view. Use date_add().
Add the column "ExpiryDate" to the table, and keep it up-to-date with a trigger.
BEFORE INSERT trigger example
create table `Meter` (
`MeterID` CHAR(8) NOT NULL,
`Value` CHAR(8) NOT NULL,
`InstalledDate` Date NOT NULL,
`ExpiryDate` Date not null,
primary key (`MeterID`)
);
create trigger trigexpdate1
before insert on `Meter`
FOR EACH ROW
SET NEW.`ExpiryDate` = date_add(NEW.`InstalledDate`, interval 6 month);
Note how ExpiryDate changes from the insert statement to the select statement below.
insert into Meter
values ('1', '1', '2014-07-01', '2014-07-01');
select * from Meter;
MeterID Value InstalledDate ExpiryDate
--
1 1 2014-07-01 2015-01-01

I am trying To add Check constraint for checking age and date of birth [duplicate]

This question already has answers here:
CHECK constraint on date of birth?
(5 answers)
Closed 8 years ago.
CREATE TABLE "TEST"."AB_EMPLOYEE22"
( "NAME" VARCHAR2(20 BYTE),
"AGE" NUMBER,
"SALARY" NUMBER,
"DOB" DATE
)
alter table "TEST"."AB_EMPLOYEE22" add constraint
Age_check check((ROUND((sysdate-DOB)/365)) = AGE) ENABLE
But This query is not working.
Pls help me out
Disclaimer: It's not a direct answer to the question.
Now, don't store derived and most importantly constantly changing data such as age in the table. Instead calculate it on the fly when you query it (e.g. with a view).
CREATE TABLE ab_employee22
(
name VARCHAR2(20),
salary NUMBER,
dob DATE
);
CREATE VIEW ab_employee22_view AS
SELECT name, salary, dob,
FLOOR(MONTHS_BETWEEN(sysdate, dob) / 12) age
FROM ab_employee22;
Here is SQLFIddle demo
You cannot use SYSDATE in check constraint. According to Oracle Documentation - Check Constraint
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
So, you can use Trigger to get your desired output in this case. Here, is the trigger which will work fine as per your requirement:
CREATE OR REPLACE TRIGGER trg_check_date
BEFORE INSERT OR UPDATE ON AB_EMPLOYEE22
FOR EACH ROW
BEGIN
IF(ROUND((sysdate-nvl(:NEW.DOB,:OLD.DOB))/365) <> nvl(:NEW.AGE,:OLD.AGE))
THEN
RAISE_APPLICATION_ERROR( -20001, 'Your Date of Birth and Age do not match');
END IF;
END;
If you find any difficulty in this trigger, please feel free to write in comments.
It is not possible to user system variables like 'Sysdate' in a constraint.
Link: http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:465820332843
suggestion is to use a trigger with the same logic
Forget about having SYSDATE since its not valid or using a trigger for a workaround. I will show you a cheap trick!!!
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
You have missed an parameter of round function.
alter table AB_EMPLOYEE22 add constraint
Age_check check((ROUND((sysdate-DOB)/365,0)) = AGE) ENABLE

Confused with Oracle Procedure with sequence, linking errors and filling null fields

I am trying to make a procedure that takes makes potential empty "received" fields use the current date. I made a sequence called Order_number_seq that populates the order number (Ono) column. I don't know how to link errors in the orders table to a entry in the Orders_errors table.
this is what i have so far:
CREATE PROCEDURE Add_Order
AS BEGIN
UPDATE Orders
CREATE Sequence Order_number_seq
Start with 1,
Increment by 1;
UPDATE Orders SET received = GETDATE WHERE received = null;
These are the tables I am working with:
Orders table
(
Ono Number Not Null,
Cno Number Not Null,
Eno Number Not Null,
Received Date Null,
Shipped_Date Date Null,
Creation_Date Date Not Null,
Created_By VARCHAR2(10) Not Null,
Last_Update_Date Date Not Null,
Last_Updated_By VARCHAR2(10) Not Null,
CONSTRAINT Ono_PK PRIMARY KEY (Ono),
CONSTRAINT Cno_FK FOREIGN KEY (Cno)
REFERENCES Customers_Proj2 (Cno)
);
and
Order_Errors table
(
Ono Number Not Null,
Transaction_Date Date Not Null,
Message VARCHAR(100) Not Null
);
Any help is appreciated, especially on linking the orders table errors to create a new entry in OrderErrors table.
Thanks in advance.
Contrary to Martin Drautzburg's answer, there is no foreign key for the order number on the Order_Errors table. There is an Ono column which appears to serve that purpose, but it is not a foreign as far as Oracle is concerned. To make it a foreign key, you need to add a constraint much like the Cno_FK on Orders. An example:
CREATE TABLE Order_Errors
(
Ono Number Not Null,
Transaction_Date Date Not Null,
Message VARCHAR(100) Not Null,
CONSTRAINT Order_Errors_Orders_FK FOREIGN KEY (Ono) REFERENCES Orders (Ono)
);
Or, if your Order_Errors table already exists and you don't want to drop it, you can use an ALTER TABLE statement:
ALTER TABLE Order_Errors
ADD CONSTRAINT Order_Errors_Orders_FK FOREIGN KEY (Ono) REFERENCES Orders (Ono)
;
As for the procedure, I'm inclined to say what you're trying to do does not lend itself well to a PROCEDURE. If your intention is that you want the row to use default values when inserted, a trigger is better suited for this purpose. (There is some performance hit to using a trigger, so that's a consideration.)
-- Create sequence to be used
CREATE SEQUENCE Order_Number_Sequence
START WITH 1
INCREMENT BY 1
/
-- Create trigger for insert
CREATE TRIGGER Orders_Insert_Trigger
BEFORE INSERT ON Orders
FOR EACH ROW
DECLARE
BEGIN
IF :NEW.Ono IS NULL
THEN
SELECT Order_Number_Sequence.NEXTVAL INTO :NEW.Ono FROM DUAL;
END IF;
IF :NEW.Received IS NULL
THEN
SELECT CURRENT_DATE INTO :NEW.O_Received FROM DUAL;
END IF;
END;
/
This trigger will then be executed on every single row inserted into the Orders table. It checks if the Ono column was NULL and replaces it with an ID from the sequence if so. (Be careful that you don't ever provide an ID that will later be generated by the sequence; it will get a primary key conflict error.) It then checks if the received date is NULL and sets it to the current date, using the CURRENT_DATE function (which I believe was one of the things you were trying to figure out), if so.
(Side note: Other databases may not require a trigger to do this and instead could use a default value. I believe PostgreSQL, for instance, allows the use of function calls in its DEFAULT clauses, and that is how its SERIAL auto-increment type is implemented.)
If you are merely trying to update existing data, I would think the UPDATE statements by themselves would suffice. Is there a reason this needs to be a PROCEDURE?
One other note. Order_Errors has no primary key. You probably want to have an auto-incrementating surrogate key column, or at least create an index on its Ono column if you only ever intend to select off that column.
There are a number of confusing things in your question:
(1) You are creating a sequence inside a procedure. Does this even compile?
(2) Your procedure does not have any parameters. It just updates the RECEIVED column of all rows.
(3) You are not telling us what you want in the MESSAGE column.
My impression is that you should first go "back to the books" before you ask questions here.
As for your original question
how to link errors in the orders table to a entry in the Orders_errors
table.
This is aleady (correctly) done. The Orders_error table contains an ONO foreign key which points to an order.