Need a constraint to conditionally avoid certain uniques - sql

I created and filled the following table:
CREATE TABLE Assignments(
ID_CxC CHAR(3) NOT NULL PRIMARY KEY,
truck_code char(3) NOT NULL REFERENCES Trucks(truck_code),
driver_code char(5) NOT NULL REFERENCES Drivers(driver_code),
[date] DATE NOT NULL
);
INSERT Assignments
VALUES(1,1,1,'06-11-2021');
INSERT Assignments
VALUES(2,2,2,'06-11-2021');
INSERT Assignments
VALUES(3,3,3,'06-11-2021');
INSERT Assignments
VALUES(4,4,4,'06-11-2021');
INSERT Assignments
VALUES(5,5,5,'06-11-2021');
INSERT Assignments
VALUES(6,6,6,'06-11-2021');
INSERT Assignments
VALUES(7,1,1,'06-11-2021');
INSERT Assignments
VALUES(8,2,2,'06-11-2021');
INSERT Assignments
VALUES(9,3,3,'06-11-2021');
INSERT Assignments
VALUES(10,4,4,'06-11-2021');
I need to create a constraint that will only allow a driver to have more than one truck assigned on a single day if it is the same truck as previously assigned that same day.
I was attempting to write a CHECK constraint where in case UNIQUE(driver_code,date) is broken, the statement would only allow an insert or update if it verifies that UNIQUE(driver_code,truck_code,date) is also false.

Write a boolean function that passes in Dateval, Truckval, & Driverval and returns the value of
Not Exists (Select id_CxC From Assignments
Where Date = Dateval and Driver=Driverval
And Truck<>Truckval)
Set up a table check constraint that calls this function, prefaced with the schema name.

Related

Is it possible to store a query in a variable and use that variable in Insert query? "#countrid =SELECT id FROM COUNTRIES WHERE description = 'asdf';"

So I've been going through SQL migrations to insert data in a SEQUENTIAL manner specifically from parent to child.
I've inserted data in the parent table. Now I've to store the primary key value of that
specific row (WHERE condition is defined in query for reference " where description = '1234'") in a variable.
And while inserting data to the child table I've to use that primary key value stored in a variable in place of a foreign key column("country_code_id") of the child table.
I'm using Postgresql
CREATE TABLE Countries
(
id SERIAL,
description VARCHAR(100),
CONSTRAINT coutry_pkey PRIMARY KEY (id)
);
CREATE TABLE Cities
(
country_code_id int ,
city_id int,
description VARCHAR(100),
CONSTRAINT cities_pkey PRIMARY KEY (city_id),
CONSTRAINT fk_cities_countries FOREIGN KEY (country_code_id) REFERENCES Countries (id)
);
INSERT INTO COUNTRIES (description) VALUES('asdf');
#countrid = SELECT id FROM COUNTRIES WHERE description = 'asdf';
INSERT INTO cities VALUES (countrid, 1 , 'abc');
SQL does not have variables. The normal way to do this is to use INSERT ... RETURNING:
INSERT INTO countries (description) VALUES ('1234')
RETURNING id;
This will return the automatically generated primary key. You store that in a variable on the client side and run a second statement:
INSERT INTO cities (country_code_id, city_id, description)
VALUES (4711, 1, 'abc');
where 4711 is the value returned from the first statement. To avoid hard-coding the value, you can use a prepared statement, which also will boost performance.
An alternative, more complicated, solution is to run both statements in a single statement using a common table expression:
WITH country_ids AS (
INSERT INTO countries (description) VALUES ('1234')
RETURNING id
INSERT INTO (country_code_id, city_id, description)
SELECT id, 1, 'abc'
FROM country_ids;

Multi-Column check constraint

Consider a simple table..
create table dbo.car( car_guid UNIQUEIDENTIFIER default(newid())
, car_type varchar(20) not null
, wind_protector varchar(20) not null
)
insert into dbo.car(car_type, wind_protector) VALUES('HARD_TOP', 'NA')
insert into dbo.car(car_type, wind_protector) VALUES('CONVERTIBLE', 'FLAPBLAST_3')
insert into dbo.car(car_type, wind_protector) values('CONVERTIBLE', 'FLAPBLAST_2')
I'm trying to craft a check constraint that says if car_type is "CONVERTIBLE" then wind_protector can be "FLAPBLAST_2" or "FLAPBLAST_3". Otherwise the value of wind_protector is "NA". The column can not be null.
I have the basic check constraint written..
([wind_protector]='FLAPBLAST_3' OR [wind_protector]='FLAPBLAST_3')
I'm stuck on writing the check constraint across two columns and using and or logic.
Is it possible to do what I'm looking to accomplish?
Thanks,
I think you're after the following constraint:
alter table car
add constraint chk1
check (
( car_type='CONVERTIBLE' and wind_protector in ('FLAPBLAST_2','FLAPBLAST_3'))
or wind_protector='NA'
);
You could do this with a simple multi-column constraint with AND OR logic
CHECK (
car_type = 'CONVERTIBLE' AND wind_protector IN ('FLAPBLAST_2', 'FLAPBLAST_3')
OR
car_type = 'CONVERTIBLE' AND wind_protector = 'NA'
)
But you don't want a check constraint here. wind_protector is not a property of Car, it is a property of CarType. And it can have multiple wind_protector.
So you need to normalize your schema into proper Third Normal Form. You need a few more tables: CarType which contains each car type. Then WindProtector table contains possible options for wind protectors. And finally a table which joins them and defines which combinations are possible:
create table dbo.CarType (
car_type varchar(20) not null primary key
);
insert dbo.CarType (car_type) VALUES
('HARD_TOP'),
('CONVERTIBLE');
create table dbo.WindProtector (
wind_protector varchar(20) not null primary key
);
insert dbo.WindProtector (wind_protector) VALUES
('NA'),
('FLAPBLAST_2'),
('FLAPBLAST_3');
create table dbo.CarOptions (
options_id int not null primary key -- surrogate key
, car_type varchar(20) not null references CarType (car_type)
, wind_protector varchar(20) not null references WindProtector (wind_protector)
, unique (car_type, wind_protector)
);
insert into dbo.CarOptions (options_id, car_type, wind_protector) VALUES
(1, 'HARD_TOP', 'NA'),
(2, 'CONVERTIBLE', 'FLAPBLAST_3'),
(3, 'CONVERTIBLE', 'FLAPBLAST_2');
create table dbo.Car (
car_guid UNIQUEIDENTIFIER default(newid())
, option_id int not null references CarOptions (options_id)
);
insert dbo.Car (option_id) VALUES
(1),
(2),
(3);
db<>fiddle
You may want to merge Car and CarOptions into one table, depending on requirements.
I would also recommend using NULL instead of 'NA'.

Passing parameters for a constraint function

I have the following table that joins driver and truck tables in order to assign trucks to drivers. I need to constraint entries that belong to the same driver on the same day, and DO NOT include the same truck code.
CREATE TABLE Assignments(
ID_CxC CHAR(3) NOT NULL PRIMARY KEY,
truck_code char(3) NOT NULL REFERENCES Trucks(truck_code),
driver_code char(5) NOT NULL REFERENCES Drivers(driver_code),
[date] DATE NOT NULL
);
INSERT Assignments
VALUES(1,1,1,'06-11-2021');
INSERT Assignments
VALUES(2,2,2,'06-11-2021');
INSERT Assignments
VALUES(3,3,3,'06-11-2021');
INSERT Assignments
VALUES(4,4,4,'06-11-2021');
INSERT Assignments
VALUES(5,5,5,'06-11-2021');
INSERT Assignments
VALUES(6,6,6,'06-11-2021');
INSERT Assignments
VALUES(7,1,1,'06-11-2021');
INSERT Assignments
VALUES(8,2,2,'06-11-2021');
INSERT Assignments
VALUES(9,3,3,'06-11-2021');
INSERT Assignments
VALUES(10,4,4,'06-11-2021');
It was suggested to create a boolean function to pass along dateval, truckval and driverval, and returns the following.
Not Exists (Select id_CxC From Assignments
Where Date = Dateval and Driver=Driverval
And Truck<>Truckval)
I have tried creating a function returning a bit datatype, but I honestly lack knowledge on user created functions, how would I go about passing the parameters for the function?
Is there an easier approach to the whole situation?
You clearly have two different entities -- but they are related. You have something like an "assignment" where a driver has a truck for one or more days. Then you have something like a "trip" where a driver uses the truck (what you are currently calling an "assignment").
So, I would model this as:
CREATE TABLE assignments (
ID_CxC CHAR(3) PRIMARY KEY,
truck_code char(3) NOT NULL REFERENCES Trucks(truck_code),
driver_code char(5) NOT NULL REFERENCES Drivers(driver_code),
date DATE NOT NULL,
UNIQUE (driver_code, date)
);
Note that there is only one row per driver and per date. Voila! Only one truck.
CREATE TABLE trips (
ID_trip INT IDENTITY(1, 1) PRIMARY KEY,
AssignmentId CHAR(3) NOT NULL REFERENCES Assignments(ID_CxC)
);
You can then add as many trips for a driver that you like, but for a given driver, they all have the same truck.

How do i specify an if statement within a trigger?

i need to insert an "exam entry" row into a table.
But the exam entry cannot be inserted if a student (recognised by the student number sno) is already entered into that exam (recognised by the exam code excode), and the entry also cannot be inserted if the student has more than one exam on the same day (i have an exam table holding the information abotu the exam dates).
I am fairly certain that i should be using an insert trigger function for this and have been looking at:
Example 39-3 from http://www.postgresql.org/docs/9.2/static/plpgsql-trigger.html
so far i have:
INSERT INTO
entry(excode, sno, egrade) VALUES (2, 1, 98.56)
CREATE FUNCTION entry_insert() RETURNS trigger AS $entry_insert$
BEGIN
--Check student is not entered into same exam twice
IF BLA BLA
RAISE EXCEPTION 'A student cannot be be entered into the same exam more than once';
END IF;
--Check student not taking more than one exam on same day
IF BLA BLA
RAISE EXCEPTION 'A student cannot take more than one exam on the same day';
END IF;
END;
$entry_insert$ LANGUAGE PLPGSQL;
CREATE TRIGGER entry_insert BEFORE INSERT ON entry
FOR EACH ROW EXECUTE PROCEDURE entry_insert();
the places where I've put bla bla is where i need the conditions that i cant quite figure out how to meet my conditions.
would love some help?
edit: my exam table
CREATE TABLE exam (
excode CHAR(4) NOT NULL PRIMARY KEY,
extitle VARCHAR(20) NOT NULL,
exlocation VARCHAR(20) NOT NULL, --I'm assuming that an exam will have a location confirmed prior to insertion into the table--
exdate DATE NOT NULL
CONSTRAINT incorrectDate
CHECK (exdate >='01/06/2015' AND exdate <= '30/06/2015'), /* I'm assuming that all exams must have a date confirmed or otherwise the exam wouldn't be inserted into the table*/
extime TIME NOT NULL, -- I'm assuming that an exam will have a time confirmed prior to insertion into the table--
CONSTRAINT incorrect_time
CHECK (extime BETWEEN '09:00:00' AND '18:00:00')
);
You don't need to use triggers for this, you can use normal table constraints, although you will need to define a function.
Your first requirement - that the same student cannot enter the same exam twice - can be checked using a UNIQUE constraint on (excode,sno). In theory this check is redundant because the second check (that a student cannot enter more than one exam per day) would also be violated by that. However, to cater for the possibility of subsequent record updates, this UNIQUE constraint is still needed.
The second requirement can be met using a CHECK constraint. However you have to create a function, because it is not possible to use a subquery inside a CHECK constraint.
Here is an example:
-- Assume we have an exams table. This table specifies the time of each exam
CREATE TABLE exams(excode SERIAL PRIMARY KEY, extime timestamp);
-- Create the entry table. We cannot add the CHECK constraint right away
-- because we have to define the function first, and the table must exist
-- before we can do that.
CREATE TABLE entry(excode int, sno int, egrade FLOAT, UNIQUE(excode,sno));
-- Create a function, which performs a query to return TRUE if there is
-- another exam already existing which this student is enrolled in that
-- is on the same day as the exame identified with p_excode
CREATE FUNCTION exam_on_day(p_excode int, p_sno int) RETURNS bool as $$
SELECT TRUE
FROM entry
LEFT JOIN exams ON entry.excode=exams.excode
WHERE sno=p_sno AND entry.excode != p_excode
AND date_trunc('day',extime)=(
SELECT date_trunc('day', extime) FROM exams WHERE excode=p_excode
);
$$ LANGUAGE SQL;
-- Add check constraint
ALTER TABLE entry ADD CONSTRAINT exam_on_same_day
CHECK(not exam_on_day(excode, sno));
-- Populate some exames.
-- excode 1
INSERT INTO exams(extime) VALUES('2014-12-06 10:00');
-- excode 2
INSERT INTO exams(extime) VALUES('2014-12-06 15:00');
-- excode 3
INSERT INTO exams(extime) VALUES('2014-12-05 15:00');
Now we can try it out:
harmic=> INSERT INTO entry(excode,sno,egrade) VALUES(1,1,98.5);
INSERT 0 1
harmic=> INSERT INTO entry(excode,sno,egrade) VALUES(1,1,50);
ERROR: duplicate key value violates unique constraint "entry_excode_sno_key"
DETAIL: Key (excode, sno)=(1, 1) already exists.
harmic=> INSERT INTO entry(excode,sno,egrade) VALUES(2,1,99);
ERROR: new row for relation "entry" violates check constraint "exam_on_same_day"
DETAIL: Failing row contains (2, 1, 99).
harmic=> INSERT INTO entry(excode,sno,egrade) VALUES(3,1,75);
INSERT 0 1
harmic=> UPDATE entry SET egrade=98 WHERE excode=1 AND sno=1;
UPDATE 1
test=> UPDATE entry SET excode=2 WHERE excode=3 AND sno=1;
ERROR: new row for relation "entry" violates check constraint "exam_on_same_day"
DETAIL: Failing row contains (2, 1, 75).
Note that I named the constraint meaningfully, so that when you get an error you can see why (useful if you have more than one constraint).
Also note that the function used for the SELECT constraint excludes the record being updated from the check (entry.excode != p_excode) otherwise you could not update any records.
You could still do this with triggers, of course, although it would be unnecessarily complicated to set up. The IF condition would be similar to the function created above.

Basic primary key / foreign key with constraint, sequence, trigger

Learner here in Oracle 11g. I'm having an issue with INSERTing some rows into two tables that are linked by a primary/foreign key relationship.
Basically I create a sequence to start with 1000 and increment by 1.
Then create a 'STORE' table with a ST_ID column
The ST_ID column is linked to the SEQUENCE with a TRIGGER.
Then I have an 'EMPLOYEE' table that has a EST_ID field that is a foreign key to the ST_ID column in the STORE table.
However, when I tried to insert rows I initially got a error saying EST_ID could not be null. So I created a sequence and trigger for EST_ID and now I'm getting an error saying the foreign key constraint is being violated.
I think that was maybe the wrong thing to do. Do I really want E_ID and EST_ID to be identical and how would I get that to happen? With some kind of trigger?
The actual code:
CREATE SEQUENCE "STORSEQ" MINVALUE 1000 MAXVALUE 9999 INCREMENT BY 1 START WITH 1000 NOCACHE NOORDER
NOCYCLE ;
CREATE TABLE "STORE"
( "ST_ID" CHAR(4) NOT NULL ENABLE,
"STADDR_ID" CHAR(4) NOT NULL ENABLE,
CONSTRAINT "STORE_PK" PRIMARY KEY ("ST_ID") ENABLE
) ;
CREATE TABLE "EMPLOYEE"
( "E_ID" CHAR(8) NOT NULL ENABLE,
"EF_NAME" VARCHAR2(20) NOT NULL ENABLE,
"EL_NAME" VARCHAR2(20) NOT NULL ENABLE,
"EST_ID" CHAR(4) NOT NULL ENABLE,
CONSTRAINT "EMPLOYEE_PK" PRIMARY KEY ("E_ID") ENABLE
) ;
alter table "EMPLOYEE" add CONSTRAINT "EMPLOYEE_CON" foreign key ("EST_ID") references
"STORE" ("ST_ID")
/
CREATE OR REPLACE TRIGGER "BI_STORE"
before insert on "STORE"
for each row
begin
if :NEW."ST_ID" is null then
select "STORSEQ".nextval into :NEW."ST_ID" from dual;
end if;
end;
/
At the moment my INSERT code looks like this:
INSERT INTO STORE
(ST_ID, STADDR_ID)
VALUES
(DEFAULT, DEFAULT);
INSERT INTO EMPLOYEE
(EF_NAME, EL_NAME)
VALUES
('James', 'Smith');
When you try to insert data into table that has foreign key reference, it will not get value for id automatically, you need to pass that value.
You can do this:
declare
v_store_id integer;
begin
INSERT INTO STORE (ST_ID, STADDR_ID) VALUES (DEFAULT, DEFAULT)
RETURNING ST_ID INTO v_Store_id;
INSERT INTO EMPLOYEE (EF_NAME, EL_NAME, EST_ID)
VALUES ('James', 'Smith', v_store_id);
end;
You can also insert id in store id table without trigger using this
declare
v_store_id integer;
begin
INSERT INTO STORE (ST_ID, STADDR_ID) VALUES ("STORSEQ".nextval, DEFAULT)
RETURNING ST_ID INTO v_Store_id;
INSERT INTO EMPLOYEE (EF_NAME, EL_NAME, EST_ID)
VALUES ('James', 'Smith', v_store_id);
end