How to use SELECT in function to compare fields - sql

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

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.

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)

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

List the 5 most expensive toys ordened by price(Postgres)

I want a list of the 5 toys that have cost more money, in descending order if we have delivering each toy every single time.
For example, I have an Ipad that cost 600€ and is requested by child 1, 2 and 3. Now, I have a Nintendo that cost 300€ and is requested 3 times (1 time by child 1 and 2 times by the child 3). I have another toy (Laptop) that cost 360€ and is requested 8 times (2 times by child 1 and 6 times by child 3). Then, I must see:
Laptop because is requested 8 times and cost 360€ (in total 8 * 360€ = 2880€)
Ipad because is requested 3 times and cost 600€ (in total 3 * 600€ = 1800€)
Nintendo
When I have this, I want to see the child's data that more times request the same toy, for example, in case of Nintendo I would like see information about child 2. In case of laptop I would like see information about child 3.
I create this type:
CREATE TYPE ToysList AS (
t_Toy_name VARCHAR(255),
t_Price REAL,
t_Times_requested INTEGER,
t_Total_amount_money REAL,
t_Child_name VARCHAR(255),
t_Child_times_request SMALLINT,
t_Child_address VARCHAR(255),
t_Number_Siblings SMALLINT);
The tables are these:
CREATE TABLE CHILD(
child_id SMALLINT,
child_name VARCHAR(255) NOT NULL,
birth_date DATE NOT NULL,
gender VARCHAR(255) NOT NULL,
address VARCHAR(255),
city VARCHAR(255),
CONSTRAINT PK_CHILD PRIMARY KEY(child_id),
CONSTRAINT VALID_GENDER CHECK (gender IN ('m', 'f')),
CONSTRAINT VALID_DATE CHECK (birth_date <= now())
);
CREATE TABLE letter (
letter_id SMALLINT NOT NULL,
arrival_date DATE DEFAULT now() NOT NULL,
number_toys INTEGER NOT NULL,
child_id SMALLINT,
CONSTRAINT valid_child_id CHECK ((child_id IS NOT NULL)),
CONSTRAINT PK_LETTER PRIMARY KEY(letter_id),
CONSTRAINT CHILD_FK FOREIGN KEY (child_id) REFERENCES CHILD(child_id)
);
CREATE TABLE SIBLING(
child_id1 SMALLINT,
child_id2 SMALLINT,
CONSTRAINT PK_SIBLING PRIMARY KEY(child_id1, child_id2),
CONSTRAINT CHILD1_FK FOREIGN KEY (child_id1) REFERENCES CHILD(child_id),
CONSTRAINT CHILD2_FK FOREIGN KEY (child_id2) REFERENCES CHILD(child_id)
);
CREATE TABLE TOY(
toy_id SMALLINT,
toy_name VARCHAR(255) NOT NULL,
price REAL NOT NULL,
toy_type VARCHAR(255) NOT NULL,
manufacturer VARCHAR(255),
CONSTRAINT PK_TOY PRIMARY KEY(toy_id),
CONSTRAINT POSITIVE_PRICE CHECK (price > 0),
CONSTRAINT VALID_TYPE CHECK(toy_type IN ('symbolic', 'rule', 'educational', 'cooperative', 'other'))
);
CREATE TABLE WISHED_TOY(
letter_id SMALLINT,
toy_id SMALLINT,
CONSTRAINT PK_WISHED_TOY PRIMARY KEY(letter_id, toy_id),
CONSTRAINT LETTER_FK FOREIGN KEY (letter_id) REFERENCES LETTER(letter_id),
CONSTRAINT TOY_FK FOREIGN KEY (toy_id) REFERENCES TOY(toy_id)
);
At this moment I did this:
CREATE OR REPLACE FUNCTION list_top_Toys() RETURNS SETOF ToysList AS $$
DECLARE
l_Toy_name VARCHAR(255);
l_Price REAL;
l_Times_requested INTEGER;--total times requested for this toy
l_Total_amount_money REAL; --total times requested * price toy
l_Child_name VARCHAR(255);
l_Child_times_request SMALLINT; --times request for the child
l_Child_address VARCHAR(255);
l_Number_Siblings SMALLINT;
l_toy_id INTEGER;
l_child_id INTEGER;
l_letter_id INTEGER;
returnset ToysList;
BEGIN
FOR l_toy_id, l_Toy_name, l_Times_requested, l_Total_amount_money
IN SELECT t.toy_id, t.toy_name, COUNT(*), SUM(price) AS totalAmountMoney
FROM toy t INNER JOIN wished_toy WT ON t.toy_id = WT.toy_id
GROUP BY t.toy_id, t.toy_name
ORDER BY totalAmountMoney DESC, t.toy_name
LIMIT 5
LOOP
returnset.t_Toy_name = l_Toy_name;
returnset.t_Times_requested = l_Times_requested;
returnset.t_Total_amount_money = l_Total_amount_money;
SELECT c.child_id, c.child_name, c.address, SUM(L.number_toys) AS totalToys
INTO l_child_id, l_Child_name, l_Child_address, l_Child_times_request
FROM child c
INNER JOIN letter L ON c.child_id = L.child_id
INNER JOIN wished_toy WIS ON WIS.letter_id = L.letter_id
WHERE c.child_id = l_child_id
GROUP BY c.child_id, c.child_name
ORDER BY totalToys DESC
LIMIT 1;
returnset.t_Child_name = l_Child_name;
returnset.t_Child_address = l_Child_address;
returnset.t_Child_times_request = l_Child_times_request;
SELECT COUNT(s.child_id2) AS numberSiblings
INTO l_Number_Siblings
FROM sibling s
INNER JOIN child c1 ON c1.child_id = s.child_id1
WHERE s.child_id1 = l_child_id
LIMIT 1;
returnset.t_Number_Siblings = l_Number_Siblings;
return next returnset;
END LOOP;
END;
$$LANGUAGE plpgsql;
COMMIT;
Can anyone say me what I am doing wrong?
Thank you,
Your function returns setof type of data. So, just select from it`s result, like this:
select * from list_top_Toys();
After that You can manipulate with results as it is table.
But, as I can see, this function needs much more changes.
Second query gives same results in every LOOP iteration, so I changed it to reflect result of first SELECT, and to make Letters table as leading in query.
At first, why group by toy_name - there need to be only group by toy_id.
Also, group by child_name (in first inner query) is redundant.
I would include toy_id in result set, it may be useful in later computations.
Also, You did not set toy price as you says in your post, so first SELECT have to have toy`s price.
So, my version of your function will be:
CREATE OR REPLACE FUNCTION list_top_Toys()
RETURNS SETOF ToysList
AS $$
DECLARE
l_Toy_name VARCHAR(255);
l_Price REAL;
l_Times_requested INTEGER;--total times requested for this toy
l_Total_amount_money REAL; --total times requested * price toy
l_Child_name VARCHAR(255);
l_Child_times_request SMALLINT; --times request for the child
l_Child_address VARCHAR(255);
l_Number_Siblings SMALLINT;
l_toy_id INTEGER;
l_child_id INTEGER;
l_letter_id INTEGER;
returnset ToysList;
BEGIN
FOR l_toy_id, l_Toy_name, l_Price, l_Times_requested, l_Total_amount_money
IN SELECT t.toy_id, t.toy_name, t.price, COUNT(*), SUM(price) AS totalAmountMoney
FROM toy t
INNER JOIN wished_toy WT ON t.toy_id = WT.toy_id
GROUP BY t.toy_id
ORDER BY totalAmountMoney DESC, t.toy_name
LIMIT 5
LOOP
returnset.t_Toy_name = l_Toy_name;
returnset.t_Price = l_price;
returnset.t_Times_requested = l_Times_requested;
returnset.t_Total_amount_money = l_Total_amount_money;
SELECT c.child_id, c.child_name, c.address, SUM(L.number_toys) AS totalToys
INTO l_child_id, l_Child_name, l_Child_address, l_Child_times_request
FROM letter L
INNER JOIN child c ON c.child_id = L.child_id
INNER JOIN wished_toy WIS ON WIS.letter_id = L.letter_id
WHERE wis.toy_id = l_toy_id
GROUP BY c.child_id, c.child_name
ORDER BY totalToys DESC
LIMIT 1;
returnset.t_Child_name = l_Child_name;
returnset.t_Child_address = l_Child_address;
returnset.t_Child_times_request = l_Child_times_request;
SELECT COUNT(s.child_id2) AS numberSiblings
INTO l_Number_Siblings
FROM sibling s
INNER JOIN child c1 ON c1.child_id = s.child_id1
WHERE s.child_id1 = l_child_id
LIMIT 1;
returnset.t_Number_Siblings = l_Number_Siblings;
return next returnset;
END LOOP;
END;
$$LANGUAGE plpgsql;
I did not touch siblings query.

The INSERT statement conflicted with the constraint

I want to make a date constraint in my table (I use sql server). I want to make sure that the date in one of my columns is later than the current date and time (I know it sounds weird, but it's an assignment so I have no choice). I tried to do it this way:
ALTER TABLE sales ADD CONSTRAINT d CHECK (Date > CURRENT_TIMESTAMP);
but later when inserting DEFAULT into date column I get the following error:
The INSERT statement conflicted with the CHECK constraint "d".
The conflict occurred in database "Newsagents", table "dbo.Sales", column 'Date'.
This is the said table:
CREATE TABLE Sales (
ID INT IDENTITY(1,1) NOT NULL ,
ClientID INT REFERENCES Client(ClientID),
ProductNumber CHAR(10) REFERENCES Product(ProductNumber),
Quantity INT NOT NULL,
Price FLOAT NOT NULL ,
Date TIMESTAMP NOT NULL,
PRIMARY KEY ( ID )
and this how I insert my data into Sales column and get the constraint conflict:
DECLARE #counter INT
DECLARE #quantity int
DECLARE #prodNum varchar(20)
SET #counter = 0
WHILE #counter < 10
BEGIN
SET #quantity = (select FLOOR(RAND()*100))
SET #prodNum = (select TOP 1 ProductNumber from Product Order by NEWID())
insert into Sales (ClientID, ProductNumber, Quantity, Price, Date )
values(
(select TOP 1 ClientID from Client Order by NEWID()),
(select #prodNum),
(select #quantity),
((select #quantity)*(select TOP 1 Price from Product where ProductNumber = #prodNum)),
DEFAULT
)
SET #counter = #counter + 1
END
Is there a different way to do this? Or am I doing something wrong?
ALTER TABLE sales ADD CONSTRAINT d CHECK (Date > GETDATE());
change the Date column to datetime