postrgesql function to modify another table if changes occur in one of the tables - sql

I need to create a trigger that executes a function "modification_function( )" so that when inserting, deleting or updating each row of the "People" table, the function has to insert or update the information needed by the "People_info" table, or delete it in case it has been deleted from the "People" table.
In order to do any action on the "People_info" table, the function will get all the necessary information from the tables: "People", "Employment" and "Country".
In addition, the varchar attribute 'experience' of the table 'People_info' must store the non-null value of one of the following two attributes of the 'people' table:
The value of the 'age' attribute or
The value of the attribute 'Date of birth'.
'Age' is integer and 'Date of birth' is type date. Therefore, since 'experience' is varchar I must modify the typology of this data using 'to_char'.
In fact I think that to store the non-null value of one of these attributes, an if must be done so that if 'age' IS NULL : experience= date of birth, ELSE experience=age.
The minimum wage attribute of info_persons must contain the values of the salary attribute of the employment table.
I have set up the scheme as follows but I am very blocked when it comes to putting these requirements in the code. I appreciate any proposal to implement it.
CREATE OR REPLACE FUNCTION modification_function()
RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP='UPDATE') THEN
UPDATE PEOPLE_INFO
name =NEW.people.name
id_employee =NEW.employment.id_employee
current employment= NEW.employment.current_employment
minimum_wage= NEW.employment.salary
country_id=NEW.country.country_id
street=NEW.country.street
RETURN NEW;
IF (TG_OP='INSERT') THEN
END IF;
END;
$$ LANGUAGE 'plpgsql';
CREATE TRIGGER trigger
AFTER UPDATE OR DELETE OR INSERT ON people
FOR EACH ROW EXECUTE PROCEDURE modification_function();
TABLE PEOPLE:
name PRIMARY KEY
age
date of birth
level of studies
EMPLOYMENT TABLE
id_employee PRIMARY KEY
current_employment
HEADQUARTERS
salary
seniority
COUNTRY TABLE
country_id PRIMARY KEY
street
zip/postal code
PEOPLE_INFO TABLE
name PRIMARY KEY
id_employee PRIMARY KEY
current_employment
minimum_wage
country_id
street
experience
Thank you in advance

Related

PSQL Constraint based on column value

Is it possible to have a Constraint but only when one column is set to a particular value. For example take this pseudo-code example of a President which checks to make sure there is never more than 1 President at any time (note, this is NOT valid psql syntax)
CREATE TABLE president (
id BIGSERIAL PRIMARY KEY,
current BOOLEAN NOT NULL,
CONSTRAINT there_can_be_only_one CHECK(COUNT(current=true)<=1)
);
You can use the so called partial index to enforce this specific constraint. In SQL Server they are called filtered indexes.
CREATE UNIQUE INDEX IX ON president (current)
WHERE current = true;
This index should prevent having more than one row in a table with current value set to true, because it is defined as unique.
Unfortunately NO as far as I know and anyway it already tells us,
ERROR: aggregate functions are not allowed in check constraints.
But we can use BEFORE trigger to check that the data you are trying to insert should meets the criteria COUNT(current=true)<=1
CREATE TABLE president (
id BIGSERIAL PRIMARY KEY,
current BOOLEAN NOT NULL
);
---------------------------------------------------------------------
CREATE FUNCTION check_current_flag()
RETURNS trigger
AS $current_president$
DECLARE
current_flag_count integer;
BEGIN
SELECT COUNT(*) FILTER (WHERE current = true )
INTO current_flag_count
FROM president;
IF new.current = true
and current_flag_count >= 1 THEN
RAISE EXCEPTION 'There can be only one current president';
-- RETURN NULL;
ELSE
RETURN NEW;
END IF;
END;
$current_president$ LANGUAGE plpgsql;
---------------------------------------------------------------------
CREATE TRIGGER current_president BEFORE INSERT OR UPDATE ON president
FOR EACH ROW EXECUTE PROCEDURE check_current_flag();
Db<>Fiddle for reference
Note:
You can either throw exception in case of preconditions doesn't match ore simply returning NULL will skip the insert and do nothing. as official document says also here

Postgresql SET DEFAULT value from another table SQL

I'm making a sql script so I have create tables, now I have a new table that have columns. One column has a FOREIGN KEY so I need this value to be SET DEFAULT at the value of the value of the original table. For example consider this two table
PERSON(Name,Surename,ID,Age);
EMPLOYER(Name,Surname,Sector,Age);
In Employer I need AGE to be setted on default on the AGE of Person, this only if PERSON have rows or just 1 row.
ID is Primary key for person and Surname,Sector for employer and AGE is FOREIGN KEY in Employer refferenced from Person
Example sql :
CREATE TABLE PERSON(
name VARCHAR(30) ,
surename VARCHAR(20),
ID VARCHAR(50) PRIMARY KEY,
Age INT NOT NULL,
);
CREATE TABLE EMPLOYER(
name VARCHAR(30) ,
Surename VARCHAR(20),
Sector VARCHAR(20),
Age INT NOT NULL,
PRIMARY KEY (Surename,Sector),
FOREIGN KEY (Age) REFERENCES Person(Age) //HERE SET DEFAULT Person(Age), how'??
);
Taking away the poor design choices of this exercise it is possible to assign the value of a column to that of another one using a trigger.
Rough working example below:
create table a (
cola int,
colb int) ;
create table b (
colc int,
cold int);
Create or replace function fn()
returns trigger
as $$ begin
if new.cold is null then
new.cold = (select colb from a where cola = new.colc);
end if;
return new;
end;
$$ language plpgsql;
CREATE TRIGGER
fn
BEFORE INSERT ON
b
FOR EACH ROW EXECUTE PROCEDURE
fn();
Use a trigger rather than a default. I have done things like this (useful occasionally for aggregated full text vectors among other things).
You cannot use a default here because you have no access to the current row data. Therefore there is nothing to look up if it is depending on your values currently being saved.
Instead you want to create a BEFORE trigger which sets the value if it is not set, and looks up data. Note that this has a different limitation because DEFAULT looks at the query (was a value specified) while a trigger looks at the value (i.e. what does your current row look like). Consequently a default can be avoided by explicitly passing in a NULL. But a trigger will populate that anyway.

How to avoid "blank" insert when inserting data into a view with a trigger & insert procedure?

I'm trying to update tables from insert or update call on a PostgreSQL view. Here's a simplified example of what I do:
[Person] table:
id | lastname | firstname | city | age
[Person_View] table:
id | lastname | firstname | city
Here is the trigger and the related procedure :
CREATE TRIGGER tg_update_person_view
INSTEAD OF INSERT OR UPDATE OR DELETE ON
Person_View FOR EACH ROW EXECUTE PROCEDURE update_person_view_table();
CREATE OR REPLACE FUNCTION update_person_view_table()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $function$
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO Person (id, lastname, firstname)
VALUES(NEW.id, NEW.lastname, NEW.firstname);
RETURN NEW;
ELSIF TG_OP = 'UPDATE' THEN
UPDATE Person
SET id=NEW.id, lastname=NEW.lastname, firstname=NEW.firstname
WHERE id=OLD.id;
RETURN NEW;
END IF;
RETURN NEW;
END;
$function$;
If I do:
INSERT INTO Person_View (id, city) VALUES ('3', 'Berlin')
A row with only the ID is added to the view and the parent table.
How can I check in the procedure that columns in which values are being inserted have a "mapping" defined in the procedure and if there ain't any mapped columns, it does not proceed ?
You can define a check constraint on the table, e.g.:
create table person(
id int primary key,
lastname text,
firstname text,
city text,
age int,
check(coalesce(lastname, firstname, city, age::text) is not null)
);
insert into person (id)
values (1);
ERROR: new row for relation "person" violates check constraint "person_check"
DETAIL: Failing row contains (1, null, null, null, null).
The solution works regardless whether any views based on the table were created or not.
Have a separate trigger & trigger function for ON DELETE to simplify. (You are not doing anything ON DELETE anyway?)
A CHECK constraint like klin suggested seems like a good idea. You don't need COALESCE and casting, though. Check a row value for NULL.
CHECK (NOT ROW(lastname, firstname) IS NULL) -- ROW keyword is noise
This enforces at least one notnull value in the row. Works for any number of columns and any data type.
Note in particular that ROW(lastname, firstname) IS NOT NULL is not the same and would not work. Detailed explanation:
NOT NULL constraint over a set of columns
If the CHECK constraint is not an option, you can use the same expression in a trigger - which should be faster than adding it to the trigger function. The manual on CREATE TRIGGER:
Also, a trigger definition can specify a Boolean WHEN condition, which
will be tested to see whether the trigger should be fired. In
row-level triggers the WHEN condition can examine the old and/or new
values of columns of the row.
CREATE TRIGGER tg_update_person_view
INSTEAD OF INSERT OR UPDATE ON Person_View
FOR EACH ROW
WHEN (NOT (NEW.lastname, NEW.firstname) IS NULL) -- more columns?
EXECUTE PROCEDURE update_person_view_table();
If the WHEN expression does not evaluate to TRUE, the trigger function is not even called - so it does not proceed like requested.
However, I missed your trigger INSTEAD OF. The manual:
INSTEAD OF triggers do not support WHEN conditions.
In this case you have to move the check into the function body:
IF NOT (NEW.lastname, NEW.firstname) IS NULL THEN
-- do stuff
END IF;

Is it possible to populate table using trigger on another table in oracle 12c

Using oracle 12c, I have a table for employee and a table for managers, if the newly inserted employee salary >=5000 then he/she is considered manager. So I'd like to create trigger on table employee that checks if the salary of the newly inserted employee >=5000 this row should be duplicated in the manager table. IS this possible? If yes, could you simply give me the right syntax.
Some general words first: This could be considered bad database design. If you consider an employee beyond a certain salary a manager, this almost screams for a column in the same table, either physical or virtual. For example, it could look like this:
CREATE TABLE employees (
id NUMBER,
first_name VARCHAR2(10),
last_name VARCHAR2(10),
salary NUMBER(9,2),
is_manager as (case when salary >= 5000 then 1 else 0 end)
CONSTRAINT employees_pk PRIMARY KEY (id)
);
If you still want to use a trigger and a second managers table, it could work like this:
CREATE OR REPLACE TRIGGER trig_emp_insert
AFTER INSERT
ON employees
FOR EACH ROW
BEGIN
if (:new.salary >= 5000) then
insert into managers (...) values (...)
end if;
END;

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.