Oracle-SQL How to check dates before inserting - sql

I have a simple insert statement like below
Insert into table_X (id, validFrom, validTo, someValue) VALUES(?,?,?,?);
where validFrom and validTo have a DATE type.
I want to insert records to table only if validFrom <= validTo. How can I add some condition checking to the SQL statement to do so? For now I'm doing the cheking with java and I want to do it in the sql query if possible.
EDIT
CREATE TABLE XYZ.TABLE_X
(
ID VARCHAR2(20 BYTE) NOT NULL ,
VALIDFROM DATE NOT NULL ,
VALIDTO DATE NOT NULL ,
SOMEVALUE NUMBER(18, 0) NOT NULL ,
CONSTRAINT TABLE_X_PK_N2 PRIMARY KEY
(
ID ,
VALIDFROM ,
VALIDTO
)
ENABLE
CONSTRAINT chk_table_x_valids
(
check(VALIDFROM <= VALIDTO )
)
ENABLE
)
ORGANIZATION INDEX
LOGGING
TABLESPACE XYZ
...

You can add a check constraint:
alter table table_x add constraint chk_table_x_valids
check (validFrom <= validTo);

Related

How to write case expression inside merge statement to print the desired result

CREATE TABLE DELIGATE_DETAILS_MAIN
( E_ID NUMBER(10,0),
COMPLETED_DATE TIMESTAMP (6),
CONSTRAINT PK_DELIGATE_DETAILS_MAIN PRIMARY KEY (E_ID));
Insert into deligate_details_main (E_ID,COMPLETED_DATE) values (1,to_timestamp('13-12-21 6:05:23.991000000 PM','DD-MM-RR fmHH12:fmMI:SSXFF AM'));
Insert into deligate_details_main (E_ID,COMPLETED_DATE) values (2,to_timestamp('13-12-21 6:05:24.019000000 PM','DD-MM-RR fmHH12:fmMI:SSXFF AM'));
Insert into deligate_details_main (E_ID,COMPLETED_DATE) values (3,to_timestamp('13-12-21 6:05:24.029000000 PM','DD-MM-RR fmHH12:fmMI:SSXFF AM'));
Insert into deligate_details_main (E_ID,COMPLETED_DATE) values (4,to_timestamp('13-12-21 10:46:00.015000000 PM','DD-MM-RR fmHH12:fmMI:SSXFF AM'));
CREATE TABLE CONTROL_MAIN
( E_ID NUMBER(10,0),
E_SPEC VARCHAR2(30 BYTE),
CONSTRAINT PK_CONTROL_MAIN PRIMARY KEY (E_ID));
Insert into CONTROL_MAIN (E_ID,E_SPEC) values (1,'SAP1');
Insert into CONTROL_MAIN (E_ID,E_SPEC) values (2,'FSAP');
Insert into CONTROL_MAIN (E_ID,E_SPEC) values (3,'SAP2');
Insert into CONTROL_MAIN (E_ID,E_SPEC) values (4,'SAP1-480');
CREATE TABLE QUESTION
( E_ID NUMBER(10,0),
QUEST VARCHAR2(30 BYTE),
CONSTRAINT PK_QUESTION PRIMARY KEY (E_ID));
Insert into QUESTION (E_ID,QUEST) values (1,'Yes');
Insert into QUESTION (E_ID,QUEST) values (2,'No');
Insert into QUESTION (E_ID,QUEST) values (3,'Yes');
Insert into QUESTION (E_ID,QUEST) values (4,'Yes');
CREATE TABLE DELIGATE_DETAILS_TRANS
( D_ID NUMBER(10,0),
E_ID NUMBER(10,0),
COMPLETED_DATE_TRANS DATE,
OWNER_DETAIL VARCHAR2(30 BYTE),
CONSTRAINT PK_DELIGATE_DETAILS_TRANS PRIMARY KEY (D_ID),
CONSTRAINT FK_E_ID FOREIGN KEY (E_ID)
REFERENCES TAM.DELIGATE_DETAILS_MAIN (E_ID));
Attempt:
MERGE INTO deligate_details_trans t USING ( SELECT
ddm.e_id,
ddm.completed_date
FROM
deligate_details_main ddm
JOIN control_main cm ON ( cm.e_id = ddm.e_id AND cm.e_spec LIKE %'SAP'% )
JOIN question q ON ( q.e_id = ddm.e_id
AND q.quest = 'Yes' )
) s
on (t.e_id = s.e_id)
when not matched then
insert (d_id,e_id, completed_date_trans, owner_detail)
values (
deligate_details_trans_sq.nextval,
s.e_id,
CAST(s.completed_date AS DATE),
--Here need to insert owner detail from control main
--If it is SAP1 or SAP2 then it will insert SAP1 or SAP2
--If it is SAP1-480 then it should insert SAP3);
Expected output:
+------+---------+-----------------------+--------------+
| D_ID | E_ID | COMPLETED_DATE_TRANS | OWNER_DETAIL |
+------+---------+-----------------------+--------------+
| 1 | 1 | 13-12-21 | SAP1 |
| 2 | 3 | 13-12-21 | SAP2 |
| 3 | 4 | 13-12-21 | SAP3 |
+------+---------+-----------------------+--------------+
For e_id 1: Based on the join condition from control_main and question table. Data should get inserted into the deligate_details_trans table and owner detail should be SAP1.
For e_id 2: Based on the join condition from control_main and question table. Data is NOT matching so it should not get inserted into the trans table.
For e_id 3: Based on the join condition from control_main and question table. Data should get inserted into the deligate_details_trans table and owner detail should be SAP2.
For e_id 4 it should check in the control main table and if it is SAP1-480 then it should insert SAP3 and for others, corresponding owner details should be inserted from the control_main table
If you want the case expression in the insert's values clause then you need to expose the control table value in the using clause:
MERGE INTO deligate_details_trans t
USING (
SELECT
ddm.e_id,
ddm.completed_date,
cm.e_spec
FROM
deligate_details_main ddm
JOIN control_main cm ON ( cm.e_id = ddm.e_id AND cm.e_spec LIKE '%SAP%' )
JOIN question q ON ( q.e_id = ddm.e_id
AND q.quest = 'Yes' )
) s
ON (t.e_id = s.e_id)
WHEN NOT MATCHED THEN INSERT (
d_id,e_id, completed_date_trans, owner_detail
)
VALUES (
deligate_details_trans_sq.nextval,
s.e_id,
CAST(s.completed_date AS DATE),
CASE s.e_spec
WHEN 'SAP1' THEN 'SAP1'
WHEN 'SAP2' THEN 'SAP2'
WHEN 'SAP1-480' THEN 'SAP3'
END
);
Alternatively move the case expression into the using clause, give it an alias, and refer to that alias in the values clause:
MERGE INTO deligate_details_trans t
USING (
SELECT
ddm.e_id,
ddm.completed_date,
CASE cm.e_spec
WHEN 'SAP1' THEN 'SAP1'
WHEN 'SAP2' THEN 'SAP2'
WHEN 'SAP1-480' THEN 'SAP3'
END AS owner_detail
FROM
deligate_details_main ddm
JOIN control_main cm ON ( cm.e_id = ddm.e_id AND cm.e_spec LIKE '%SAP%' )
JOIN question q ON ( q.e_id = ddm.e_id
AND q.quest = 'Yes' )
) s
ON (t.e_id = s.e_id)
WHEN NOT MATCHED THEN INSERT (
d_id,e_id, completed_date_trans, owner_detail
)
VALUES (
deligate_details_trans_sq.nextval,
s.e_id,
CAST(s.completed_date AS DATE),
s.owner_detail
);
db<>fiddle showing both (with %'SAP'% changed to '%SAP%', and sequence creation added).
I'm not sure why you have a timestamp in one table and a date in the other, and you don't need an explicit cast (though it doesn't hurt). But if you're doing that because you're only interested in the date part you should be aware that an Oracle date still has a time component, even if it isn't being shown in your DD-MM-YY format or db<>fiddle's default DD-MON-YY format. If you want to 'lose' the time part you can truncate the value at the day (DD) component, shown in this db<>fiddle which changes the display format so you can see the difference. But you might want to keep the time - in which case, ignore this part...

Can I add a CHECK in my SQL CREATE TABLE script?

I created this table for my database.
CREATE TABLE Reservation
(
Reservation_Name SERIAL UNIQUE PRIMARY KEY,
User VARCHAR(64) DEFAULT 'Member', FOREIGN KEY(User) REFERENCES User(Email)
ON UPDATE CASCADE
ON DELETE SET DEFAULT,
Location INT, FOREIGN KEY(Location) REFERENCES Place(Id_Location)
ON UPDATE CASCADE
ON DELETE NO ACTION,
Start_Date DATE NOT NULL,
Check_In TIME(1) DEFAULT '10:00:00',
End_Date DATE NOT NULL,
Check_Out TIME(1) DEFAULT '18:00:00',
CHECK(Start_Date >= End_Date),
Deleted BOOLEAN DEFAULT FALSE
);
How can I insert a Check that doesn't allow to add a reservation if there's already another one with the same Start_Date and the same End_Date end the same location?
You can use an exclusion constraint:
CREATE EXTENSION btree_gist;
ALTER TABLE reservation ADD EXCLUDE USING gist (
location WITH =,
daterange(start_date, end_date, '[]') WITH &&
);
The extension is required so that you can create a GiST index on an integer column, and && is the "overlaps" operator for range types.
You can symply add a constraint to the table.
Alter Table Reservations Add Constraint unique_reservation Unique(Location,StartDate,EndDate);
You will need a trigger for this. Look at the above code :
CREATE TRIGGER no_overlap
BEFORE INSERT
ON Reservation FOR EACH ROW
BEGIN
SET #overlaps = ( SELECT count(*) FROM Reservation WHERE ( ( NEW.Start_Date >= Start_Date AND NEW.Start_Date <= End_Date AND NEW.Location = Location) || ( NEW.End_Date >= Start_Date AND NEW.End_Date <= End_Date AND NEW.Location = Location)));
IF #overlaps > 0 THEN
SIGNAL SQLSTATE '45000' SET
MYSQL_ERRNO = 31000,
MESSAGE_TEXT = 'Unable to insert an overlapping reservation';
END IF;
END;
INSERT INTO Reservation (Location,Start_Date,End_Date) VALUES(1,'2020-12-13','2020-12-16');
INSERT INTO Reservation (Location,Start_Date,End_Date) VALUES(1,'2020-12-14','2020-12-17');
The first insert will succeed while the second one will fail with the corresponding error message if the dates overlap :
SQL Error [31000] [45000]: (conn=10) Unable to insert an overlapping reservation
By the way, I think you have an error in your table definition. Instead of CHECK(Start_Date >= End_Date), I think you meant CHECK(Start_Date <= End_Date),
Let me know if it helps.
Note : I did this on MariaDB but you can apply the same for any SQL DB.
This works in sql server. I don't have access at the moment to check against postgres. You will need to run in two different batches after creating your table
First
CREATE FUNCTION dbo.HasOverlap (
#locationId int, #start datetime, #end datetime)
RETURNS VARCHAR(5)
AS
BEGIN
IF (SELECT count(*) FROM dbo.Reservation WHERE Location = #locationId
and (
#start between Start_Date and End_Date
or
#end between Start_Date and End_Date
or
(#start <=Start_Date and #end>=End_Date )
)
) >1
return 1
return 0
END
Second
Alter Table dbo.Reservation
with check add Constraint Check_Overlap
check (dbo.HasOverlap(Location, Start_Date, End_Date)=0)
You need to use the composite primary key concept in MySQL Database. Its disable to insert duplicate items in specific columns.
ALTER TABLE table_name ADD CONSTRAINT constraint_name PRIMARY KEY (Start_Date, End_Date, Location)

Constraining Child Record Based on Parent Record

In a timesheets data model, suppose I have the following parent table:
CREATE TABLE EmployeeInRole (
employeeInRoleId PRIMARY KEY,
employeeId,
roleId,
rate,
effectiveFrom DATE, --from when can this employee assume this role
effectiveTo DATE
);
and the following child table:
CREATE TABLE TimesheetEntry (
startTime DATETIME,
endTime DATETIME,
employeeInRoleId,
CONSTRAINT fk FOREIGN KEY (employeeInRoleId) REFERENCES EmployeeInRole (employeeInRoleId)
);
When I insert into TimesheetEntry, I'd like to make sure that time period falls within the boundaries of the parent record's effectiveFrom/To.
Is it possible to build this constraint into the DDL without use of a trigger, or do I have to maintain this constraint via a trigger or at the application level?
(Here is some info about Oracle only)
It's not possible in Oracle with clear DDL but you can do something like this:
create table t1 (id number primary key, date_from date, date_to date);
create table t2 (id number primary key, date_from date, date_to date, parent_id number references t1(id));
create view v as
select t2.* from t2
where exists (select 1 from t1 where t1.id = t2.parent_id
and t2.date_from between t1.date_from and t1.date_to
and t2.date_to between t1.date_from and t1.date_to)
with check option constraint chk_v;
insert into t1 values (1, sysdate - 5, sysdate); -- OK
insert into v values (1, sysdate - 4, sysdate - 3, 1); -- OK
insert into v values (1, sysdate - 6, sysdate - 3, 1); -- ERROR (WITH CHECK OPTION where-clause violation)
V is updatable view created with CHECK OPTION
"Is it possible to build this constraint into the DDL without use of a trigger,"
It is possible in some RDBMS systems, but it is not possible in SQL.

Digit restriction on SQL server numeric field

How can i restrict field in a table to 15 or 16 digits. I have this table:
create table Person(
,UserID varchar(30)
,Password varchar(30) not null
,CCtype varchar(8)
,CCNumber numeric
,primary key(UserID)
,constraint CK_CCvalidity check
(
(CCType is null or CCNumber is null)
or
(
(CCType = 'Amex' or CCType = 'Discover' or CCType = 'MC' or CCType = 'VISA')
and
(CCNumber >= 15 and CCNumber <= 16)
)
)
);
But this actually checks for the values 15 an 16, not for the number of digits. Also, we can assume that the numeric may hold 000... as the first digits.
Thanks for the help
CCNumber should never be numeric. That will lead to a world of pain.
It should be varchar(X) where X is 13 - 24 digits.
Credit card numbers are usually represented by groups of 4 or 5
digits separated by spaces or dashes or simply all together with no separators.
[note: American Express: 15 digits; Visa: 13 or 16 digits]
In response to your comment:
ALTER TABLE dbo.Person
ADD CONSTRAINT CK_Person_CCNumber
CHECK (LEN(CCNumber) = 16 OR LEN(CCNumber) = 15);
But probably better as:
ALTER TABLE dbo.Person
ADD CONSTRAINT CK_Person_CCNumber
CHECK (LEN(CCNumber) >= 13 AND LEN(CCNumber) <= 15);
AND add a constraint to ensure it is a valid credit card number perhaps (there are plenty of examples online).
Bank Card Number
You can create a function to remove the Non-Numeric characters from a varchar, like this one:
CREATE Function [fnRemoveNonNumericCharacters](#strText VARCHAR(1000))
RETURNS VARCHAR(1000)
AS
BEGIN
WHILE PATINDEX('%[^0-9]%', #strText) > 0
BEGIN
SET #strText = STUFF(#strText, PATINDEX('%[^0-9]%', #strText), 1, '')
END
RETURN #strText
END
Now, if you want to allow only digits and want to check the length, you could add two Check Constraints like this:
Create Table Person
(
Id int not null primary key,
CCNumber varchar(30),
CONSTRAINT CK_Person_CCNumber_Length CHECK (LEN(CCNumber) BETWEEN 15 AND 16),
CONSTRAINT CK_Person_CCNumber_IsNumeric CHECK (LEN(dbo.[fnRemoveNonNumericCharacters](CCNumber)) = LEN(CCNumber))
)
First Constraint will check the length of the field to be 15 or 16.
Second one will check that the field is numeric (length of field removing non-numeric is equal to length of the original field)
You can also do it in just one ANDed Check Constraint.
Storing a credit card number as a...number is guaranteed to shoot you in the foot some day. Such as the day you start encountering credit card numbers with leading zeroes. They may consist of decimal digits, but they're not numbers. They're text.
Plan for the future: what happens when somebody start issuing credit card numbers with letters?
So, try this:
create table dbo.some_table
(
...
credit_card_type varchar(8) null ,
credit_card_number varchar(32) null ,
constraint some_table_ck01 check (
( credit_card_type is not null
and credit_card_number is not null
)
OR ( credit_card_type is null
and credit_card_number is null
)
) ,
constraint some_table_ck02 check (
credit_card_type in ( 'amex' , 'discover' , 'mc' , 'visa' )
) ,
constraint some_table_ck03 check (
credit_card_number not like '%[^0-9]%'
) ,
constraint some_table_ck04 check (
len(credit_card_number) = case credit_card_type
when 'amex' then 15
when 'discover' then 16
when 'mc' then 16
when 'visa' then 16
else -1 -- coerce failure on invalid/unknown type
end
) ,
)
go
insert some_table values( null , null ) -- succeeds
insert some_table values( 'amex' , null ) -- violates check constraint #1
insert some_table values( null , '1' ) -- violates check constraint #1
insert some_table values( 'acme' , '1' ) -- violates check constraint #2
insert some_table values( 'amex' , 'A1B2' ) -- violates check constraint #3
insert some_table values( 'amex' , '12345' ) -- violates check constraint #4
insert some_table values( 'amex' , '123456789012345' ) -- success!
go
But as noted by others, you need to fix your data model. A credit card is a separate entity from a customer. It has a dependent relationship upon the customer (the card's existence is predicated upon the existence of the customer who own it). You can a data model like the following. This
create table credit_card_type
(
int id not null primary key clustered ,
description varchar(32) not null unique ,
... -- other columns describing validation rules here
)
create table credit_card
(
customer_id int not null ,
type int not null ,
number varchar(32) not null ,
expiry_date date not null ,
primary key ( customer_id , number , type , expiry_date ) ,
unique ( number , customer_id , type , expiry_date ) ,
foreign key customer references customer(id) ,
foreign key type references credit_card_type(id) ,
)
Further: you are encrypting card numbers using strong encryption, aren't you?

Creating table in Oracle

My requirement is that the column accno has no null value and no duplicates. The name column has no nulls and accepts only A to Z (no other like number or * $). The acctype columns is a character that allows only ( 'S' , 'C' ,'R') and the balance column has no null values. If acctype is S then balance should be >= 5000, when C the balance should be > 10000 and when it's R >= 5000.
I am trying to apply this with:
create table kcb_acc_tab
(accno varchar2(20)
constraint kcb_acc_Pk
primary key,
name varchar2(20)
constraint kcb_name_NN
Not null
constraint kcb_name_CK
check((name =upper(name)) and (name like '[(A-Z)]')),
Acctype char
constraint kcb_acctype_ck
check (acctype in('S' ,'C' ,'R')) ,
Doo timestamp
default sysdate ,
bal number(7,2) kcb_bal_NN
constraint kcb_bal_ck
check((aacctype ='S' and bal >=5000) or
(acctype = 'C' and bal >=10000) or
(acctype ='R' and bal >=5000));
This sounds like a perfect use-case for regular expressions, which I think is your intention with the like in your constraint.
I've considerably cleaned up your statement, you were missing a comma in the definition of kcb_bal_ck, and I've put the constraints at the end and added whitespace. This makes it easier, for me, to see what's going on and where any mistakes might be.
create table kcb_acc_tab(
accno varchar2(20) not null
, name varchar2(20) not null
, acctype char(1) not null -- missing size of column
, doo timestamp default sysdate not null -- missing not null
, bal number(7,2) not null
, constraint kcb_acc_pk primary key (accno)
, constraint kcb_name_ck check ( regexp_like(name, '[A-Z]', 'c' ) )
, constraint kcb_acctype_ck check ( acctype in ( 'S' ,'C' ,'R' ) )
-- acctype was spelled incorrectly.
, constraint kcb_bal_ck check( ( acctype ='S' and bal >= 5000 )
or ( acctype = 'C' and bal >= 10000 )
or ( acctype ='R' and bal >= 5000 )
) -- this parenthesis was missing
)
Here's a SQL Fiddle to demonstrate.
The main difference between this and your own is regexp_like(name, '[A-Z]', 'c' ). This ensures that the characters in the column name are solely contained within the group A-Z, that is the set of the upper-case Latin alphabet. The match_parameter 'c', specifies that the matching should be case-sensitive. The default case-sensitivity is determined by your NLS_SORT parameter, so you may not need to specify this explicitly but it's wise to do so.