Postgresql function to create table with dynamic table name? - postgresql

Let's say I have several users, each with their own set of contacts. Users get to select which 3rd parties have access to their contacts.
I could maybe create 1 large contact table and have an 'owner' column so that I can identify which contacts belong to which users. However, I'd then need to maintain row-level permissions so that I can restrict which 3rd parties do/don't have access to specific user contacts.
Instead, I think (and I could be mistaken here) it makes more sense to have a contact table for each user, e.g.: contact_e878df81_eba1_4a61_b592_30ac7100362a. I could then manage permissions in a separate table.
To create these 'dynamic contact tables', I have the following function:
CREATE OR REPLACE FUNCTION create_contact_table(IN tbl_name text) RETURNS INT AS $$
DECLARE
table_name text;
BEGIN
table_name = tbl_name;
create table IF NOT EXISTS table_name
(
id serial not null
constraint test_pkey
primary key,
firstname varchar not null,
lastname varchar not null,
age int not null,
address varchar not null,
email varchar not null,
created timestamp default CURRENT_TIMESTAMP
);
alter table table_name
owner to postgres;
RETURN 1;
END ;
$$ LANGUAGE plpgsql;
When I execute the function (create_contact_table(contact_e878df81_eba1_4a61_b592_30ac7100362a), the table is created, but the name of the table is table_name...
Why is that? How can I fix it so that the table name is correct?

You can do it only using dynamic SQL, for example:
CREATE OR REPLACE FUNCTION create_contact_table(tbl_name text)
RETURNS boolean
LANGUAGE plpgsql
AS $function$
declare
v_sql text;
t1 text;
begin
v_sql = '
create table %I
(
id serial not null
constraint test_pkey
primary key,
firstname varchar not null,
lastname varchar not null,
age int not null,
address varchar not null,
email varchar not null,
created timestamp default CURRENT_TIMESTAMP
);
';
raise notice 'SQL:: %', v_sql;
EXECUTE format(v_sql, tbl_name);
return true;
END;
$function$
;

Related

I can not compute age from datebirth with trigger

I need compute age and update column of age with trigger after Insert Or Update.
Here is my code
CREATE OR REPLACE function to_age()
RETURNS TRIGGER AS $$
declare ages int;
BEGIN
select date_part('year', age(old.dob)) into ages from employees;
-- date_part('year', age(old.dob));
new.age_of_person := ages;
raise notice 'success(%)', new.age_of_person;
RETURN new;
end;
$$
LANGUAGE plpgsql;
CREATE TRIGGER ages
AFTER INSERT OR UPDATE ON employees
FOR EACH STATEMENT
EXECUTE FUNCTION to_age();
DROP TRIGGER ages on employees;
And here is my table that I need update.
CREATE TABLE employees(
id INT GENERATED ALWAYS AS IDENTITY,
first_name VARCHAR(40) NOT NULL,
last_name VARCHAR(40) NOT NULL,
dob timestamp,
age_of_person int,
PRIMARY KEY(id)
);
I have mistake and I did not find where. I can not compute age, it raised null in the function.

CREATE TRIGGER for two tables in Postgres

CREATE TRIGGER for two tables in Postgres
I am using Postgres. I have two tables here in my database, payroll and staff_absences. staff_absences is a link table to a payroll table that logs what absence the staff has and to calculate in payroll table.
The staff absent is calculated from start_date - end_date by a trigger in the staff_absences table. I'm trying to input absences_no into payroll by a trigger.
The trigger is accepted by the schema and I can insert data into staff_absences table. However, it gives me this error when I try to update payroll table.
{ERROR: missing FROM-clause entry for table "staff_absences"
LINE 1: SELECT NEW.staff_absences.start_date - NEW.staff_absences.en...
^
QUERY: SELECT NEW.staff_absences.start_date - NEW.staff_absences.end_date
CONTEXT: PL/pgSQL function lowlands_db.pay_trgr() line 4 at assignment}
These are the create scripts for payroll and staff_absences:
CREATE TABLE lowlands_db.payroll
(
payroll_id integer NOT NULL,
annual_salary numeric NOT NULL,
appointment_bonus numeric,
absences_id integer NOT NULL,
staff_id integer NOT NULL,
total_monthly_salary numeric,
monthly_salary numeric,
absence_no numeric,
tax numeric,
CONSTRAINT payroll_pkey PRIMARY KEY (payroll_id),
CONSTRAINT payroll_staff_id_fkey FOREIGN KEY (staff_id)
REFERENCES lowlands_db.staff (staff_id) MATCH SIMPLE
ON UPDATE CASCADE
ON DELETE NO ACTION
CREATE TABLE lowlands_db.staff_absences
(
absences_id integer NOT NULL,
start_date date NOT NULL,
end_date date,
cause_of_absences text COLLATE pg_catalog."default" NOT NULL,
staff_id integer NOT NULL,
absen_no numeric,
CONSTRAINT staff_absences_pkey PRIMARY KEY (absences_id),
CONSTRAINT staff_absences_staff_id_fkey FOREIGN KEY (staff_id)
REFERENCES lowlands_db.staff (staff_id) MATCH SIMPLE
ON UPDATE CASCADE
ON DELETE NO ACTION
CREATE FUNCTION lowlands_db.asb_pay_trg()
RETURNS trigger
LANGUAGE 'plpgsql'
AS $BODY$
BEGIN
UPDATE lowlands_db.payroll
SET lowlands_db.payroll.absence_no = new.staff_absences.absen_no
WHERE lowlands_db.staff_absences.absences_id = NEW.lowlands_db.payroll.absences_id;
RETURN NEW;
END;
$BODY$;
CREATE FUNCTION lowlands_db.pay_trgr()
RETURNS trigger
LANGUAGE 'plpgsql'
COST 100
VOLATILE NOT LEAKPROOF
AS $BODY$
BEGIN
NEW.absences_no = NEW.staff_absences.start_date - NEW.staff_absences.end_date;
NEW.monthly_salary = ROUND(NEW.annual_salary /12 );
NEW.tax = ROUND(NEW.monthly_salary * .2);
NEW.total_monthly_salary = ROUND((NEW.monthly_salary - NEW.tax)+ NEW.appointment_bonus);
RETURN NEW;
END;
$BODY$;
your help in advance.

Loop for multiple select statements

I am a bit confused on how to deal with looping on multiple select statements using PL/SQL for PostgreSQL. I have written a function that works as expected for multiple select statements that each return a single row (see code below).
I have a middle table (called bridge_table) that breaks up many-to-many relationship between tables 'stock' and 'client', where one client_id can match up with multiple stock_id.
How/Where do I add in the necessary LOOP syntax to grab all the necessary stock_id's for a client_id? Is this a scenario where a cursor might be useful?
Thanks ahead of time!
CREATE OR REPLACE FUNCTION my_func(
a_id INTEGER,
b_id INTEGER,
c_id INTEGER)
RETURNS BOOLEAN AS $$
DECLARE
current_time TIMESTAMP;
new_inv INTEGER;
inv_id INTEGER;
inv_q INTEGER;
current_quant INTEGER;
still_true boolean := true;
BEGIN
current_time := clock_timestamp();
SELECT i_id INTO inv_id
FROM bridge_table
WHERE bridge_table.client_id=c_id;
SELECT inv_quant INTO inv_q
FROM bridge_table
WHERE bridge_table.client_id=c_id;
SELECT quantity INTO current_quant
FROM stock
WHERE stock.i_id=inv_id;
IF current_quant < inv_q THEN
RAISE NOTICE 'STOP';
still_true := false;
ELSE
new_inv := current_quant - inv_q;
UPDATE stock
SET quantity = new_inv
WHERE i_id=inv_id;
END IF;
IF still_true = true THEN
INSERT INTO log_table (log_id, u_id, client_id, time)
VALUES (a_id, b_id, c_id, current_time);
ELSE
RAISE NOTICE 'TRANSACTION WILL NOT OCCUR';
END IF;
RETURN still_true;
END;
$$ LANGUAGE plpgsql;
Edit:
Table DDLs
CREATE TABLE stock
(
i_id INTEGER PRIMARY KEY,
name VARCHAR(120) NOT NULL,
quantity INTEGER NOT NULL
);
CREATE TABLE clients
(
client_id INTEGER PRIMARY KEY,
name VARCHAR(120) NOT NULL
);
CREATE TABLE bridge_table
(
client_id INTEGER REFERENCES clients (client_id),
i_id INTEGER REFERENCES stock (i_id),
inv_quant INTEGER NOT NULL
);
CREATE TABLE log
(
log_id INTEGER PRIMARY KEY,
u_id INTEGER REFERENCES users (user_id),
client_id INTEGER REFERENCES clients (client_id),
time TIMESTAMP NOT NULL
);

Insert record dynamically inside of Procedural Trigger

We are looking to convert our database over to Postgres (9.3.5), which I have no experience with, and I am trying to get our audit tables up and running. I understand that each table will need its own trigger, but all triggers can call a single function.
The trigger on the table is passing a list of the columns that need to be audited since some of our columns are not tracked.
Here are some of the posts I followed:
- https://stackoverflow.com/a/7915100/229897
- http://www.postgresql.org/docs/9.3/static/plpgsql-statements.html
- http://www.postgresql.org/docs/9.4/static/plpgsql-trigger.html
When I run this I get the error: ERROR: syntax error at or near "$1"
DROP TABLE IF EXISTS people;
DROP TABLE IF EXISTS a_people;
CREATE TABLE IF NOT EXISTS people (
record_id SERIAL PRIMARY KEY NOT NULL,
first_name VARCHAR NOT NULL,
last_name VARCHAR NOT NULL,
last_updated_on TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS a_people (
record_id SERIAL PRIMARY KEY NOT NULL,
a_record_id INT,
first_name VARCHAR NULL,
last_name VARCHAR NULL,
last_updated_on TIMESTAMP
);
/******************************************************/
--the function
CREATE OR REPLACE FUNCTION audit_func()
RETURNS TRIGGER AS
$BODY$
DECLARE
audit TEXT := TG_TABLE_SCHEMA || '.a_' || TG_TABLE_NAME;
cols TEXT := TG_ARGV[0];
BEGIN
EXECUTE format('INSERT INTO %1$s(a_%2$s) SELECT %2$s FROM ($1)', audit, cols) USING OLD;
NEW.last_updated_on = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
/******************************************************/
--the trigger calling the function to update inbound records
CREATE TRIGGER build_user_full_name_trg
BEFORE UPDATE
ON people
FOR EACH ROW WHEN (OLD.* IS DISTINCT FROM NEW.*)
EXECUTE PROCEDURE audit_func('record_id,first_name,last_name');
/******************************************************/
INSERT INTO people (first_name, last_name) VALUES ('George','Lincoln');
UPDATE people SET last_name = 'Washington' WHERE first_name = 'George';
SELECT * FROM people;
I welcome your assistance (and patience)!
This subselect should work:
EXECUTE format('INSERT INTO %1$s(a_%2$s) SELECT %2$s FROM (select ($1).*) XX', audit, cols) USING OLD;

CONSTRAINT to check values from a remotely related table (via join etc.)

I would like to add a constraint that will check values from related table.
I have 3 tables:
CREATE TABLE somethink_usr_rel (
user_id BIGINT NOT NULL,
stomethink_id BIGINT NOT NULL
);
CREATE TABLE usr (
id BIGINT NOT NULL,
role_id BIGINT NOT NULL
);
CREATE TABLE role (
id BIGINT NOT NULL,
type BIGINT NOT NULL
);
(If you want me to put constraint with FK let me know.)
I want to add a constraint to somethink_usr_rel that checks type in role ("two tables away"), e.g.:
ALTER TABLE somethink_usr_rel
ADD CONSTRAINT CH_sm_usr_type_check
CHECK (usr.role.type = 'SOME_ENUM');
I tried to do this with JOINs but didn't succeed. Any idea how to achieve it?
CHECK constraints cannot currently reference other tables. The manual:
Currently, CHECK expressions cannot contain subqueries nor refer to
variables other than columns of the current row.
One way is to use a trigger like demonstrated by #Wolph.
A clean solution without triggers: add redundant columns and include them in FOREIGN KEY constraints, which are the first choice to enforce referential integrity. Related answer on dba.SE with detailed instructions:
Enforcing constraints “two tables away”
Another option would be to "fake" an IMMUTABLE function doing the check and use that in a CHECK constraint. Postgres will allow this, but be aware of possible caveats. Best make that a NOT VALID constraint. See:
Disable all constraints and table checks while restoring a dump
A CHECK constraint is not an option if you need joins. You can create a trigger which raises an error instead.
Have a look at this example: http://www.postgresql.org/docs/9.1/static/plpgsql-trigger.html#PLPGSQL-TRIGGER-EXAMPLE
CREATE TABLE emp (
empname text,
salary integer,
last_date timestamp,
last_user text
);
CREATE FUNCTION emp_stamp() RETURNS trigger AS $emp_stamp$
BEGIN
-- Check that empname and salary are given
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname cannot be null';
END IF;
IF NEW.salary IS NULL THEN
RAISE EXCEPTION '% cannot have null salary', NEW.empname;
END IF;
-- Who works for us when she must pay for it?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;
END IF;
-- Remember who changed the payroll when
NEW.last_date := current_timestamp;
NEW.last_user := current_user;
RETURN NEW;
END;
$emp_stamp$ LANGUAGE plpgsql;
CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp
FOR EACH ROW EXECUTE PROCEDURE emp_stamp();
...i did it so (nazwa=user name, firma = company name) :
CREATE TABLE users
(
id bigserial CONSTRAINT firstkey PRIMARY KEY,
nazwa character varying(20),
firma character varying(50)
);
CREATE TABLE test
(
id bigserial CONSTRAINT firstkey PRIMARY KEY,
firma character varying(50),
towar character varying(20),
nazwisko character varying(20)
);
ALTER TABLE public.test ENABLE ROW LEVEL SECURITY;
CREATE OR REPLACE FUNCTION whoIAM3() RETURNS varchar(50) as $$
declare
result varchar(50);
BEGIN
select into result users.firma from users where users.nazwa = current_user;
return result;
END;
$$ LANGUAGE plpgsql;
CREATE POLICY user_policy ON public.test
USING (firma = whoIAM3());
CREATE FUNCTION test_trigger_function()
RETURNS trigger AS $$
BEGIN
NEW.firma:=whoIam3();
return NEW;
END
$$ LANGUAGE 'plpgsql'
CREATE TRIGGER test_trigger_insert BEFORE INSERT ON test FOR EACH ROW EXECUTE PROCEDURE test_trigger_function();