How to remove Unique Indexes without knowing its name - postgresql

I am using PostgreSQL and I need to remove an index without knowing its name.
I have multiple instances where the index was created using Liquibase script. I want to drop the indexes created and add a new index.
I have a script that would give me the Drop statements.
select format('drop index %I.%I;', schemaname, indexname) as qry
from pg_indexes
where schemaname not in ('pg_catalog', 'pg_toast')
and tablename='table_name'
and indexname!='table_name_pkey'
But I am not sure how to use the result set to drop the inedexes.
Executing using SQL Shell/ Liquibase where I can add SQL Statements. Cant make long functions to do that.

Try this with DO block like below:
do $$
declare
x text;
begin
x=(select string_agg(format('drop index %I.%I;', schemaname, indexname), ' ') as qry
from pg_indexes
where schemaname not in ('pg_catalog', 'pg_toast')
and tablename='test'
and indexname!='test_pkey');
execute x;
end $$
DEMO1
Same you can do with function/Procedure(Depend upon your Version of PostgreSQL) like below:
create or replace function remove_index(table_name varchar, table_pkey varchar) returns void
as $$
declare
x text;
begin
x=(select string_agg(format('drop index %I.%I;', schemaname, indexname), ' ') as qry
from pg_indexes
where schemaname not in ('pg_catalog', 'pg_toast')
and tablename=table_name
and indexname!=table_pkey);
execute x;
end;
$$
language plpgsql
and call above function like below:
select * from remove_index('test','test_pkey')
DEMO2

Related

Dynamic query splicing error,a fuction use falie

I want to use the cursor to get the count(data) in all tables under the schema I need.
But I am unfamiliar with SQL, and I still cannot pass the following code:
CREATE OR REPLACE FUNCTION check_tool.get_nae(v_dbName character varying)
RETURNS numeric
LANGUAGE edbspl
SECURITY DEFINER
AS $function$
numInsert numeric;
numCal numeric;
v_result numeric;
query TEXT DEFAULT '';
cursor c_pj is
select t.table_schema::text as tableSchema,
t.table_name::text as tableName
from information_schema.tables t
where t.table_catalog = v_dbName
and t.table_type = 'BASE TABLE'
and t.table_schema in (select schema_name from check_tool.img_schema where dbName = v_dbName);
BEGIN
v_result := -1;
numInsert := 0;
for r_pj in c_pj loop
query := 'select count(*) from '||tableSchema||'.'||tableName||';'; -- select count(*) from "item"."project";
execute query into numCal;
insert into check_tool.img_result(schema_name,table_name,num) values (r_pj.tableSchema,r_pj.tableName,numCal);
numInsert := numInsert + 1;
if numInsert > 1000 then
numInsert := 0;
commit;
end if;
end loop;
commit;
v_result := 0;
RETURN v_result;
EXCEPTION
WHEN others THEN
RETURN v_result;
END get_nae$function$
;
/
I also tried concat() and quote_ident(), but the result is not ideal.
Dynamic SQL should be constructed using format() to better handle identifiers. In Postgres you can't commit inside a function, only in a procedure. Committing in a loop rarely improves the performance to begin with, so I would just skip that. I also wouldn't hide the real error (by just returning -1 or 0) but simply let any exception reach the caller of the procedure. And language edbspl is nothing I know, but in PL/pgSQL I would write it like this:
CREATE OR REPLACE PROCEDURE check_tool.get_nae(v_dbName character varying)
LANGUAGE plpgsql
AS
$body$
declare --<< required in PL/pgSQL to declare variables
numcal numeric;
query TEXT DEFAULT '';
l_rec record;
BEGIN
for l_rec in select t.table_schema::text as tableschema, t.table_name::text as tablename
from information_schema.tables t
where t.table_catalog = v_dbName
and t.table_type = 'BASE TABLE'
and t.table_schema in (select schema_name from check_tool.img_schema where dbName = v_dbName)
loop
query := format('select count(*) from %I.%I', l_rec.tableschema, l_rec.tablename);
execute query into numcal;
insert into check_tool.img_result(schema_name,table_name,num) values (l_rec.tableschema, l_rec.tablename, numcal);
end loop;
commit;
END;
$body$
;
Note that the condition t.table_catalog = v_dbName is actually useless, because you can't query tables that are not in the current database anyway.
Note that you don't really need a stored procedure to do this. You can use query_to_xml() to do this in a single SQL query by adjusting this answer
insert into check_tool.img_result(schema_name,table_name,num)
select table_schema,
table_name,
(xpath('/row/cnt/text()', xml_count))[1]::text::int as row_count
from (
select table_name, table_schema,
query_to_xml(format('select count(*) as cnt from %I.%I', table_schema, table_name), false, true, '') as xml_count
from information_schema.tables
where t.table_type = 'BASE TABLE'
and t.table_schema in (select schema_name from check_tool.img_schema)
) t;

Simple PostgreSQL plpgsql to create a new table using existing table

I'm new to plpgsql. I'm sure there is some really simple way to do this, but for some reason I'm having a lot of trouble trying to figure out how to do this.
I'm simply trying to loop through the list of existing tables and execute
CREATE TABLE z_existing_table_name AS SELECT * FROM existing_table_name WITH DATA
So far, I have this:
CREATE OR REPLACE FUNCTION create_backup_row()
RETURNS RECORD
AS $$
DECLARE
row RECORD;
BEGIN
FOR row IN SELECT * FROM information_schema.tables WHERE table_catalog = 'my_db' and table_schema = 'public'
LOOP
EXECUTE 'CREATE TABLE z_' || t.table_name || ' as ' || t.table_name
END LOOP;
END;
$$ LANGUAGE plpgsql;
It would be an added bonus if I can make this function re-runnable. Something like drop table if exist then create table ...
#Steven, use below procedure,
-- Function: create_backup_row()
-- DROP FUNCTION create_backup_row();
CREATE OR REPLACE FUNCTION create_backup_row()
RETURNS integer AS
$BODY$
DECLARE
v_table text;
BEGIN
FOR v_table IN
SELECT table_name
FROM information_schema.tables
WHERE table_catalog = 'my_db'
AND table_schema = 'public'
AND table_name not ilike '%z_%' -- to skip the table with z_ when we rerun it.
LOOP
EXECUTE ' DROP TABLE IF EXISTS z_' || v_table ;
EXECUTE 'CREATE TABLE z_' || v_table || ' as SELECT * FROM ' || v_table ;
END LOOP;
return 1;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION create_backup_row()
OWNER TO postgres;

Loop through all user tables and insert row in each

for some reason I just can not figure this out. I have a seperate schema in PostgreSQL for notification related tables for each user connected to the server. My plan is to have each user create a TEMP table to receive extra notification info from since Xojo doesn't support PostgreSQL payloads.
I feel like I'm starting to get close so I'll just post my code that is in my trigger function.
DECLARE
my_table RECORD;
BEGIN
FOR my_table IN
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'information_schema'
LOOP
INSERT INTO my_table.table_name (effected_row_id)
VALUES (NEW.effected_row_id);
END LOOP;
END;
Tell me if I'm wrong, but I believe my main problem is figuring out how to use the table name returned from the SELECT statement in the INSERT statement.
EDIT:
This is my current trigger function
-- Function: notification.my_insert_trigger_function()
-- DROP FUNCTION notification.my_insert_trigger_function();
CREATE OR REPLACE FUNCTION notification.my_insert_trigger_function()
RETURNS trigger AS
$BODY$DECLARE
my_table RECORD;
BEGIN
FOR my_table IN
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'notification' AND table_name <> 'notification_global' AND table_name <> 'switcher'
LOOP
EXECUTE(FORMAT($f$
INSERT INTO %s (effected_row_username)
VALUES (%s);
$f$, 'notification.' || my_table.table_name, NEW.effected_row_username));
END LOOP;
RETURN new;
END;$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION notification.my_insert_trigger_function()
OWNER TO serveradmin;
You need to use dynamic commands in your trigger function.
The funcion format() is often very helpful.
DECLARE
my_table RECORD;
BEGIN
FOR my_table IN
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'information_schema'
LOOP
EXECUTE(FORMAT($f$
INSERT INTO %s (effected_row_id)
VALUES (%s);
$f$, my_table.tablename, NEW.effected_row_id));
END LOOP;
END;

SELECTing commands into a temp table to EXECUTE later in PostgreSQL

For some fancy database maintenance for my developer database I'd like to be able to use queries to generate commands to alter the database. The thing is: I'm a complete greenhorn to PostgreSQL. I've made my attempt but have failed colorfully.
So in the end, I would like to have a table with a single column and each row would be a command (or group of commands, depending on the case) that I would think would look something like this...
DO $$
DECLARE
command_entry RECORD;
BEGIN
FOR command_entry IN SELECT * FROM list_of_commands
LOOP
EXECUTE command_entry;
END LOOP;
END;
$$;
Where the table list_of_commands could be populated with something like the following (which in this example would remove all tables from the public schema)...
CREATE TEMP TABLE list_of_commands AS
SELECT 'drop table if exists "' || tablename || '" cascade;'
FROM pg_tables
WHERE schemaname = 'public';
However, with this I get the following error...
ERROR: syntax error at or near ""drop table if exists ""dummy_table"" cascade;""
LINE 1: ("drop table if exists ""dummy_table"" cascade;")
I assume this is a matter of escaping characters, but I'm not entirely sure how to fit that into either A) the population of the table or B) the execution of each row. Does anyone know what I could do to achieve the desired result?
The command_entry variable is of type record while the EXECUTE command expects a string. What is apparently happening is that PostgreSQL turns the record into a double-quoted string, but that messes up your command. Also, your temp table does not use a column name, making things a bit awkward to work with (the column name becomes ?column?), so change both as follows:
CREATE TEMP TABLE list_of_commands AS
SELECT 'drop table if exists public.' || quote_ident(tablename) || ' cascade' AS cmd
FROM pg_tables
WHERE schemaname = 'public';
DO $$
DECLARE
command_entry varchar;
BEGIN
FOR command_entry IN SELECT cmd FROM list_of_commands
LOOP
EXECUTE command_entry;
END LOOP;
END;
$$;
But seeing that you do all of this at session level (temp table, anonymous code block), why not write a stored procedure that performs all of this housekeeping when you are ready to do spring cleaning?
CREATE FUNCTION cleanup() RETURNS void AS $$
BEGIN
FOR tbl IN SELECT tablename FROM pg_tables WHERE schemaname = 'public'
LOOP
EXECUTE 'DROP TABLE IF EXISTS ' || quote_ident(tbl) || ' CASCADE';
END LOOP;
-- More housekeeping jobs
END;
$$ LANGUAGE plpgsql;
This saves a lot of typing: SELECT cleanup();. Any other housekeeping jobs you have you simply add to the stored procedure.
I had trouble with Patrick's answers, so here is an updated version for postgreSQL 10.
CREATE FUNCTION droptables(sn varchar) RETURNS void AS $$
DECLARE
tbl varchar;
BEGIN
FOR tbl IN SELECT tablename FROM pg_tables WHERE schemaname = sn
LOOP
EXECUTE 'DROP TABLE IF EXISTS ' || quote_ident(tbl) || ' CASCADE';
END LOOP;
END;
$$ LANGUAGE plpgsql;
And then "SELECT droptables('public');".

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.