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.
Related
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
I have recently started studying PostgreSQL and am having trouble creating triggers.
In the specific case I should check that a male athlete cannot participate in a competition for women and vice versa; in the match_code attribute an 'M' or an 'F' is inserted as the third to last character to identify that the race is for males or females (for example: 'Q100x4M06'); only one character, 'M' or 'F', is stored in the gender attribute.
I would therefore need to understand how to compare them and activate the trigger when they are not correctly entered in the participation table.
This is what i have assumed but i know it is wrong, it is just an idea, can someone help me?
CREATE FUNCTION check()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS $BODY$
DECLARE
x VARCHAR;
y VARCHAR;
BEGIN
SELECT match_code INTO x FROM race;
SELECT gender INTO y FROM athlete;
IF $x LIKE '&$y__' == athlete.gender
THEN
RETURN new;
ELSE
RAISE EXCEPTION $$It is not possible to add an athlete of a gender that is not compatible with the competition$$;
RETURN NULL;
END IF;
END;
$BODY$
CREAT TRIGGER triggerCheck
BEFORE INSERT OR UPDATE ON participation
FOR EACH ROW
EXECUTE PROCEDURE check();
below are the definitions of the tables:
CREATE TABLE race (
ID_g SERIAL NOT NULL,
code_race VARCHAR (20) PRIMARY KEY,
r_date DATE,
discipline VARCHAR (20) NOT NULL
);
CREATE TABLE athlete (
ID_a SERIAL NOT NULL,
code_athlete INT CHECK (codice_atleta >= 0 AND codice_atleta <= 15000) PRIMARY KEY,
name VARCHAR (30),
surname VARCHAR (30) NOT NULL,
nation VARCHAR (3) NOT NULL,
gender CHAR CHECK (gender = 'M' OR gender = 'F'),
b_date DATE,
sponsor VARCHAR (20)
);
CREATE TABLE participation (
ID_p SERIAL NOT NULL,
codR VARCHAR (20) REFERENCES race (code_race) ON DELETE CASCADE ON UPDATE CASCADE,
codA INT REFERENCES athlete (code_athlete) ON DELETE CASCADE ON UPDATE CASCADE,
arrival_order INT CHECK (arrival_order > 0),
r_time TIME DEFAULT '00:00:00.00',
PRIMARY KEY (codG, codA)
);
According to the tables definition, your trigger function should be someting like :
CREATE FUNCTION check()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS $BODY$
BEGIN
IF EXISTS ( SELECT 1
FROM race AS r
INNER JOIN athlete AS a
ON r.code_race ~ (a.gender || '..$')
WHERE r.code_race = NEW.codR
AND a.code_athlete = NEW.codA
)
THEN
RETURN NEW ;
ELSE
RAISE EXCEPTION 'It is not possible to add an athlete of a gender that is not compatible with the competition';
RETURN NULL;
END IF;
END;
$BODY$
In a function called by a trigger, you can (have to) use the variable NEW (resp. OLD) so that to refer to the new row (resp. the old row) to be inserted or updated (resp. only to be updated) in the targeted table ("participation" in your case) see the manual.
The proposed function doesn't need to declare any variable, as the test can be performed directly through the proposed sql query.
The proposed sql query :
First search for the rows in table "race" whose code_race equals the inserted/updated value of participation.codR = NEW.codR
Then search for the rows in table "athlete" whose code_athlete equals
the inserted/updated value of participation.codA = NEW.codA
Finally compare the selected rows from both tables "race" and
"athlete" using a regular expression to compare the code_race with
the gender values, see the manual
By the way,
(a) I don't see the added value of the columns ID of type serial in the tables definition, especially as they are not used in any primary key nor foreign key.
(b) The significant codification defined for the code_race attribute (for instance : 3rd last character defining the gender, an other character defining the competition level, ...) is a quite old-fashion practice that was used 30 years ago when the computer sciences had limited capacities and performances. In a more up-to-date approach, I would suggest you to manage these meaningful information in dedicated columns of the table "race", and then, if you really need a significant composite code_race, to implement it as a generated column :
CREATE TABLE race
( ID_g SERIAL NOT NULL PRIMARY KEY
, gender_race CHAR(1) NOT NULL CHECK (gender_race IN ('M', 'F'))
, competition_level VARCHAR(12) NOT NULL CHECK (competition_level IN ('Q-Qualifiers', 'H-Heats', 'S-Semifinals', 'F=Finals'))
, code_race VARCHAR (20) GENERATED ALWAYS AS (ID_g :: text || '-' || discipline || '-' || gender_race || '-' || left(competition_level, 1)) STORED
, r_date DATE NOT NULL
, discipline VARCHAR (20) NOT NULL
);
see the result in db<>fiddle.
Basically, users when they create a new record in mytable1, there is an id field that needs to be the same across multiple tables. I achieve this by having mytable2 with the s_id as primary key
My current function looks like
CREATE OR REPLACE FUNCTION test.new_record()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
case when new.s_id in (select s_id from mytable1) then
insert into mytable2 (sprn, date_created) select max(s_id) +1, now() from mytable2 ;
update mytable1 set new.s_id = (select max(b.s_id) from mytable2 b);
end case;
RETURN new;
END;
$function$;
Intended was when the s_id is replicated then it would create a new entry on mytable2. This new entry would then be updated onto mytable1
Problem with this function is that right now it does not recognise the new on the update part of the function.
How to keep the s_id take the value on every new insert ?
If you want to have one "generator" across multiple tables, create one sequence that is used across all those tables for the default value:
create sequence the_id_sequence;
create table one
(
id integer primary key default nextval('the_id_sequence')
.... other columns
);
create table two
(
id integer primary key default nextval('the_id_sequence')
.... other columns ...
);
If you want to replicate an ID from one table to another during insert, you only need one sequence:
create table one
(
-- using identity is the preferred over "serial" to auto-generate PK values
id integer primary key generated always as identity
);
create table two
(
id integer primary key
);
create or replace function insert_two()
returns trigger
as
$$
begin
insert into two (id) values (new.id);
return new;
end;
$$
language plpgsql;
create trigger replicate_id
before insert on one
for each row
execute procedure insert_two();
Then if you run:
insert into one (id) values (default);
A row with exactly the same id value will be inserted into table two.
If you don't have a generated ID column so far, use the following syntax:
alter table one
add testidcolumn bigint generated always as identity;
I want to be able to label the database with a single value, i.e its name, from within the database instead of my application, since it will always be one ID per database. For example, something like this:
DATABASE_A.sql
-- Database Name Table
CREATE TABLE database (
name VARCHAR(10) NOT NULL UNIQUE,
);
CREATE TABLE item (
id SERIAL PRIMARY KEY,
name VARCHAR(10) NOT NULL UNIQUE,
);
Insert Into database (name) values ('A');
DATABASE_B.sql
-- Database Name Table
CREATE TABLE database (
name VARCHAR(10) NOT NULL UNIQUE,
);
CREATE TABLE item (
id SERIAL PRIMARY KEY,
name VARCHAR(10) NOT NULL UNIQUE,
);
Insert Into database (name) values ('B');
This is because when they are combined and stored on a SOLR search server their ID is a combination of their database name and their item ID, such as this:
SOLR ITEM ID's
A1
A2
A3
B1
Is it ok to have a single table to define the prefix so that when I do the look up from my SQL website to SOLR I can just do the following query:
database (name) + item (id) = SolrID
I'd be more inclined to build a procedure in each database that contained the database ID, for example:
CREATE OR REPLACE FUNCTION solrid(IN local_id INTEGER, OUT result TEXT) AS $$
DECLARE
database_id TEXT := 'A';
BEGIN
result := database_id || local_id::TEXT;
END;
$$ LANGUAGE PLPGSQL;
Then you could write your select statement like:
SELECT solrid(id), name FROM item;
which seems to be a cleaner solution.
I have a view, and I have a function that returns records from this view.
Here is the view definition:
CREATE VIEW ctags(id, name, descr, freq) AS
SELECT tags.conc_id, expressions.name, concepts.descr, tags.freq
FROM tags, concepts, expressions
WHERE concepts.id = tags.conc_id
AND expressions.id = concepts.expr_id;
The column id references to the table tags, that, references to another table concepts, which, in turn, references to the table expressions.
Here are the table definitions:
CREATE TABLE expressions(
id serial PRIMARY KEY,
name text,
is_dropped bool DEFAULT FALSE,
rank float(53) DEFAULT 0,
state text DEFAULT 'never edited',
UNIQUE(name)
);
CREATE TABLE concepts(
id serial PRIMARY KEY,
expr_id int NOT NULL,
descr text NOT NULL,
source_id int,
equiv_p_id int,
equiv_r_id int,
equiv_len int,
weight int,
is_dropped bool DEFAULT FALSE,
FOREIGN KEY(expr_id) REFERENCES expressions,
FOREIGN KEY(source_id),
FOREIGN KEY(equiv_p_id) REFERENCES concepts,
FOREIGN KEY(equiv_r_id) REFERENCES concepts,
UNIQUE(id,equiv_p_id),
UNIQUE(id,equiv_r_id)
);
CREATE TABLE tags(
conc_id int NOT NULL,
freq int NOT NULL default 0,
UNIQUE(conc_id, freq)
);
The table expressions is also referenced from my view (ctags).
I want my function to combine rows of my view, that have equal values in the column name and that refer to rows of the table concepts with equal values of the column equiv_r_id so that these rows are combined only once, the combined row has one (doesn't matter which) of the ids, the value of the column descr is concatenated from the values of the rows being combined, and the row freq contains the sum of the values from the rows being combined. I have no idea how to do it, any help would be appreciated.
Basically, what you describe looks like this:
CREATE FUNCTION f_test()
RETURNS TABLE(min_id int, name text, all_descr text, sum_freq int) AS
$x$
SELECT min(t.conc_id) -- AS min_id
,e.name
,string_agg(c.descr, ', ') -- AS all_descr
,sum(t.freq) -- AS sum_freq
FROM tags t
JOIN concepts c USING (id)
JOIN expressions e ON e.id = c.expr_id;
-- WHERE e.name IS DISTINCT FROM
$x$
LANGUAGE sql;
Major points:
I ignored the view ctags altogether as it is not needed.
You could also write this as View so far, the function wrapper is not necessary.
You need PostgreSQL 9.0+ for string_agg(). Else you have to substitute with
array_to_string(array_agg(c.descr), ', ')
The only unclear part is this:
and that refer to rows of the table concepts with equal values of the column equiv_r_id so that these rows are combined only once
Waht column exactly refers to what column in table concepts?
concepts.equiv_r_id equals what exactly?
If you can clarify that part, I might be able to incorporate it into the solution.