I can not compute age from datebirth with trigger - postgresql

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.

Related

Postgresql function to create table with dynamic table name?

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$
;

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
);

How to upsert with serial primary key

I have a stored procedure to perform an upsert. However the conflict condition never runs, passing an existing ID always causes it to create a new record.
create or replace function upsert_email(v_id bigint, v_subject character varying)
returns bigint
language plpgsql
as $$
declare
v_id bigint;
begin
insert into emails
(id, subject)
values (coalesce(v_id, (select nextval('serial'))), v_subject)
on conflict(id)
do update set (subject) = (v_subject) where emails.id = v_id
returning id into v_id;
return v_id;
end;
$$;
When running select upsert_email(6958500, 'subject'); which is a record that exists, it always creates a new record instead.
I have already looked at: Upsert/on conflict with serial primary key which is the most similar question and is what my SQL is modeled on, however I haven't been able to get it to work.
Wow, idiot of the year award goes to me.
I'm taking a parameter called v_id, then immediately overwrite it in the declare with a v_id; They should be named 2 different things:
create or replace function upsert_email(v_id bigint, v_subject character varying)
returns bigint
language plpgsql
as $$
declare
v_return_id bigint;
begin
insert into emails
(id, subject)
values (coalesce(v_id, (select nextval('serial'))), v_subject)
on conflict(id)
do update set (subject) = (v_subject) where emails.id = v_id
returning id into v_return_id;
return v_return_id;
end;
$$;

Postgres "cannot assign non-composite value to a row variable" error assigning a composite value

I have the following problem with two procedures updating a product and its attributes on Postgresql 9.4. First off the tables:
CREATE TABLE product
(
id uuid NOT NULL,
version bigint NOT NULL,
is_part_of_import_id uuid,
name text NOT NULL,
owner_id uuid NOT NULL,
replaced_by_id uuid,
state character varying(255) NOT NULL,
replaces_id uuid,
last_update timestamp without time zone,
...
)
CREATE TABLE attribute_value
(
id uuid NOT NULL,
version bigint NOT NULL,
last_update timestamp without time zone,
product_id uuid NOT NULL,
replaced_by_id uuid,
replaces_id uuid,
value text,
...
)
There is a one-to-many relationship between product and attribute_value. I have a procedure to update the attribute_value
CREATE OR REPLACE FUNCTION update_attribute_value(attr_val attribute_value)
RETURNS UUID AS
$BODY$
DECLARE new_id UUID;
BEGIN
attr_val.replaces_id := attr_val.id;
attr_val.id := uuid_generate_v4();
attr_val.last_update := NOW();
INSERT INTO attribute_value SELECT attr_val.* RETURNING attribute_value.id INTO new_id;
UPDATE attribute_value SET replaced_by_id = new_id WHERE id = attr_val.replaces_id;
RETURN attr_val.id;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
and a procedure to update the product calling the update_attribute_value_procedure to update all attribute_values of the product as well
CREATE OR REPLACE FUNCTION update_product(prod product)
RETURNS UUID AS
$BODY$
DECLARE new_id UUID;
DECLARE attr_val attribute_value%ROWTYPE;
BEGIN
prod.replaces_id := prod.id;
prod.id := uuid_generate_v4();
prod.last_update := NOW();
INSERT INTO product SELECT prod.* RETURNING id INTO new_id;
UPDATE product SET replaced_by_id = new_id WHERE id = prod.replaces_id;
FOR attr_val IN
SELECT * FROM attribute_value WHERE product_id = prod.replaces_id
LOOP
attr_val.product_id = new_id;
PERFORM update_attribute_value(attr_val);
END LOOP;
RETURN new_id;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
When I run update_product like this
select update_product(row('140613c5-3e83-4ae2-986e-5c6b824d5766', 0, '0ba5a6c8-2513-4163-a5bd-c460e10d2059', null, null,
'ce594f2c-87f9-497a-a0e5-7d3fbc4abd8a', null, 'aeabe6fe-b9e1-47e5-96a7-7bf16c56ddf4', 'Rotak 43 1', 'eaf6bea0-99c4-4759-8c38-d35b9ae11403', null,
'PENDING', null, null)::product);
I get the following error output:
ERROR: cannot assign non-composite value to a row variable
SQL state: 42804
Context: PL/pgSQL function change_original_attribute_value() line 15 at assignment
SQL statement "INSERT INTO attribute_value SELECT attr_val.* RETURNING attribute_value.id"
PL/pgSQL function update_attribute_value(attribute_value) line 9 at SQL statement
SQL statement "SELECT update_attribute_value(attr_val)"
PL/pgSQL function update_product(product) line 17 at PERFORM
This error makes absolutely no sense to me in this context. Does anyone has an idea whats wrong in here, or could this be a bug?
I found the error. The problem was inside the function change_original_attribute_value() that is triggered on insert of a product. In there a function was called that returns a UUID, but the type it was assigned to was a rowtype.

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;