Check if trigger exists - postgresql

I have the following query to triggers on all tables in schema public:
SELECT 'CREATE TRIGGER ' || tab_name|| '_if_modified_trg INSERT OR UPDATE OR DELETE ON ' || tab_name|| ' FOR EACH ROW EXECUTE PROCEDURE audit.if_modified_func(); ' AS trigger_creation_query
FROM (
SELECT quote_ident(table_schema) || '.' || quote_ident(table_name) as tab_name
FROM information_schema.tables
WHERE table_schema='public'
) AS foo;
And I know how to check if a trigger exists:
SELECT tgname
from pg_trigger
where not tgisinternal AND tgname='randomname'
But how can I check in the first query whether a trigger with the same name already exists - and skip creating it and just continue? Here is my solution but it doesn't work:
SELECT 'CREATE TRIGGER ' || tab_name|| '_if_modified_trg INSERT OR UPDATE OR DELETE ON ' || tab_name|| ' FOR EACH ROW EXECUTE PROCEDURE audit.if_modified_func(); ' AS trigger_creation_query
FROM (
SELECT quote_ident(table_schema) || '.' || quote_ident(table_name) as tab_name
FROM information_schema.tables
WHERE table_schema='public'
) AS foo
WHERE tab_name||'if_modified_trg' NOT IN (
SELECT tgname
from pg_trigger
where not tgisinternal );

Using this you can check if the trigger exists and create it if not. Don't forget the last ";".
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'randomname') THEN
CREATE TRIGGER randomname
AFTER INSERT OR UPDATE OR DELETE ON randomtable
FOR EACH ROW EXECUTE PROCEDURE randomfunction();
END IF;
END
$$;
I hope this can help you.

You can use a DO statement or a plpgsql function to execute the trigger creation conditionally:
DO
$do$
BEGIN
IF EXISTS (
SELECT 1
FROM pg_trigger
WHERE NOT tgisinternal AND tgname = 'randomname'
) THEN
-- do nothing
ELSE
-- create trigger
END IF;
END
$do$
On closer inspection, the rest of your code also has various problems.
Seems like you are trying to do this:
DO
$do$
DECLARE
_tbl regclass;
_trg text;
BEGIN
FOR _tbl, _trg IN
SELECT c.oid::regclass, relname || '_if_modified_trg'
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'public'
AND c.relkind = 'r' -- only regular tables
LOOP
IF EXISTS (
SELECT
FROM pg_trigger
WHERE tgname = _trg
AND tgrelid = _tbl -- check only for respective table
) THEN
-- do nothing
ELSE
-- create trigger
EXECUTE format(
'CREATE TRIGGER %I
BEFORE INSERT OR UPDATE OR DELETE ON %s
FOR EACH ROW EXECUTE PROCEDURE audit.if_modified_func()'
, _trg, _tbl::text
);
END IF;
END LOOP;
END
$do$;
I use the system catalog pg_class instead of information_schema.tables for multiple reasons. Most importantly, it includes the oid of the table, which makes the check on pg_trigger simpler and less error-prone.
We can actually simplify further and check for existence of the trigger in the same query. Substantially faster, yet:
DO
$do$
DECLARE
_tbl text;
_trg text;
BEGIN
FOR _tbl, _trg IN
SELECT c.oid::regclass::text, relname || '_if_modified_trg'
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
LEFT JOIN pg_trigger t ON t.tgname = c.relname || '_if_modified_trg'
AND t.tgrelid = c.oid -- check only respective table
WHERE n.nspname = 'public'
AND c.relkind = 'r' -- only regular tables
AND t.tgrelid IS NULL -- trigger does not exist yet
LOOP
EXECUTE format(
'CREATE TRIGGER %I
BEFORE INSERT OR UPDATE OR DELETE ON %s
FOR EACH ROW EXECUTE PROCEDURE audit.if_modified_func()'
, _trg_name, _tbl_oid::text
);
END LOOP;
END
$do$;
How to check if a table exists in a given schema
Information schema vs. system catalogs
Select rows which are not present in other table
Related answers with more explanation:
EXECUTE of SELECT ... INTO is not implemented
Loop on tables with PL/pgSQL in Postgres 9.0+
Postgres: check disk space taken by materialized view?

Related

Delete selected tables from Postgres sql

I need to remove selected tables from the Postgres SQL. It would better to use like or where clause.
Like I have
TABLE_A
TABLE_B
TABLE_C
-
-
-
TABLE_N
I need to delete
TABLE_A to TABLE_X
Can be done with a single command, which is faster - in case this is a recurring task.
Add IF EXISTS if the existence of any tables is uncertain. This way we save an extra trip to the system catalogs (information_schema.tables or pg_catalog.pg_tables).
And you may want to add CASCADE:
DO
$do$
BEGIN
-- child safety device: quote RAISE instead of EXECUTE to prime the bomb
-- EXECUTE (
RAISE NOTICE '%', (
SELECT 'DROP TABLE IF EXISTS'
|| string_agg('table_' || chr(ascii('a') + g) , ', ')
|| ' CASCADE;'
FROM generate_series(0,13) g
);
END
$do$;
Generates a command of the form:
DROP TABLE IF EXISTS table_a, table_b, ... , table_n CASCADE;
Using generate_series() to generate requested table names. More here:
How to drop many (but not all) tables in one fell swoop?
How to check if a table exists in a given schema
DO
$$
DECLARE
r RECORD;
BEGIN
FOR r IN SELECT oid::REGCLASS table_name
FROM pg_class
WHERE relname <= 'table_x'
AND relkind = 'r'
LOOP
EXECUTE 'DROP TABLE' || r.table_name;
END LOOP;
END;
$$ LANGUAGE plpgsql;

Could you explain part of this function that begins with " if not exist...."?

CREATE OR REPLACE FUNCTION event_partition()
RETURNS trigger AS
$BODY$
DECLARE
_tbl text := to_char(NEW.the_date, 'YYYY-MM-DD');
BEGIN
***IF NOT EXISTS (
SELECT 1
FROM pg_catalog.pg_class c
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'public' -- your schema
AND c.relname = _tbl
AND c.relkind = 'r') THEN***
EXECUTE format('CREATE TABLE %I (CHECK (the_date >= %L AND
the_date < %L)) INHERITS (events)'
, _tbl
, to_char(NEW.the_date, 'YYYY-MM-DD')
, to_char(NEW.the_date + 1, 'YYYY-MM-DD')
);
END IF;
EXECUTE 'INSERT INTO ' || quote_ident(_tbl) || ' VALUES ($1.*)'
USING NEW;
RETURN NULL;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION event_partition() SET search_path=public;
It's querying the database's internal table list (pg_class) to see if the table _tbl already exists in the public schema, and creating it if it doesn't.
You should be able to remove the check, and just use a CREATE TABLE IF NOT EXISTS... statement instead.

Loop on tables with PL/pgSQL in Postgres 9.0+

I want to loop through all my tables to count rows in each of them. The following query gets me an error:
DO $$
DECLARE
tables CURSOR FOR
SELECT tablename FROM pg_tables
WHERE tablename NOT LIKE 'pg_%'
ORDER BY tablename;
tablename varchar(100);
nbRow int;
BEGIN
FOR tablename IN tables LOOP
EXECUTE 'SELECT count(*) FROM ' || tablename INTO nbRow;
-- Do something with nbRow
END LOOP;
END$$;
Errors:
ERROR: syntax error at or near ")"
LINE 1: SELECT count(*) FROM (sql_features)
^
QUERY: SELECT count(*) FROM (sql_features)
CONTEXT: PL/pgSQL function inline_code_block line 8 at EXECUTE statement
sql_features is a table's name in my DB. I already tried to use quote_ident() but to no avail.
I can't remember the last time I actually needed to use an explicit cursor for looping in PL/pgSQL.
Use the implicit cursor of a FOR loop, that's much cleaner:
DO
$$
DECLARE
rec record;
nbrow bigint;
BEGIN
FOR rec IN
SELECT *
FROM pg_tables
WHERE tablename NOT LIKE 'pg\_%'
ORDER BY tablename
LOOP
EXECUTE 'SELECT count(*) FROM '
|| quote_ident(rec.schemaname) || '.'
|| quote_ident(rec.tablename)
INTO nbrow;
-- Do something with nbrow
END LOOP;
END
$$;
You need to include the schema name to make this work for all schemas (including those not in your search_path).
Also, you actually need to use quote_ident() or format() with %I or a regclass variable to safeguard against SQL injection. A table name can be almost anything inside double quotes. See:
Table name as a PostgreSQL function parameter
Minor detail: escape the underscore (_) in the LIKE pattern to make it a literal underscore: tablename NOT LIKE 'pg\_%'
How I might do it:
DO
$$
DECLARE
tbl regclass;
nbrow bigint;
BEGIN
FOR tbl IN
SELECT c.oid
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'r'
AND n.nspname NOT LIKE 'pg\_%' -- system schema(s)
AND n.nspname <> 'information_schema' -- information schema
ORDER BY n.nspname, c.relname
LOOP
EXECUTE 'SELECT count(*) FROM ' || tbl INTO nbrow;
-- raise notice '%: % rows', tbl, nbrow;
END LOOP;
END
$$;
Query pg_catalog.pg_class instead of tablename, it provides the OID of the table.
The object identifier type regclass is handy to simplify. n particular, table names are double-quoted and schema-qualified where necessary automatically (also prevents SQL injection).
This query also excludes temporary tables (temp schema is named pg_temp% internally).
To only include tables from a given schema:
AND n.nspname = 'public' -- schema name here, case-sensitive
The cursor returns a record, not a scalar value, so "tablename" is not a string variable.
The concatenation turns the record into a string that looks like this (sql_features). If you had selected e.g. the schemaname with the tablename, the text representation of the record would have been (public,sql_features).
So you need to access the column inside the record to create your SQL statement:
DO $$
DECLARE
tables CURSOR FOR
SELECT tablename
FROM pg_tables
WHERE tablename NOT LIKE 'pg_%'
ORDER BY tablename;
nbRow int;
BEGIN
FOR table_record IN tables LOOP
EXECUTE 'SELECT count(*) FROM ' || table_record.tablename INTO nbRow;
-- Do something with nbRow
END LOOP;
END$$;
You might want to use WHERE schemaname = 'public' instead of not like 'pg_%' to exclude the Postgres system tables.

DROP All Views PostgreSQL

How I can Delete All User Defined Views From PostgreSQL using a Query?
Like we can delete All functions using query :
SELECT 'DROP FUNCTION ' || ns.nspname || '.' || proname
|| '(' || oidvectortypes(proargtypes) || ');'
FROM pg_proc INNER JOIN pg_namespace ns ON (pg_proc.pronamespace = ns.oid)
WHERE ns.nspname = 'my_messed_up_schema' order by proname;
Script for deleting all views in a certain schema:
SELECT 'DROP VIEW ' || t.oid::regclass || ';' -- CASCADE?
FROM pg_class t
JOIN pg_namespace n ON n.oid = t.relnamespace
WHERE t.relkind = 'v'
AND n.nspname = 'my_messed_up_schema -- select by schema(s)
ORDER BY 1;
The cast to regclass (t.oid::regclass) prevents SQLi, because otherwise illegal names are quoted automatically. You could also use quote_ident().
Your example is inherently unsafe.
Do it right away:
DO
$$
DECLARE
sql text;
BEGIN
SELECT INTO sql
string_agg('DROP VIEW ' || t.oid::regclass || ';', ' ') -- CASCADE?
FROM pg_class t
JOIN pg_namespace n ON n.oid = t.relnamespace
WHERE t.relkind = 'v'
AND n.nspname = 'my_messed_up_schema';
IF sql IS NOT NULL THEN
-- RAISE NOTICE '%', sql; -- to debug
EXECUTE sql;
ELSE
RAISE NOTICE 'No views found. Nothing dropped.';
END IF;
END
$$
DO requires PostgreSQL 9.0 or later.
The IF construct avoids an exception if no views are found.
If you have views referencing other views, you'll have to add the keyword CASCADE or drop views in their hierarchical order from top to bottom.
Always check what you are going to drop before you do it, or you might nuke yourself. If you are unsure, start a transaction, drop the bomb, check if all is good and then either commit or roll back.
BEGIN;
DO$$
...
$$;
-- check ..
ROLLBACK; -- if something wrong
COMMIT; -- else
Note that you cannot COMMIT or ROLLBACK inside the plpgsql block. Only outside.
Use table pg_class.
You need relkind = 'v'

How to change schema of multiple PostgreSQL tables in one operation?

I have a PostgreSQL 9.1 database with 100 or so tables that were loaded into the 'public' schema. I would like to move those tables (but not all of the functions in 'public') to a 'data' schema.
I know that I can use the following to move 1 table at a time.
ALTER TABLE [tablename] SET SCHEMA [new_schema]
Is it possible to move all of the tables to the new schema in one operation? If so, what would be the most efficient way to accomplish this task?
DO will do the trick:
DO
$$
DECLARE
row record;
BEGIN
FOR row IN SELECT tablename FROM pg_tables WHERE schemaname = 'public' -- and other conditions, if needed
LOOP
EXECUTE format('ALTER TABLE public.%I SET SCHEMA [new_schema];', row.tablename);
END LOOP;
END;
$$;
-- ####### USING DBEAVER WHICH SUPPORT VARIABLES ########
-- ### ANSWER_1 -- USING DO ###--------
-- Step1: Set variables one by one
#set _SCHEMA = 'public'
#set _COLUMN = 'dml_status'
#set _DATA_TYPE = 'integer'
#set _DEFAULT = '1'
-- Step2: Call the below procedure
DO
$$
DECLARE
row record;
query varchar;
BEGIN
FOR ROW IN SELECT table_name FROM information_schema.tables WHERE table_schema = ${_SCHEMA}
LOOP
query :='ALTER TABLE public.' || quote_ident(row.table_name) ||' ADD COLUMN IF NOT EXISTS '||${_COLUMN} || ' ' || ${_DATA_TYPE} ||' not null default ' || ${_DEFAULT} || ';' ;
execute query;
END LOOP;
END;
$$;
-- ### ANSWER_2 -- STORE PROCEDURE FN ###--------
DROP FUNCTION addColumnToMultipleTables cascade;
create or replace function addColumnToMultipleTables()
returns void
LANGUAGE 'plpgsql'
as $$
DECLARE
row record;
query varchar;
BEGIN
FOR ROW IN SELECT table_name FROM information_schema.tables WHERE table_schema = ${_SCHEMA}
LOOP
query :='ALTER TABLE public.' || quote_ident(row.table_name) ||' ADD COLUMN IF NOT EXISTS '||${_COLUMN} || ' ' || ${_DATA_TYPE} ||' not null default ' || ${_DEFAULT} || ';' ;
raise info 'query : % ', query;
execute query;
END LOOP;
END;
$$;
select addColumnToMultipleTables();