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;
Related
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$;
I'm trying to write some SQL to copy data from all of my PostgreSQL tables in a given database based on what's in the information_schema. It should output data files to my local machine ready for import to another machine. Ultimately, I'm going to tweak this so that I dump only select portions of tables (some of the tables I'm dumping have millions of records and I only want a small subset of data for testing purposes).
Here's what I have so far...
--Copy all tables...
DO
$$
DECLARE
formatstring text;
rec record;
BEGIN
RAISE NOTICE 'Copying tables...';
formatstring = 'COPY (select * from %I) to ''C:\Media\Code\%s.csv'';';
FOR rec IN
select table_name from information_schema.tables where table_schema = 'public' order by table_name
LOOP
RAISE NOTICE 'Table: %', rec.table_name;
RAISE NOTICE format(formatstring, rec.table_name, rec.table_name);
EXECUTE format(formatstring, rec.table_name, rec.table_name);
END LOOP;
END;
$$
LANGUAGE plpgsql;
However, I am getting this exception...
ERROR: unrecognized exception condition "format"
CONTEXT: compilation of PL/pgSQL function "inline_code_block" near line 12
********** Error **********
ERROR: unrecognized exception condition "format"
SQL state: 42704
Context: compilation of PL/pgSQL function "inline_code_block" near line 12
The escaping of the single quotes seems fine (already checked this question: Insert text with single quotes in PostgreSQL). Indeed I can do the following and it works, with text being inserted into the formatting:
select format('COPY (select * from %I) to ''C:\Media\Code\%s.csv'';', 'system_user', 'system_user');
Can anyone assist with this issue? I can easily write a script or code that will generate the copy commands for me, but it would be great to do it all within a simple bit of SQL.
The cause is a syntax error in your 3nd RAISE statement. There are several valid formats, but you cannot feed an expression to RAISE directly. It has to be a string literal - with the option of string interpolation.
While being at it, I would simplify a couple of other things, too:
DO
$do$
DECLARE
_formatstring text := $$COPY %1$I TO 'C:\Media\Code\%1$s.csv'$$;
_sql text;
_tbl text;
BEGIN
RAISE NOTICE 'Copying tables...';
FOR _tbl IN
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
ORDER BY table_name
LOOP
_sql := format(_formatstring, _tbl);
RAISE NOTICE 'Table: %', _tbl;
RAISE NOTICE '%', _sql; -- fixed!!
EXECUTE _sql;
END LOOP;
END
$do$ LANGUAGE plpgsql;
Major points
The plain table name with COPY instead of SELECT * FROM tbl.
The use of nested dollar-quotes.
Format specifiers %1$I and %1$s for the format() function, so we only need to supply the table name once.
You can assign variables at declaration time.
The scalar variable instead of a record in the FOR loop - we only need the one column anyway.
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.