I'm trying to drop tables returned from a query using EXECUTE. Here's an example:
CREATE TABLE test_a (id BIGINT);
CREATE TABLE test_b (id BIGINT);
DO
$f$
DECLARE command TEXT;
BEGIN
SELECT INTO command 'SELECT ARRAY_TO_STRING(ARRAY_AGG($$DROP TABLE $$ || table_name), $$;$$) FROM information_schema.tables WHERE table_name ILIKE $$test%$$';
EXECUTE command;
END;
$f$;
The SELECT statement returns "DROP TABLE test_a; DROP TABLE test_b", which I'm passing into the declared variable and trying to run using EXECUTE, but to no effect. What am I doing wrong?
PostgreSQL 9.5.18, compiled by Visual C++ build 1800, 64-bit
You are storing the string SELECT ARRAY_TO_STRING ... in that variable, not the result of the SELECT statement.
You can also simplify ARRAY_TO_STRING(ARRAY_AGG(..)) to string_agg() and it's highly recommended to use format() to generate dynamic SQL, to properly deal with identifiers that need quoting.
Use the following:
DO
$f$
DECLARE
command TEXT;
BEGIN
SELECT string_agg(format('drop table %I', table_name), ';')
INTO command
FROM information_schema.tables
WHERE table_name ILIKE 'test%';
execute command;
END;
$f$;
Related
It is possible to allow PL/pgSQL script run for only specific database?
Example, I have the script, that deleting unnecessary columns in all database tables and I want to be sure that this script can only be run in a database named 'test':
DO
$$
DECLARE
rec record;
BEGIN
FOR rec IN
SELECT table_schema, table_name, column_name
FROM information_schema.columns
WHERE data_type not like 'timestamp with time zone'
AND data_type not like 'uuid'
AND data_type not like'date'
AND table_schema like 'public'
LOOP
EXECUTE format('ALTER TABLE %I.%I DROP COLUMN %I;',
rec.table_schema, rec.table_name, rec.column_name);
END LOOP;
END;
$$
LANGUAGE plpgsql;
The function current_database() will return that information. You could throw an exception to prevent further actions, if the current database isn't named test
if current_database() <> 'test' then
raise exception 'This script should only run in the database "test"';
end if;
I want to truncate the whole database while maintaining the sequence identity. I came up with something like this:
WITH tables_to_be_truncated AS (
SELECT table_name
FROM information_schema.tables
WHERE table_type='BASE TABLE'
AND table_schema='public'
AND table_name NOT IN ('admins', 'admin_roles')
)
TRUNCATE TABLE (SELECT table_name FROM tables_to_be_truncated) CONTINUE IDENTITY RESTRICT;
I get this error:
ERROR: syntax error at or near "TRUNCATE"
LINE 9: TRUNCATE TABLE (SELECT table_name FROM tables_to_be_truncated...
I do have the permissions to truncate the tables and when I run for a single table like TRUNCATE TABLE access_tokens it works fine.
I also tried with this
TRUNCATE TABLE (SELECT string_agg(table_name, ', ') FROM tables_to_be_truncated) CONTINUE IDENTITY RESTRICT
which didn't work as well.
From what I see in other posts, people are doing it with functions. I didn't want to go down this path honestly but if this is the only way...
You don't need a function for that. An anonymous code block will do:
DO $$
DECLARE row RECORD;
BEGIN
FOR row IN SELECT table_name
FROM information_schema.tables
WHERE table_type='BASE TABLE'
AND table_schema='public'
AND table_name NOT IN ('admins', 'admin_roles')
LOOP
EXECUTE format('TRUNCATE TABLE %I CONTINUE IDENTITY RESTRICT;',row.table_name);
END LOOP;
END;
$$;
Other than that I don't think you'll be able to run dynamic queries with pure SQL.
Demo: db<>fiddle
I have a query that I have to update every month and generate a new table. There are several references to this table, and I always seem to miss one. I was wondering if there is a way that I can set a local variable and reuse it through out the query. As an example:
DECLARE 'table'||to_char(curent_timestamp, 'MON') ||
to_char(current_timestanp,"YY") AS table_ref;
CREATE TABLE table_ref AS select * FROM base_table;
SELECT * FROM table_ref;
Thanks.
You can use FORMAT() and EXECUTE to execute your dynamic SQL, like so:
DO $$
DECLARE table_name TEXT;
BEGIN
SELECT FORMAT('table%I',TO_CHAR(CURRENT_TIMESTAMP,'MONYY')) INTO table_name; -- ex. tableJUN16
EXECUTE FORMAT('CREATE TABLE %I AS SELECT * FROM base_table;',table_name);
END; $$ LANGUAGE PLPGSQL;
This will create your new table from the base_table with your dynamic name.
https://www.postgresql.org/docs/current/static/functions-string.html
I have created the following function to truncate bunch of tables starting with "irm_gtresult". There are no syntax errors in my function, but the function doesn't truncate the tables when I run it. What could be wrong here?
My Postgres db version is 8.4.
create or replace function br()
RETURNS void
LANGUAGE plpgsql
AS
$$
DECLARE
row text;
BEGIN
FOR row IN
select table_name from information_schema.tables where table_name ILIKE 'irm_gtresult%'
LOOP
EXECUTE 'TRUNCATE TABLE ' || row;
END LOOP;
END;
$$;
Call:
select br();
Your code is valid. I tested and it works for me in Postgres 9.4.
Using the outdated and unsupported version 8.4 (like you added) may be the problem. The version is just too old, consider upgrading to a current version.
However, I have a couple of suggestions:
Don't use key word row as variable name.
You don't need to loop, you can TRUNCATE all tables in a single command. Faster, shorter.
You may need to add CASCADE if there are dependencies. Be aware of the effect.
CREATE OR REPLACE FUNCTION br()
RETURNS void AS
$func$
BEGIN
EXECUTE (
SELECT 'TRUNCATE TABLE '
|| string_agg(format('%I.%I', schemaname, tablename), ',')
|| ' CASCADE'
FROM pg_tables t
WHERE tablename ILIKE 'irm_gtresult%'
AND schemaname = 'public'
-- AND tableowner = 'postgres' -- optionaly restrict to one user
);
END
$func$ LANGUAGE plpgsql;
Call:
SELECT br();
I am using the view pg_tables from the system catalog. You can as well use information_schema.tables like you did. Note the subtle differences:
How to check if a table exists in a given schema
Related answers with more explanation:
Can I truncate tables dynamically?
Truncating all tables in a Postgres database
To truncate in postgres you just have to use the TRUNC() function.
Example:
SELECT TRUNC(price, 0) AS truncated_price
FROM product;
This question already has answers here:
Use text output from a function as new query
(2 answers)
Closed 8 years ago.
Using the following code I can select a few columns that share the same prefixes (either upreg_srt or downreg_srt) from my table and delete (drop) them:
DO
$do$
DECLARE
_column TEXT;
BEGIN
FOR _column IN
SELECT DISTINCT quote_ident(column_name)
FROM information_schema.columns
WHERE table_name = 'all_se_13patients_downreg_ranks'
AND column_name LIKE '%upreg_srt' OR column_name LIKE '%downreg_srt'
AND table_schema NOT LIKE 'pg_%'
order by quote_ident
LOOP
RAISE NOTICE '%',
-- EXECUTE
'ALTER TABLE all_se_13patients_downreg_ranks DROP COLUMN ' || _column;
END LOOP;
END
$do$
This code works nicely under Postgres. (Demark the --EXECUTE line first of course!)
Is there a way to utilize/alter this code (or to use different scripting) in order to actually save the chosen columns (the ones with shared prefixes) into a daughter table? Pseudo-code:
select [my chosen columns]
into myNewTbl
from myOriginalTbl
I was able to run the following code:
DO
$do$
DECLARE
qry TEXT;
BEGIN
SELECT 'SELECT id_13,' || substr(cols,2,length(cols)-2) ||
' FROM all_se_13patients_downreg_ranks' INTO qry
FROM (
SELECT array(
SELECT DISTINCT quote_ident(column_name::text)
FROM information_schema.columns
WHERE table_name = 'all_se_13patients_downreg_ranks'
AND column_name LIKE '%downreg_srt'
order by quote_ident
)::text cols
-- CAST text so we can just strip off {}s and have column list
) sub;
--EXECUTE qry;
RAISE NOTICE '%',qry;
END
$do$
It works nicely - but I can't use the EXECUTE qry line for some reason.
If I try the RAISE NOTICE '%',qry; line I get an output - which is basically the command line that I can later copy/paste and execute it just fine in a new query window(!). Therefore, I'm wondering why the EXECUTE part doesn't work?
Running the procedure with the RAISE NOTICE line I get:
NOTICE: SELECT
id_13,agk_downreg_srt,bvi_downreg_srt,cbk_downreg_srt,dj_downreg_srt,dkj_downreg_srt,flv_downreg_srt,ghw_downreg_srt,gvz_downreg_srt,idy_downreg_srt,prw_downreg_srt,spn_downreg_srt,zgr_downreg_srt,znk_downreg_srt
FROM all_se_13patients_downreg_ranks
However, if I try to run the procedure with the EXECUTE part instead I get:
Query returned successfully with no result in 51 ms.
So the problem is that postgres fails to actually execute the command line. The question is WHY? And is there a better way to perform this procedure so it actually executes?
However, if I try to run the procedure with the EXECUTE part instead I
get: "Query returned successfully with no result in 51 ms." - so the
problem is that postgres fails to actually execute the command line
No, PostgreSQL successfully executed the query. That's what "Query returned successfully" means. It returned no result, and it took 51 ms.
If you want to execute a dynamic SELECT statement, and you want to see some kind of result, use execute ... into.
do
$$
declare
qry text;
table_name text;
begin
qry := 'select table_name from information_schema.tables where table_name like ''pg_%'';';
raise notice '%', qry;
execute qry into table_name;
raise notice '%', table_name;
END
$$
NOTICE: select table_name from information_schema.tables where table_name like 'pg_%';
NOTICE: pg_statistic
Query returned successfully with no result in 24 ms.
The value "pg_statistic" was the first row in the result set. Using execute this way assigns the value of only the first row to table_name. This is by design.
If you want to insert the column names into a table, you need to write an INSERT statement, not a SELECT statement.