I'm trying to dynamically partition log entries in Postgres. I have 53 child tables (1 for each week's worth of log entries), and would like to route INSERTs to a child table using a trigger.
I run the function with INSERT INTO log5 VALUES (NEW.*), and it works.
I run the function with the EXECUTE statement instead, and it fails. Within the EXECUTE statement, it's recognizing NEW as a table name and not a variable passed to the trigger function. Any ideas on how to fix? Thanks!
The error:
QUERY: INSERT INTO log5 VALUES (NEW.*)
CONTEXT: PL/pgSQL function log_roll_test() line 6 at EXECUTE statement
ERROR: missing FROM-clause entry for table "new" SQL state: 42P01
My function:
CREATE FUNCTION log_roll_test() RETURNS trigger AS $body$
DECLARE t text;
BEGIN
t := 'log' || extract(week FROM NEW.updt_ts); --child table name
--INSERT INTO log5 VALUES (NEW.*);
EXECUTE format('INSERT INTO %I VALUES (NEW.*);', t);
RETURN NULL;
END;
$body$ LANGUAGE plpgsql;
My trigger:
CREATE TRIGGER log_roll_test
BEFORE INSERT ON log FOR EACH ROW
EXECUTE PROCEDURE log_roll_test();
CREATE FUNCTION log_roll_test()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
EXECUTE format('INSERT INTO %I SELECT ($1).*' -- !
, to_char(NEW.updt_ts, '"log"WW')) -- child table name
USING NEW; -- !
RETURN NULL;
END
$func$;
You cannot reference NEW inside the query string. NEW is visible in the function body, but not inside EXECUTE environment. The best solution is to pass values in the USING clause.
I also substituted the equivalent to_char(NEW.updt_ts, '"log"WW') for the table name. to_char() is faster and simpler here.
Related
I am trying to create a function to automatically create a row in one table when there is an insert in another row, I already created it before but it was wrong and now I can't create it again
this is my sql code
create or replace function public.handle_new_user()
returns trigger as
$$
begin
insert into public.profiles (user_id, first_name, last_name)
values (new.id, new.raw_user_meta_data->>'first_name', new.raw_user_meta_data->>'last_name');
return new;
end;
$$
language plpgsql security definer;
create or replace trigger on_auth_user_created
after insert on auth.users
for each row
execute procedure public.handle_new_user();
I am getting this error
Failed to validate sql query: syntax error at or near "trigger"
I'm trying to debug this adn find out why I'm getting syntax error:
CREATE OR REPLACE FUNCTION public.myfunc(_report_id integer, _cutoff_date date)
RETURNS record
LANGUAGE plpgsql
AS $function$
declare
_deliverable_id RECORD ;
BEGIN
FOR _deliverable_id IN
SELECT deliverable_id FROM public.deliverables where report_id=_report_id
LOOP
execute format('DROP TABLE IF EXISTS report.products_%I',_deliverable_id);
END LOOP;
END
$function$
;
When I execute this, I get:
syntax error at or near ""(1111)""
1111 is one deliverable for sure, so this leads me to think it has something to do with the execute statement format, or the way I'm using %I?
%I is replaced as a whole identifier. If you want to concatenate things, you need to do it before replacement.
You can test/debug this for yourself by inspecting the result of the format() function:
select format('DROP TABLE IF EXISTS report.products_%I',42);
returns DROP TABLE IF EXISTS report.products_"42"
you need to use:
select format('DROP TABLE IF EXISTS report.%I',concat('products_', 42));
which correctly returns DROP TABLE IF EXISTS report.products_42
(obviously you need to replace 42 with your variable.
I have two table table1 and table2 in two different database. Now I have created a trigger which will insert in table2 whenever a new row is inserted in table1. Below is my trigger
CREATE TRIGGER sync_user_table AFTER INSERT ON table1
for each row execute procedure sync_and_maintain_users_table()
CREATE OR REPLACE function sync_and_maintain_users_table()
returns trigger as
$BODY$
begin
insert into global_db.public.table2
values (user_uuid, user_registration_date, user_service_provider);
end;
$BODY$
language plpgsql;
But the above trigger is not working. Neither I am getting any errors, I am not sure what's wrong.
You may have no error when creating the trigger but you likely have errors at run-time:
CREATE OR REPLACE function sync_and_maintain_users_table()
returns trigger as
$BODY$
begin
insert into global_db.public.table2
values (user_uuid, user_registration_date, user_service_provider);
end;
$BODY$
language plpgsql;
CREATE FUNCTION
CREATE TRIGGER sync_user_table AFTER INSERT ON table1
for each row execute procedure sync_and_maintain_users_table();
CREATE TRIGGER
insert into table1 values(1);
ERROR: cross-database references are not implemented: "global_db.public.table2"
LINE 1: insert into global_db.public.table2
^
QUERY: insert into global_db.public.table2
values (user_uuid, user_registration_date, user_service_provider)
CONTEXT: PL/pgSQL function sync_and_maintain_users_table() line 3 at SQL statement
In PostgreSQL you cannot reference directly another database with database.schema.object syntax.
And if you fix this you have another error:
CREATE OR REPLACE function sync_and_maintain_users_table()
returns trigger as
$BODY$
begin
insert into public.table2
values (user_uuid, user_registration_date, user_service_provider);
end;
$BODY$
language plpgsql;
CREATE FUNCTION
insert into table1 values(1);
ERROR: column "user_uuid" does not exist
LINE 2: values (user_uuid, user_registration_date, user_service_...
^
HINT: There is a column named "user_uuid" in table "table2", but it cannot be referenced from this part of the query.
QUERY: insert into public.table2
values (user_uuid, user_registration_date, user_service_provider)
CONTEXT: PL/pgSQL function sync_and_maintain_users_table() line 3 at SQL statement
You need to add code to initialize variables used in VALUES clause.
See examples in https://www.postgresql.org/docs/12/plpgsql-trigger.html.
I know how to do this using SQL Server or Sybase, but can't seem to get it right using Postgres 9.4. I have a FUNCTION that is working as intended:
CREATE OR REPLACE FUNCTION "public"."welcome"(u_id INT DEFAULT 1234)
RETURNS "void"
AS $$
INSERT INTO my_table_1 (title,body,category,user_id,created_at)
VALUES ('Welcome!', '<some text>','general',u_id,now())
$$ LANGUAGE sql
This FUNCTION works as expected when called as SELECT welcome(1234);
However, what I'm trying to do is call or trigger this FUNCTION based on the condition AFTER a new user gets inserted into another table and that user is the first and only user:
INSERT INTO my_table_2 (user_id,name,...) VALUES (5678,'John Doe',...);
and the following condition is met:
SELECT COUNT(*) from my_table_2 WHERE <my conditions are met>;
returns exactly and only 1
So, symantically, I'm trying to accomplish:
call TRIGGER welcome(1234) AFTER INSERT into my_table_2 where my conditions are met
I've seen some examples, and don't quite understand the CREATE FUNCTION x() AS TRIGGER syntax, and it seems that Postgres is steering me in this direction, but examples are lacking in my case. Help! (and thanks in advance!)
In the SQL standard, you define a trigger that fires a trigger function when a certain action is taking place. In your case you want to create an AFTER INSERT trigger whose trigger function calls your "welcome" function.
First the trigger function:
CREATE FUNCTION call_welcome() RETURNS trigger AS $$
DECLARE
cnt integer;
BEGIN
SELECT count(*) INTO cnt FROM my_table_2 WHERE ...;
IF cnt = 1 THEN
PERFORM welcome(NEW.user_id);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
And the trigger:
CREATE TRIGGER tr_call_welcome
AFTER INSERT ON my_table_2
FOR EACH ROW EXECUTE PROCEDURE call_welcome();
I am trying to figure out how to write an INSERT INTO query with table name and column name of the source as parameter.
For starters I was just trying to parametrize the source table name. I have written the following query. For now I am declaring and assigning the value of the variable tablename directly, but in actual example it would come from some other source/list. The target table has only one column.
CREATE OR REPLACE FUNCTION foo()
RETURNS void AS
$$
DECLARE
tablename text;
BEGIN
tablename := 'Table_1';
EXECUTE 'INSERT INTO "Schemaname"."targettable"
SELECT "Col_A"
FROM "schemaname".'
||quote_ident(tablename);
END
$$ LANGUAGE PLPGSQL;
Although the query runs without any error no changes are reflected at the target table. On running the query I get the following output.
Query OK, 0 rows affected (execution time: 296 ms; total time: 296 ms)
I want the changes to be reflected at the target table. I don't know how to resolve the problem.
Audited code
CREATE OR REPLACE FUNCTION foo()
RETURNS void AS
$func$
DECLARE
_tbl text := 'Table_1'; -- or 'table_1'?
BEGIN
EXECUTE 'INSERT INTO schemaname.targettable(column_name)
SELECT "Col_A"
FROM schemaname.' || quote_ident(_tbl); -- or "Schemaname"?
END
$func$ LANGUAGE plpgsql;
Always use an explicit target list for persisted INSERT statements.
You can assign variables at declare time.
It's a wide-spread folly to use double-quoted identifiers to preserve otherwise illegal spelling. You have to keep double-quoting the name for the rest of its existence. One or more of those errors seem to have crept into your code: "Schemaname" or "schemaname"? Table_1 or "Table_1"?
Are PostgreSQL column names case-sensitive?
When you provide an identifier like a table name as text parameter and escape it with quote_ident(), it is case sensitive!
Identifiers in SQL code are cast to lower case unless double-quoted. But quote-ident() (which you must use to defend against SQL injection) preserves the spelling you provide with double-quotes where necessary.
Function with parameter
CREATE OR REPLACE FUNCTION foo(_tbl text)
RETURNS void AS
$func$
BEGIN
EXECUTE 'INSERT INTO schemaname.targettable(column_name)
SELECT "Col_A"
FROM schemaname.' || quote_ident(_tbl);
END
$func$ LANGUAGE plpgsql;
Call:
SELECT foo('tablename'); -- tablename is case sensitive
There are other ways:
Table name as a PostgreSQL function parameter