Using a variable in a function with sql and sh script - postgresql

I am developing a set of .sql scripts which will be called by a .sh script. In this .sh script, I use the command:
psql "connection parameters" -f ./myscript.sql -v var1 = "schema.table"
For the moment everything is well.
In my .sql script, I currently have this:
CREATE or replace FUNCTION myFunction (tarteenpion varchar) RETURNS void AS $$
DECLARED
MONTH_MM varchar: = to_char (current_timestamp, 'MM');
YEAR_AAAA varchar: = to_char (current_timestamp, 'YYYY');
YEAR_AA varchar: = to_char (current_timestamp, 'YY')
cmd varchar: = 'DROP TABLE IF EXISTS' || tarteenpion || ';' ;
BEGIN
execute cmd;
END;
$$ LANGUAGE plpgsql;
--SELECT myFunction ('schema.table');
SELECT myFunction (: var1);
DROP FUNCTION myFunction (tarteenpion varchar);
My problem comes from the use of my external variable :var1
- If I directly write my value 'schema.table' with ', the function runs correctly. But I don't want that.
If I write my variable :var1 WITHOUT the ' I have this error
SELECT myFunction (: var1); => ERROR: missing FROM-clause entry for table "schema"
LINE 1: SELECT myFunction (schema.table);
So it is interpreted as not usable by the function.
If I write my variable ':var1' WITH the ' I have this error
SELECT myFunction (':var1'); => ERROR: syntax error at or near ":"
LINE 1: DROP TABLE IF EXISTS :var1
So it is not interpreted but used by the function.
Can you guide me to find a solution that has bothered me for a long time.
Thank you :)

You can make psql quote the variable value with single quotes:
SELECT myFunction (:'var1');
That will be replaced to
SELECT myFunction ('schema.table');

Related

Postgresql - pass parameters to COPY in an sql script

I can use the -v v1=foo syntax just fine for queries in my sql script, but I can't figure out how to use that parameter in a copy statement. I'd like to execute the script like:
psql -d my_db -f ./exports.sql -v v1="'/Users/username/test.json'"
And in the script do some version of:
copy (
select * from bar
) to :v1;
or
DO $$
BEGIN
EXECUTE
'copy (select * from bar) to ' || :v1;
END $$
or
DO $$
BEGIN
EXECUTE
format('copy (select * from bar) to %L',:v1);
END $$
But none of the above work :(
Variable substitution doesn't work in a string literal.
Use psql's \gexec:
SELECT format(
$$copy (select * from bar) to %L$$,
:v1
) \gexec

How can I use a variable to specify a COPY TO destination in a function?

In PostgreSQL 9.3, I want to create a function that copies data from a table into a CSV file. This function works:
CREATE OR REPLACE FUNCTION backup_tables(character varying)
RETURNS integer AS
$BODY$
declare
fileName text;
begin
copy adhoc_query to 'c:/misc/adhoc_query.csv' csv;
return 1;
end;
However, I get a syntax error from this:
CREATE OR REPLACE FUNCTION backup_tables(character varying)
RETURNS integer AS
$BODY$
declare
fileName text;
begin
fileName = 'c:/misc/adhoc_query.csv';
copy adhoc_query to fileName csv;
return 1;
end;
pgAdmin flags "fileName" in the copy command as the location of the error. I would like to be able to have the caller specify the destination file, but it looks to be impossible. Is there a way?
You cannot use anything else than a literal for the path. But you can build the command from the variables and then EXECUTE it.
...
EXECUTE 'COPY (' || adhoc_query || ') TO ''' || fileName || ''' CSV;';
...
Save your query in a .sql file and execute it with psql :
psql -U postgres -d database -A -F"," -f /path/to/sql/file.sql > /path/to/csv/file.csv
From the psql doc:
-A : switches to unaligned output mode. (The default output mode is aligned.) This is equivalent to \pset format unaligned.
-F : Use separator as the field separator for unaligned output. This is equivalent to \pset fieldsep or \f.

Reference psql parameter inside PL/pgSQL anonymous block

I'd like to pass a parameter to an anonymous PL/pgSQL block via psql command line, then check that parameter in a conditional.
The relevant part of the SQL is here:
do $$
begin
if (':para' = 1) then
-- statements;
end if;
end $$
;
I call this script as such:
psql -d dbname -v para=1 < script.sql
I receive the error:
ERROR: invalid input syntax for integer: ":para"
LINE 1: SELECT (':para' = 1)
^
QUERY: SELECT (':para' = 1)
CONTEXT: PL/pgSQL function inline_code_block line 3 at IF
I tried using the case/when paradigm, which did not work either.
I am guessing that a psql parameter is not compatible with PL/pgSQL? Ultimately, my goal is to run a single delete statement if I pass 1 as a psql parameter, and not run it if I pass 0.
The psql parser can't see what is inside strings. This might be what you want:
delete from t
where :para = 1
Do it outside of an anonymous block. If you really need PL/pgSQL use a parameterized function:
create or replace function f(_para integer)
returns void as $$
begin
if _para = 1 then
--statements
end if;
end; $$ language plpgsql;
And your script file will have:
select f(:para);
If you do not want to permanently add a function to the db do it all inside the script:
drop function if exists f_iu7YttW(integer);
create or replace function f_iu7YttW(_para integer)
returns void as $$
begin
if _para = 1 then
--statements
end if;
end; $$ language plpgsql;
select f_iu7YttW(:para);
drop function f_iu7YttW(integer);
Give the function an unique name so you do not run the risk of dropping something else.

plpgsql Select statement in For loop to create multiple CSV files

I would like to repeat the following query 8760 times, replacing ‘2’ with 1 to 8760 for every hour in the year. The idea is to create a separate CSV file for each hour for further processing.
COPY
(SELECT *
FROM
public.completedsolarirad2012
WHERE
completedsolarirad2012."UniquetmstmpID" = 2)
TO 'C:\temp\2012hour2.csv' WITH DELIMITER ','
CSV HEADER
I have put together the following function (testing with only a few hours):
CREATE OR REPLACE FUNCTION everyhour()
RETURNS void AS
$BODY$BEGIN
FOR i IN 0..5 LOOP
EXECUTE $x$
COPY (
SELECT *
FROM
public.completedsolarirad2012
WHERE
completedsolarirad2012."UniquetmstmpID" = i
)
TO $concat$ 'C:\temp.' || i::text
|| '.out.csv' WITH DELIMITER ',' CSV HEADER $concat$
$x$;
END LOOP;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION everyhour()
OWNER TO postgres;
I seem to be having two separate problems:
Firstly, I’m getting:
Error: column "i" does not exist.
Secondly, when I test the concatenation statement only by replacing “i” with e.g. “2”, I get:
Error: relative path not allowed for COPY to file
I am the postgres superuser, so I do not understand why I am having this problem.
Note: Removing the $concat$ double-quoting around the concatenation statement gives the following error:
ERROR: syntax error at or near "||"
LINE 9: TO 'C:\temp.' || i::text
I would be very grateful for any help.
Assuming your server OS is Windows.
CREATE OR REPLACE FUNCTION everyhour()
RETURNS void AS
$func$
BEGIN
FOR i IN 0..5 LOOP
EXECUTE '
COPY (
SELECT *
FROM public.completedsolarirad2012 c
WHERE c."UniquetmstmpID" = ' || i || $x$)
TO 'C:/temp.'$x$ || i || $x$'.out.csv' WITH DELIMITER ',' CSV HEADER$x$;
END LOOP;
END
$func$ LANGUAGE plpgsql;
You had one layer of dollar-quoting too many.
You also accidentally concatenated the letter "i" instead of its value.
Use forward-slashes, even with windows. Or you may have to double up the backslashes, depending on your settings. More in this related answer:
PostgreSQL: export resulting data from SQL query to Excel/CSV
Simpler with format()
Since Postgres 9.1 you can use format() to simplify complex concatenations:
CREATE OR REPLACE FUNCTION everyhour()
RETURNS void AS
$func$
BEGIN
FOR i IN 0..5 LOOP
EXECUTE format($x$COPY (
SELECT *
FROM public.completedsolarirad2012 c
WHERE c."UniquetmstmpID" = %1$s)
TO 'C:/temp.%1$s.out.csv' WITH DELIMITER ',' CSV HEADER$x$, i);
END LOOP;
END
$func$ LANGUAGE plpgsql;

EXECUTE...USING statement in PL/pgSQL doesn't work with record type?

I'm trying to write a function in PL/PgSQL that have to work with a table it receives as a parameter.
I use EXECUTE..INTO..USING statements within the function definition to build dynamic queries (it's the only way I know to do this) but ... I encountered a problem with RECORD data types.
Let's consider the follow (extremely simplified) example.
-- A table with some values.
DROP TABLE IF EXISTS table1;
CREATE TABLE table1 (
code INT,
descr TEXT
);
INSERT INTO table1 VALUES ('1','a');
INSERT INTO table1 VALUES ('2','b');
-- The function code.
DROP FUNCTION IF EXISTS foo (TEXT);
CREATE FUNCTION foo (tbl_name TEXT) RETURNS VOID AS $$
DECLARE
r RECORD;
d TEXT;
BEGIN
FOR r IN
EXECUTE 'SELECT * FROM ' || tbl_name
LOOP
--SELECT r.descr INTO d; --IT WORK
EXECUTE 'SELECT ($1)' || '.descr' INTO d USING r; --IT DOES NOT WORK
RAISE NOTICE '%', d;
END LOOP;
END;
$$ LANGUAGE plpgsql STRICT;
-- Call foo function on table1
SELECT foo('table1');
It output the following error:
ERROR: could not identify column "descr" in record data type
although the syntax I used seems valid to me. I can't use the static select (commented in the example) because I want to dinamically refer the columns names.
So..someone know what's wrong with the above code?
It's true. You cannot to use type record outside PL/pgSQL space.
RECORD value is valid only in plpgsql.
you can do
EXECUTE 'SELECT $1.descr' INTO d USING r::text::xx;
$1 should be inside the || ,like || $1 || and give spaces properly then it will work.
BEGIN
EXECUTE ' delete from ' || quote_ident($1) || ' where condition ';
END;