I have a table with schema names. I am writing a procedure to select schema name from the table and into a variable. The variable is then used to fetch records from the schema table.
Sample code below.
CREATE OR REPLACE PROCEDURE vasmol_master.sp_pushmt(
)
LANGUAGE 'plpgsql'
AS $BODY$
DECLARE
shortcodedatabase CHARACTER VARYING;
services CHARACTER VARYING;
BEGIN
FOR shortcodedatabase IN SELECT dbase FROM vasmol_master.shortcode_services ORDER BY shortcode ASC LOOP
services := shortcodedatabase ||'.smsservices';
SELECT * FROM services;
END LOOP;
END
$BODY$;
As the documentation linked to in a comment by Richard Huxton explains, to substitute a variable identifier into a query, use EXECUTE. See the documentation.
Depending on the return values you want, your procedure could look something like the following. This uses format to create a query string, substituting in the schema name as an identifier (%I).
CREATE OR REPLACE PROCEDURE vasmol_master.sp_pushmt()
LANGUAGE 'plpgsql'
AS $BODY$
DECLARE
shortcodedatabase CHARACTER VARYING;
BEGIN
FOR shortcodedatabase IN
SELECT dbase
FROM vasmol_master.shortcode_services
ORDER BY shortcode ASC
LOOP
EXECUTE format('SELECT * FROM %I.smsservices', shortcodedatabase);
END LOOP;
END
$BODY$;
I have created a stored procedure (function) for my postgresql 9.5 database to create users. code below:
CREATE FUNCTION add_user (name text, cred text) RETURNS void AS $$
DECLARE
name text := 'abc123';
BEGIN
CREATE USER name WITH LOGIN NOSUPERUSER INHERIT NOCREATEDB
NOCREATEROLE NOREPLICATION;
GRANT CONNECT ON DATABASE test TO name;
GRANT USAGE ON SCHEMA test_schema TO name;
GRANT SELECT ON test_schema.test_table TO name;
ALTER USER name WITH PASSWORD cred;
END;
$$ LANGUAGE plpgsql;
This fails for altering password for the user. The moment I replace "cred" with hardcoded password, it works.
So, ALTER USER name WITH PASSWORD 'userpassword' works,
but NOT with any variable (cred).
Is there anyway we can use variable instead of hard coding the password?
Tried running another function, just for password
it does not work because the user is called "name", it does not take parameter value
As I mentioned before, you must do it dynamically all the code to create and grant
the function should look like this:
CREATE or REPLACE FUNCTION add_user (name text, cred text) RETURNS void AS $$
DECLARE
name_temp text := 'abc123';
BEGIN
EXECUTE ' CREATE USER '|| $1 ||' WITH LOGIN NOSUPERUSER INHERIT NOCREATEDB NOCREATEROLE NOREPLICATION';
EXECUTE ' GRANT CONNECT ON DATABASE test TO ' || $1;
EXECUTE ' GRANT USAGE ON SCHEMA test_schema TO '|| $1;
EXECUTE 'GRANT SELECT ON test_schema.test_table TO '|| $1;
EXECUTE 'ALTER USER ' || $1 || ' WITH PASSWORD '''|| $2||'''';
END;
$$ LANGUAGE plpgsql;
I recommend using the format function to form the texts of CREATE, GRANT and ALTER, for safety issues, example:
EXECUTE format(' GRANT CONNECT ON DATABASE test TO %I ', $1);
other point, consider variable name diferent of parameter name
thist function work for me
You must execute the ALTER USER with dynamic SQL, for example:
EXECUTE 'ALTER USER || name || ' WITH PASSWORD '|| cred;
I believe that you must execute dynamic SQL in whole code of CREATE and GRANT commands, because I think always will create the user called "name"
Could you please advise me SQL-based or database PostgreSQL specific command(s) or flow to create thousands or even millions of similar (same) 2-column-based empty tables in PostgreSQL RDBMS? Maybe based on basic empty table as token 'fw'? 'fw_'+(increment)
The solution has to be as fast as possible. Maybe some trick(?) Thank you.
You can use dynamic SQL in a loop.
do
$$
declare
l_counter integer;
l_name text;
l_sql text;
begin
for l_counter in 1..10000 loop
l_name := 'fw_'||to_char(l_counter, 'FM00000000');
l_sql := format('create table %I (col1 integer, col2 integer)', l_name);
execute l_sql;
end loop;
end;
$$;
But this sounds like a really bad idea.
Creating 10000 tables or orders on magnitude more is not going to be blindingly fast - it's just not going to happen. But the following MAY be a little quicker.
do $$
declare stmt record;
begin
for stmt in
with s as (select (generate_series (1,100)) n )
select 'create table tbl_' || to_char(n, 'FM00000000') || ' (i1 integer, i2 integer); ' as t from s
loop
execute stmt.t ;
end loop;
end ; $$;
As suggested let us know what issue your trying to resolve. Perhaps then someone will see a variable alternation. I would ask: After you create these tables how will you use them and how will you know which one to use?
I am trying to create function in Postgres which can automate user creation process but it doesn;t accept parameter in DDL statement.
CREATE OR REPLACE FUNCTION AUTOUSER (uname varchar(20))
RETURNS TEXT AS $$
DECLARE
nm varchar(20);
BEGIN
nm=$1;
CREATE USER nm WITH PASSWORD 'Iash12';
GRANT ALL ON DATABASE iashdb TO nm;
GRANT ALL ON ALL TABLES IN SCHEMA public TO nm;
RETURN CONCAT(nm,' Created');
END;
$$
LANGUAGE plpgsql;
Above function create user as 'nm' instead of passed parameter name however RETURN statement showing correct result. Thanks in advance,
You need to use dynamic SQL and you need to quote the parameters properly. The easiest way is to use the format() function with the appropriate placeholders:
CREATE OR REPLACE FUNCTION AUTOUSER (uname varchar(20))
RETURNS TEXT AS $$
BEGIN
execute format('CREATE USER %I WITH PASSWORD %L', uname, 'Iash12');
execute format('GRANT ALL ON DATABASE iashdb TO %I', uname);
execute format('GRANT ALL ON ALL TABLES IN SCHEMA public TO %I', uname);
RETURN CONCAT(uname,' Created');
END;
$$
LANGUAGE plpgsql;
The placeholder %I properly quotes SQL identifiers. The placeholder %L properly deals with string literals.
I have created a trigger that works like this:
After deleting data from table flux_tresorerie_historique it insert this row in the table flux_tresorerie_historique that is located in another database archive
I use dblink to insert data in the remote database, the problem is that the creation of the query is too hard especially that the table contain more than 20 columns, and I want to create similar functions for 10 other tables.
Is there another rapid way to ensure this task?
Here an example that works fine:
CREATE OR REPLACE FUNCTION flux_tresorerie_historique_backup_row()
RETURNS trigger AS
$BODY$
DECLARE date_rapprochement_flux TEXT;
DECLARE code_commission TEXT;
DECLARE reference_flux TEXT;
BEGIN
IF OLD.date_rapprochement_flux is null
THEN
date_rapprochement_flux = 'NULL';
ELSE
date_rapprochement_flux = ''''||to_char(OLD.date_rapprochement_flux, 'YYYY-MM-DD')||'''';
END IF;
IF OLD.code_commission is null
THEN
code_commission = 'NULL';
ELSE
code_commission = ''''||replace(OLD.code_commission,'''','''''')||'''';
END IF;
IF OLD.reference_flux is null
THEN
reference_flux = 'NULL';
ELSE
reference_flux = ''''||replace(OLD.reference_flux,'''','''''')||'''';
END IF;
perform dblink_connect('dbname=gtr_bd_archive user=postgres password=postgres');
perform dblink_exec('insert into flux_tresorerie_historique values('||OLD.id_flux_historique||','''||OLD.date_operation_flux||''','''||OLD.date_valeur_flux||''','||date_rapprochement_flux||','''||replace(OLD.libelle_flux,'''','''''')||''','||OLD.montant_flux||','||OLD.contre_valeur_dzd||','''||replace(OLD.rib_compte_bancaire,'''','''''')||''','||OLD.frais_flux||','''||replace(OLD.sens_flux,'''','''''')||''','''||replace(OLD.statut_flux,'''','''''')||''','''||replace(OLD.code_devise,'''','''''')||''','''||replace(OLD.code_mode_paiement,'''','''''')||''','''||replace(OLD.code_agence,'''','''''')||''','''||replace(OLD.code_compte,'''','''''')||''','''||replace(OLD.code_banque,'''','''''')||''','''||OLD.date_maj_flux||''','''||replace(OLD.statut_frais,'''','''''')||''','||reference_flux||','||code_commission||','||OLD.id_flux||');');
perform dblink_disconnect();
RETURN NULL;
END;
This is a limited application of replication. Requirements vary a lot, so there are a number of different established solutions, addressing different situations. Consider the overview in the manual.
Your hand-knit, trigger-based solution is one viable option for relatively few deletions. Opening and closing a separate connection for every row incurs quite an overhead. There are other various options.
While working with dblink I suggest some modifications. Most importantly:
Use format() to escape strings more elegantly.
Pass the whole row instead of passing and escaping every single column.
Don't place the password in every single trigger function.
Use a FOREIGN SERVER plus USER MAPPING. Detailed instructions here:
Persistent inserts in a UDF even if the function aborts
Basically, run once on the source server:
CREATE SERVER myserver FOREIGN DATA WRAPPER dblink_fdw
OPTIONS (hostaddr '127.0.0.1', dbname 'gtr_bd_archive');
CREATE USER MAPPING FOR role_source SERVER myserver
OPTIONS (user 'postgres', password 'secret');
Preferably, don't log in as superuser at the target server. Use a dedicated role with limited privileges to avoid privilege escalation.
And use a password file on the target server to allow password-less access. This way you don't even have to store the password in the USER MAPPING. Instructions in the last chapter of this related answer:
Run batch file with psql command without password
Then:
CREATE OR REPLACE FUNCTION pg_temp.flux_tresorerie_historique_backup_row()
RETURNS trigger AS
$func$
BEGIN
PERFORM dblink_connect('myserver'); -- name of foreign server from above
PERFORM dblink_exec( format(
$$
INSERT INTO flux_tresorerie_historique -- provide target column list!
SELECT (r).id_flux_historique
, (r).date_operation_flux
, (r).date_valeur_flux
, (r).date_rapprochement_flux::date -- 'YYYY-MM-DD' is default ISO format anyway
, (r).libelle_flux
, (r).montant_flux
, (r).contre_valeur_dzd
, (r).rib_compte_bancaire
, (r).frais_flux
, (r).sens_flux
, (r).statut_flux
, (r).code_devise
, (r).code_mode_paiement
, (r).code_agence
, (r).code_compte
, (r).code_banque
, (r).date_maj_flux
, (r).statut_frais
, (r).reference_flux
, (r).code_commission
, (r).id_flux
FROM (SELECT %L::flux_tresorerie_historique) t(r)
$$, OLD::text)); -- cast whole row type
PERFORM dblink_disconnect();
RETURN NULL; -- only for AFTER trigger
END
$func$ LANGUAGE plpgsql;
You should spell out the list of columns for the target table if the row types don't match.
If you are serious about this:
insert this row in the table flux_tresorerie_historique
I.e., you insert the whole row and the target row type is identical (no extracting a date from a timestamp etc.), you can simplify much further passing the whole row.
CREATE OR REPLACE FUNCTION flux_tresorerie_historique_backup_row()
RETURNS trigger AS
$func$
BEGIN
PERFORM dblink_connect('myserver'); -- name of foreign server
PERFORM dblink_exec( format(
$$
INSERT INTO flux_tresorerie_historique
SELECT (%L::flux_tresorerie_historique).*
$$
, OLD::text));
PERFORM dblink_disconnect();
RETURN NULL; -- only for AFTER trigger
END
$func$ LANGUAGE plpgsql;
Related:
How do I do large non-blocking updates in PostgreSQL?
You can use quote_nullable for this! Also, concat_ws comes very handy:
CREATE OR REPLACE FUNCTION flux_tresorerie_historique_backup_row()
RETURNS trigger AS
$BODY$
BEGIN
perform dblink_connect('dbname=gtr_bd_archive user=postgres password=postgres');
perform dblink_exec('insert into flux_tresorerie_historique values('||
concat_ws(', ', quote_nullable(OLD.id_flux_historique),
quote_nullable(OLD.date_operation_flux),
quote_nullable(OLD.date_valeur_flux),
quote_nullable(to_char(OLD.date_rapprochement_flux, 'YYYY-MM-DD')),
quote_nullable(OLD.libelle_flux),
quote_nullable(OLD.montant_flux),
quote_nullable(OLD.contre_valeur_dzd),
quote_nullable(OLD.rib_compte_bancaire),
quote_nullable(OLD.frais_flux),
quote_nullable(OLD.sens_flux),
quote_nullable(OLD.statut_flux),
quote_nullable(OLD.code_devise),
quote_nullable(OLD.code_mode_paiement),
quote_nullable(OLD.code_agence),
quote_nullable(OLD.code_compte),
quote_nullable(OLD.code_banque),
quote_nullable(OLD.date_maj_flux),
quote_nullable(OLD.statut_frais),
quote_nullable(OLD.reference_flux),
quote_nullable(OLD.code_commission),
quote_nullable(OLD.id_flux)
)||');');
perform dblink_disconnect();
RETURN NULL;
END;
Note that it is OK to place non-sting values between single quotes, since a quoted literal is for PostgreSQL just as good a literal value as one without the quotes, so it is convenient to place all of the columns processed by quote_nullable. Also note that quote_nullable will already output dates in YYYY-MM-DD format (e.g. select quote_nullable(now()::date) would result in '2016-05-04'), so you may want to simplify OLD.date_rapprochement_flux even further by removing the to_char.