Error: Relation "abc.emp_audit" does not exist. when i add schema name in all objects. it was working fine without schema - sql

CREATE TABLE abc.emp
(
empname TEXT NOT NULL,
salary INTEGER
);
CREATE TABLE abc.emp_audit
(
operation CHAR(1) NOT NULL,
stamp TIMESTAMP NOT NULL,
userid TEXT NOT NULL,
empname TEXT NOT NULL,
salary INTEGER
);
CREATE FUNCTION abc.audit()
returns TRIGGER AS $$ ...
EXECUTE format
( 'INSERT INTO %I SELECT ''I'', current_timestamp, %L, ($1::%I.%I).*',
tg_argv[0], CURRENT_USER, tg_table_schema, tg_table_name ) using new;
... $$;
CREATE TRIGGER emp_audit_trig after
INSERT
OR
UPDATE
OR
DELETE
ON abc.emp FOR each row
EXECUTE FUNCTION
abc.audit('abc.emp_audit');
I got error : Relation "abc.emp_audit" does not exist . How to correct this issue when schema added in tables ad audit tables, functions, and triggers arguments.

You'd have to use two parameters, one for the schema and one for the table. In the trigger function, use
EXECUTE
format(
'INSERT INTO %I.%I SELECT ''I'', current_timestamp, %L, ($1::%I.%I).*',
TG_ARGV[0], TG_ARGV[1],
current_user,
TG_TABLE SCHEMA, TG_TABLE NAME
) USING NEW;
Then define the trigger as
CREATE TRIGGER ... EXECUTE FUNCTION abc.audit('abc', 'emp_audit');

An alternative to the one proposed by #Laurenz Albe is to stick to a single parameter and then split it before creating the INSERT statement, e.g
CREATE FUNCTION abc.audit() RETURNS TRIGGER AS $$
DECLARE
tbname TEXT := (string_to_array(tg_argv[0],'.'))[1];
schname TEXT := (string_to_array(tg_argv[0],'.'))[2];
BEGIN
-- rest of your code ...
EXECUTE format('INSERT INTO %I.%I SELECT ''I'', current_timestamp, %L, ($1::%I.%I).*',
tbname,schname,CURRENT_USER,tg_table_schema,tg_table_name ) USING NEW;
-- rest of your code ...
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER emp_audit_trig AFTER INSERT OR UPDATE OR DELETE
ON abc.emp FOR EACH ROW EXECUTE FUNCTION abc.audit('abc.emp_audit');
Demo: db<>fiddle

Related

How to return a dynamic result set from a PLPGSQL function?

How to return a dynamic result set from a PLPGSQL function? What can be used instead of RETURNS SETOF table_name?
Example:
CREATE OR REPLACE FUNCTION dynamic_query
(
table_name varchar
)
RETURNS **SETOF table_name**
LANGUAGE PLPGSQL
AS $$
DECLARE
sql varchar;
BEGIN
/*some code*/
sql = 'SELECT * FROM '|| table_name;
/*some code*/
RETURN QUERY EXECUTE sql;
END $$;
You cannot do that. Your only choices are:
use RETURNS SETOF record.
That works, but you will have to specify the table structure in the query that uses the function, as shown in the documentation.
use RETURNS SETOF jsonb and use to_jsonb in the query to convert the result rows to jsonb
You can return a set of text representation of the record and cast it with your existing type in the select query.
-- Create and fill sample
DROP TABLE IF EXISTS test_table;
CREATE TABLE test_table
(
id BIGSERIAL PRIMARY KEY,
int_val INTEGER,
text_val TEXT,
date_val DATE
);
INSERT INTO test_table(int_val, text_val, date_val)
VALUES (1, 'TEST1, TEST', CURRENT_DATE),
(2, 'TEST2, TEST', CURRENT_DATE),
(3, 'TEST3, TEST', CURRENT_DATE),
(4, 'TEST4, TEST', CURRENT_DATE);
--Create Dynamic query
DROP FUNCTION IF EXISTS dynamic_select(p_table_name TEXT);
CREATE OR REPLACE FUNCTION dynamic_select(IN p_table_name TEXT)
RETURNS SETOF TEXT
AS
$$
DECLARE
v_dynamic_query TEXT;
BEGIN
--Create query
v_dynamic_query = FORMAT('SELECT t::TEXT FROM %I t', p_table_name);
--Execute
RETURN QUERY EXECUTE v_dynamic_query;
END;
$$ LANGUAGE plpgsql;
-- Returns text representation of row values of test table
SELECT *
FROM dynamic_select('test_table');
-- Cast to defined type
SELECT (t::TEST_TABLE).*
FROM dynamic_select('test_table') t;

How To Write a trigger in postgres to update another column when data inserted into another cloum with same value in same table?

Please Find below Table structure:-
CREATE TABLE emp (
empname text NOT NULL,
salary integer,
salary1 integer
);
Whenever i will insert data into emp table using below query salary1 column should be filled automatically using trigger in postgres ?
insert into emp(empname,salary) values('Tarik','1200');
I have tried below code,But it is not working for me.
CREATE OR REPLACE FUNCTION employee_insert_trigger_fnc()
RETURNS trigger AS
$$
BEGIN
update emp set salary1=NEW.salary where NEW.salary=NEW.salary;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
create TRIGGER employee_insert_trigger
AFTER Insert
ON emp
FOR EACH ROW
EXECUTE PROCEDURE employee_insert_trigger_fnc();
Please help..
Thanks in advance.
Issue resolved after my R&D:-
CREATE OR REPLACE FUNCTION employee_insert_trigger_fnc()
RETURNS trigger AS
$$
BEGIN
NEW.salary1 := NEW.salary;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
create TRIGGER employee_insert_trigger
BEFORE INSERT OR UPDATE
ON ami_master.emp
FOR EACH ROW
EXECUTE PROCEDURE employee_insert_trigger_fnc();

Postgres SQL function and trigger to insert record in another table

I need help to complete the following requirements:
[x] Profile table with columns id, username, ...
[x] Comment table with columns id, content, ...
[x] CommentReference table with columns id, profile_id, comment_id, ...
When a new comment is created, before inserting:
[ ] Check if NEW.content have references to usernames, like #someusername
[ ] Check if each reference exists in the profile table
[ ] For references that exist, insert into CommentReferences the profile and comment
For now, what I have is the following code:
PS: the following code has errors, I need help to fix it. I'm using postgres version 12.
CREATE FUNCTION create_comment_usernames_references()
RETURNS trigger AS $$
DECLARE usernames TEXT[];
DECLARE username TEXT;
DECLARE profile_id TEXT; -- profile_id is of type uuid, is it correct to use TEXT here?
BEGIN
-- verify if there are usernames in the comment.content with the username regex
SELECT DISTINCT(
regexp_matches(
NEW.content,
'#(([a-z0-9]*((?<=[a-z0-9])[-|_|\.](?=[a-z0-9]))[a-z0-9]*)*|[a-z0-9]*)',
'g'
)
)[1]
INTO usernames;
FOREACH username IN ARRAY usernames LOOP
SELECT (SELECT id FROM "public"."Profile" WHERE "username" = username) INTO profile_id
INSERT INTO "public"."CommentReference" (comment_id, profile_id) VALUES (NEW.id, profile_id);
END LOOP;
-- return nothing
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER create_comment_usernames_references_trigger
BEFORE INSERT OR UPDATE ON "public"."Comment"
FOR EACH ROW
EXECUTE PROCEDURE create_comment_usernames_references();
Solve it myself.
Final code:
CREATE OR REPLACE FUNCTION find_profile_ids_by_usernames_in_text(_value TEXT)
RETURNS TABLE (profile_id uuid) AS
$func$
BEGIN
RETURN QUERY
SELECT
id
FROM
"public"."Profile"
WHERE
username IN (
SELECT DISTINCT(
regexp_matches(
_value,
'#(([a-z0-9]*((?<=[a-z0-9])[-|_|\.](?=[a-z0-9]))[a-z0-9]*)*|[a-z0-9]*)',
'g')
)[1]
);
END
$func$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION create_comment_reference(_comment_id UUID, _content TEXT)
RETURNS integer AS $$
DECLARE
row RECORD;
BEGIN
FOR row IN
SELECT * FROM find_profile_ids_by_usernames_in_text(_content)
LOOP
INSERT INTO
"public"."CommentReference" (comment_id, profile_id)
VALUES
(_comment_id, row.profile_id)
ON CONFLICT DO NOTHING;
END LOOP;
RETURN 1;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION create_comment_reference_trigger_func()
RETURNS trigger AS $$
DECLARE someval integer;
BEGIN
select * from create_comment_reference(NEW.id, NEW.content) into someval;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER create_comment_usernames_references_trigger
AFTER INSERT OR UPDATE ON "public"."Comment"
FOR EACH ROW
EXECUTE PROCEDURE create_comment_reference_trigger_func();

Create a trigger function in Postgres that doesn't let you have two entries with the same id

I have two tables in postgres,
I want to create a function that doesn’t have more than 2 loans in the lending table with the same person id.
example: in the loan table I cannot have 3 loans that are from the same person, that is, we loan with the same person's id.
I need to do this using a function, I put what I was trying to do but it didn't work
CREATE TABLE person (
name_person varchar (100) ,
id_person varchar(14) primary key
)
CREATE TABLE lending(
id_lending primary key (100) ,
id_publication (14) FK,
id_person fk REFERENCES id_person (person)
CREATE OR REPLACE FUNCTION check_numlending()
RETURNS trigger AS
$BODY$
BEGIN
IF( select * from lending
inner join person
on person.id_person = lending.id_person > 2 ) THEN
RAISE EXCEPTION 'ERROR';
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
-- Trigger
CREATE TRIGGER
trg_check_num_lending
BEFORE INSERT OR UPDATE ON
lendingFOR EACH ROW EXECUTE PROCEDURE check_numlending();
Write your trigger Function like below:
-- Function
CREATE OR REPLACE FUNCTION check_numlending()
RETURNS trigger AS
$BODY$
declare counter int;
BEGIN
select count(*) into counter from lending where id_person =new.id_person;
IF( counter>=2 ) THEN
RAISE EXCEPTION 'ERROR';
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
-- Trigger
CREATE TRIGGER
trg_check_num_lending
BEFORE INSERT OR UPDATE ON
lending FOR EACH ROW EXECUTE PROCEDURE check_numlending();

How can a Postgres trigger `AFTER UPDATE FOR EACH STATEMENT` correlate its `OLD TABLE` and `NEW TABLE`?

After upgrading from Postgres 9.5 to 11, I am trying to replace the following FOR EACH ROW trigger with a FOR EACH STATEMENT trigger which I hope would be more efficient for my specific use-case:
CREATE OR REPLACE FUNCTION audit_update_operations()
RETURNS TRIGGER
AS
$$
DECLARE
audit_user UUID;
data_before TEXT;
data_after TEXT;
BEGIN
audit_user := coalesce(current_setting('audit.AUDIT_USER', TRUE), '77777777-0000-7777-0000-777777777777')::UUID;
data_before := ROW (old.*);
data_after := ROW (new.*);
INSERT INTO dml_audit_log
(changed_at,
user_id,
operation,
table_name,
data_after,
data_before)
VALUES (now(),
audit_user,
'U',
tg_table_name::TEXT,
data_after,
data_before);
RETURN new;
END ;
$$
LANGUAGE plpgsql;
I thought this was pretty easy:
CREATE OR REPLACE FUNCTION audit_update_operations()
RETURNS TRIGGER
AS
$$
DECLARE
user_id UUID;
BEGIN
user_id := coalesce(current_setting('audit.AUDIT_USER', TRUE), '77777777-0000-7777-0000-777777777777')::UUID;
INSERT INTO dml_audit_log
SELECT now() AS changed_at,
user_id,
'U' AS operation,
tg_table_name::TEXT AS table_name,
ROW (new_table.*) AS data_after,
ROW (old_table.*) AS data_before
FROM new_table;
RETURN NULL;
END ;
$$
LANGUAGE plpgsql;
which is called generically for every table via the following trigger:
EXECUTE ('
CREATE TRIGGER trigger_audit_update
AFTER UPDATE ON ' || tablename || '
REFERENCING
OLD TABLE AS old_table
NEW TABLE AS new_table
FOR EACH STATEMENT
EXECUTE PROCEDURE audit_update_operations()');
but an UPDATE statement triggering this function results in the following error:
[42P01] ERROR: missing FROM-clause entry for table "old_table" Where: PL/pgSQL function shared.audit_update_operations() line 7 at SQL statement
The question I am struggling with is:
How can I correlate the old and new rows without making any assumption about the table being changed?
The tables I am triggering on may or may not have a primary key. Even if they do, I would not know its name or column(s) in the trigger function.
Are the rows in OLD TABLE and NEW TABLE guaranteed to be in the same order? I cannot rely on undocumented implementation details that may change.