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

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)

Related

Checking if date in table B is between date in Table A before inserting SQLite

I have a table called Project with start and end dates. I also have a table called Plan which have its own start and end dates column. But I some way to validate that the Plans start/end date is between the matching Project start/end date.
I dont know if its appropriate to add a check when I create the table or when I insert rows to the Plan table. So far I have tried both with no luck.
The following code gives me an error message no such column. Does anyone know how to fix this problem? Thanks in advance.
https://i.stack.imgur.com/UC5Ai.png
%%sql
DROP TABLE IF EXISTS Plan;
CREATE TABLE Plan (
pID varchar(255) NOT NULL UNIQUE,
projectID varchar(255) NOT NULL UNIQUE,
name varchar(255) NOT NULL DEFAULT ' ',
startDate DATE NOT NULL DEFAULT '2000-12-31',
endDate DARE NOT NULL DEFAULT '2000-12-31'
CHECK (JulianDay(startDate) <= JulianDay(endDate) AND (startDate >= Project.startDate) AND
(endDate <= Project.endDate)),
PRIMARY KEY (pID, projectID),
FOREIGN KEY (projectID) REFERENCES Project(projectID)
);
You need a BEFORE INSERT trigger:
CREATE TRIGGER trg_ins_plan BEFORE INSERT ON Plan
BEGIN
SELECT
CASE
WHEN NOT EXISTS (
SELECT 1
FROM Project p
WHERE p.projectID = NEW.projectID AND p.startDate <= NEW.startDate AND p.endDate >= NEW.endDate
)
THEN RAISE(ABORT, 'Invalid dates')
END;
END;
and a BEFORE UPDATE trigger:
CREATE TRIGGER trg_upd_plan BEFORE UPDATE ON Plan
BEGIN
SELECT
CASE
WHEN NOT EXISTS (
SELECT 1
FROM Project p
WHERE p.projectID = NEW.projectID AND p.startDate <= NEW.startDate AND p.endDate >= NEW.endDate
)
THEN RAISE(ABORT, 'Invalid dates')
END;
END;
Also, change the CHECK constraint for the dates to:
CHECK (JulianDay(startDate) <= JulianDay(endDate))
or just:
CHECK (startDate <= endDate)
See the demo.

Business Rule in ORACLE SQL

I need to respect this Business Rule: Set EXPIRATION_DATE NULL if the TENANT is an OWNER of the APARTMENT. EXPIRATION_DATE indicates the expiry date of a rental contract
TENANT (COD_T, EXPIRATION_DATE, COD_APARTMENT) --where the tenant is living
OWNER (COD_O, COD_APARTMENT) --apartments that belong to the owner
APARTMENT (COD_APARTMENT)
I'd like to know: if I use this script and the CHECK became true, it will set automatically EXPIRATION_DATE to NULL?
If not, how can I do it?
CREATE TABLE TENANT(
COD_T CHAR(5) PRIMARY KEY,
COD_APARTMENT INT REFERENCES APARTMENT(COD_APARTMENT),
EXPIRATION_DATE DATE
(CHECK (COD_T IN ( SELECT COD_O -- same type COD_T
FROM OWNER O
WHERE COD_APARTMENT = O.CODAPARTMENT
)
)
AND EXPIRATION_DATE IS NULL
);
You can use a view do get the data in your required check constraint format
CREATE OR replace VIEW tenant_vw
AS
SELECT cod_t,
cod_apartment,
CASE
WHEN cod_t IN (SELECT cod_o -- same type COD_T
FROM owner O
WHERE cod_apartment = O.codapartment) THEN NULL
ELSE expiration_date
END AS EXPIRATION_DATE
FROM tenant;
You can use the view for such requirement but if you want your data to be updated like this then you need to create the trigger as follows:
CREATE TRIGGER TRG_TENANT
BEFORE INSERT OR UPDATE ON TENANT
FOR EACH ROW
DECLARE
CNT NUMBER;
BEGIN
SELECT COUNT(1)
INTO CNT
FROM OWNER O
WHERE O.CODAPARTMENT = :NEW.COD_APARTMENT
AND O.COD_O = :NEW.COD_T;
IF CNT > 0 THEN
:NEW.EXPIRATION_DATE := NULL;
END IF;
END;
/

How to use SELECT in function to compare fields

There are 3 tables: Event, Booking, and Booking_Day.
The idea is that one can book separate days of the event.
I would like to put a constraint on Booking_Day so that Day has to be within Date_Start and Date_End range of the corresponding Event. I decided to use a function that will do this
create table Event
(
Event_ID int identity
constraint Event_pk
primary key nonclustered,
Date_Start date not null,
Date_End date
)
create table Booking
(
Booking_ID int identity
constraint Booking_pk
primary key nonclustered,
Event_ID int not null
constraint Booking_Event_Event_ID_fk
references Event
)
create table Booking_Day
(
Day date not null,
Booking_ID int not null
constraint Booking_Day_Booking_Booking_ID_fk
references Booking,
constraint Booking_Day_pk
primary key nonclustered (Day, Booking_ID)
)
And the function:
CREATE FUNCTION check_if_in_range (
#Event_id int,
#Day DATE
) RETURNS int
BEGIN
declare #result TABLE (Day DATE,Booking_ID INT,Event_ID INT,Date_start DATE, Data_end DATE)
INSERT into #result
SELECT Booking_Day.Day, Booking.Event_ID, Event.Date_Start, Event.Date_End
FROM ((Booking_Day INNER JOIN Booking on Booking_Day.Booking_ID = B.Booking_ID )
INNER JOIN Event on Event.Event_ID = Booking.Event_ID) WHERE Booking_Day.Day = #Day AND B.Event_ID = #Event_id
return ((#Day >= #result.Date_start) AND (#Day <= #result.Data_end))
END
Because of the primary key constraint on Booking_day table, the above should return only one row.
When trying to add function do database I get “[[S0001][137] Must declare the scalar variable “#result".
How do I deal with it? Is my approach entirely wrong and I don’t need a table within the ​function for this?
I don't understand why you would be using a table variable for this. You cannot just refer to a table unless you specify a FROM clause -- you are confusing table variables and scalar variables.
But why bother with variables at all?
IF (EXISTS (SELECT 1
FROM Booking_Day bd INNER JOIN
Booking b
ON bd.Booking_ID = B.Booking_ID INNER JOIN
Event e
ON e.Event_ID = b.Event_ID
WHERE b.Day = #Day AND
b.Event_ID = #Event_id AND
#Day >= e.Date_Start AND
#Day <= e.Data_end
)
)
BEGIN
return 1
END;
return 0

SQL Server - Limit maximum of 8 records per week to be inserted for a staff_schedule table for a single staff

I have a staff table and then staff_schedule table which references staff id. I want to limit the number of schedules created for a single staff to 8 records per week. How can I do that?
Thanks in advance.
What I have so far..
CREATE TABLE Staff
(
staffID int,
fullName varchar(100) NOT NULL,
s_category varchar(25),
s_email varchar(50),
s_contactNo int,
speciality varchar(100),
qualifications varchar(250),
pre_employment varchar(200),
salary numeric(8,2),
staff_gender char(1),
CONSTRAINT PK_Staff PRIMARY KEY (staffID),
CONSTRAINT CHK_StaffGender CHECK (staff_gender='M' OR staff_gender='F'),
CONSTRAINT CHK_FullName CHECK (fullName NOT LIKE '%0%' AND fullName NOT LIKE '%1%' AND fullName NOT LIKE '%2%' AND fullName NOT LIKE '%3%' AND fullName NOT LIKE '%4%' AND fullName NOT LIKE '%5%' AND fullName NOT LIKE '%6%' AND fullName NOT LIKE '%7%' AND fullName NOT LIKE '%8%' AND fullName NOT LIKE '%9%'),
CONSTRAINT CHK_SALARY CHECK (salary>0 AND salary<=150000)
);
CREATE TABLE Staff_Allocation
(
allocationId int,
staff_Id int,
branch_Id int,
staff_start_date DateTime,
staff_end_date DateTime,
CONSTRAINT PK_Staff_Allocation PRIMARY KEY (allocationId),
CONSTRAINT FK_Staff_Allocation_Staff FOREIGN KEY (staff_Id) REFERENCES Staff(staffID),
CONSTRAINT FK_Staff_Allocation_Branch FOREIGN KEY (branch_Id) REFERENCES Branch(branchID),
CONSTRAINT CHK_StaffAllocationRotaDaily CHECK (DATEDIFF(hh, staff_start_date, staff_end_date) <=6)
);
A trigger is definitely the way to go for enforcing this constraint. TABLE constraints cannot enforce requirements that span multiple rows.
There are a number of things to test for your requirements
Single row inserted
Multiple rows attempted to be inserted across different staff_ids
Multiple rows attempted to be inserted for same staff_id
Below trigger will satisfy these. I left the date range check as empty as I'm not sure the exact criteria. The inner query collects all the rows from rows being inserted along with all rows already present for staff_ids in the current insert. The outer query aggregates by staff_id after applying the date range criteria. Finally the IF EXISTS check is used to throw an error.
CREATE TRIGGER insert_checkScheduleCount
ON Staff_Allocation AS
BEGIN
IF EXISTS (
SELECT * FROM
(
SELECT staff_id, staff_start_date, staff_end_date
FROM
inserted // relevant columns from the current set of rows
// attempting to be inserted
UNION ALL
SELECT staff_id, staff_start_date, staff_end_date
FROM
Staff_Allocation
WHERE
staff_id IN (SELECT staff_id FROM inserted)
// rows already in the table for staff being touched in this insert
) AS staffRows
WHERE
<apply you criteria on staff_start_date and staff_end_date>
GROUP BY
staff_id
HAVING COUNT(*) > 8
)
BEGIN
RAISERROR(N'More than 8 rows', 16, -1)
END
END
You can create an instead of trigger:
Sample Query for your reference:
CREATE TRIGGER INSTEADOF_INS
ON Staff_Allocation
INSTEAD OF INSERT AS
BEGIN
DECLARE #CNT_ALLOC TINYINT
DECLARE #CNT_ALLOC_TAB TINYINT
DECLARE #ST_DATE DATE
DECLARE #END_DATE DATE
SELECT #ST_DATE=DATEADD(dd, -(DATEPART(dw, GETDATE())-1), GETDATE())
SELECT #END_DATE=DATEADD(dd, 7-(DATEPART(dw, GETDATE())), GETDATE())
SELECT #CNT_ALLOC=COUNT(*)
FROM Staff_Allocation S INNER JOIN INSERTED I
ON S.staff_Id = I.staff_Id AND S.staff_start_date>#ST_DATE AND S.staff_end_date<#END_DATE
IF (#CNT_ALLOC >8 )
BEGIN
RAISERROR (N'8 rows Per Week',
16, 1)
RETURN
END
INSERT INTO Staff_Allocation (allocationId, staff_Id, branch_Id,staff_start_date,staff_end_date)
SELECT allocationId, staff_Id, branch_Id,staff_start_date,staff_end_date
FROM inserted
END
GO
If you do not want to use a trigger you could just use an IF statement
DECLARE #TableCount INT
SELECT #TableCount = COUNT(*)
FROM YourTable
WHERE StartDate BETWEEN GETDATE()-7 AND GETDATE()
AND EndDate BETWEEN GETDATE()-7 AND GETDATE()
IF #TableCount < 8
INSERT...
ELSE
RAISERROR...
RETURN

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.