I need to drop all the schemas in my database except public, information_schema and those LIKE 'pg_%'.
Here's what I've found: (this variant doesn't seem to work)
CREATE OR REPLACE FUNCTION drop_all ()
RETURNS VOID AS
$$
DECLARE rec RECORD;
BEGIN
-- Get all the schemas
FOR rec IN
SELECT DISTINCT schemaname
FROM pg_catalog.pg_tables
-- You can exclude the schema which you don't want to drop by adding another condition here
WHERE schemaname NOT LIKE 'pg_%' AND schemaname != 'public'
AND schemaname != 'information_schema'
LOOP
EXECUTE 'DROP SCHEMA ' || rec.schemaname || ' CASCADE';
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;
SELECT drop_all();
Another variant: (this one probably works but still crashes my app)
SELECT string_agg(format('DROP SCHEMA %I CASCADE;', nspname), E'\n')
FROM pg_namespace WHERE nspname != 'public'
AND nspname NOT LIKE 'pg_%'
AND nspname != 'information_schema';
So, I wanted to make sure that the queries are correct or, hopefully, find another variant of the query.
Could anybody help me with finding another query for doing that?
It drops only schema with any table, because you are using the query:
SELECT DISTINCT schemaname
FROM pg_catalog.pg_tables
so schemas without any table are not dropped.
You have to use a different query
SELECT n.nspname AS "Name",
pg_catalog.pg_get_userbyid(n.nspowner) AS "Owner"
FROM pg_catalog.pg_namespace n
WHERE n.nspname !~ '^pg_' AND n.nspname <> 'information_schema'
Related
I have tables' names that start with underscore '_XXXXXXX' among other tables. I need to create a user that can only do a query on these '_XXXX" tables (nothing else) without the possibility of viewing/finding the other tables (not starting with '_XXXXX').
How can i do that in postgres psql?:
I tried
GRANT SELECT ON TABLE "_*" TO username;
i get the following:
ERROR: relation "_*" does not exist
Any help is appreciated.
Thank you
when I execute this code in PgAdmin4 query editor:
DO
$$
DECLARE
r record;
BEGIN
FOR r IN SELECT c.relname,
n.nspname
FROM pg_class c
INNER JOIN pg_namespace n
ON n.oid = c.relnamespace
WHERE n.nspname = 'Schemas'
AND c.relkind = 'r'
AND c.relname LIKE '$_%'
ESCAPE '$' LOOP
EXECUTE 'GRANT SELECT ON "' || r.nspname || '"."' || r.relname || '" TO kpidata;';
END LOOP;
END;
$$
LANGUAGE plpgsql;
i get the following response and nothing changes (still have the same access rights to all for the created user 'kpidata'). I am sure it is me who is not understanding how things work
my db structure is the following:
You could use a DO block looping over all table with names beginning with underscore, build the statement for it an execute the statement.
DO
$$
DECLARE
r record;
BEGIN
FOR r IN SELECT c.relname,
n.nspname
FROM pg_class c
INNER JOIN pg_namespace n
ON n.oid = c.relnamespace
WHERE n.nspname = 'public'
AND c.relkind = 'r'
AND c.relname LIKE '$_%'
ESCAPE '$' LOOP
EXECUTE 'GRANT SELECT ON "' || r.nspname || '"."' || r.relname || '" TO username;';
END LOOP;
END;
$$
LANGUAGE plpgsql;
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?
I have few tables in my database. They all have the same columns (id, name) but differ in the table name. Those tables have names that start with letter 'h'.
Not a very interesting schema design but I have to follow it.
I need to search for id in all those tables.
I tried something similar to:
select id from (select table_name
FROM information_schema.tables
where table_name like 'h%') as t;
I got error:
ERROR: column "id" does not exist.
I understand the error now but I still do not know how to do the query?
You need dynamic SQL to do that since you cannot use values as identifiers in plain SQL. Write a PL/pgSQL function with EXECUTE:
CREATE FUNCTION f_all_tables()
RETURNS TABLE (id int) AS
$func$
DECLARE
_tbl regclass;
BEGIN
FOR _tbl IN
SELECT c.oid::regclass
FROM pg_catalog.pg_class c
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'r'
AND c.relname LIKE 'h%'
AND n.nspname = 'public' -- your schema name here
LOOP
RETURN QUERY EXECUTE '
SELECT id FROM ' || _tbl;
END LOOP;
END
$func$ LANGUAGE plpgsql;
I am using a variable of the object identifier type regclass to prevent SQL injection effectively. More about that in this related answer:
Table name as a PostgreSQL function parameter
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.
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'