Postgresql - pass parameters to COPY in an sql script - postgresql

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

Related

Using a variable in a function with sql and sh script

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');

Merging multiple table in one postgresql

I inherit a database with a specific schema that have 288 tables. These tables have all exactly the same column name. I would like to merge all these 288 tables in 1.
My first try was to CREATE TABLE AS :
CREATE TABLE bigTable AS SELECT X.* FROM (SELECT tablename FROM pg_tables WHERE tablename LIKE '%xxx') AS X
But this obviously doesn't work.
So I tried with a plpgsql script:
DO $$
DECLARE
r RECORD;
BEGIN
FOR r in (SELECT tablename FROM pg_tables WHERE tablename LIKE '%iti') LOOP
INSERT INTO xxx(gid, shape_len, geom)
SELECT * FROM r;
END LOOP;
END;
$$;
But it tells me that it doesn't know what r is.
I think I'm missing something about how pg handle this kind of things.
When you are creating syntax dynamically, use Execute command that performs SQL supplied with string data.
I've just made 3 tables with example values and made 4th table that has been filled using this statement
DO $$
DECLARE
tbl_name text;
BEGIN
FOR tbl_name IN (SELECT tablename FROM pg_tables WHERE tablename LIKE 'table%') LOOP
execute 'INSERT INTO table4 select * FROM '||tbl_name;
END LOOP;
END;
$$
Use this shell script to fetch all the tables and insert into the new table
#!/bin/bash
psql **DATABASE_NAME** -c "select tablename from pg_tables where
schemaname='public'" | sed 1,2d | head -n -2 > hello.out
while read line;
do
psql **DATABASE_NAME** -c "insert into **TABLE_NAME** (select * from $line )"
done < hello.out

Using variable passed in from psql command line, in function

I can't figure out how to access variables inside my plpgsql function. I'm using postgres 9.5 under Cygwin.
functions.sql
-- this works fine
\echo Recreate = :oktodrop
CREATE OR REPLACE FUNCTION drop_table(TEXT) RETURNS VOID AS
$$
BEGIN
IF EXISTS ( SELECT * FROM information_schema.tables WHERE table_name = $1 ) THEN
-- syntax error here:
IF (:oktodrop == 1 ) THEN
DROP TABLE $1;
END IF;
END IF;
END;
$$
language 'plpgsql';
psql.exe -v oktodrop=1 -f functions.sql
Password:
Recreate = 1
psql:functions.sql:13: ERROR: syntax error at or near ":"
LINE 5: IF (:oktodrop == 1 ) THEN
^
Perhaps I've oversimplified your task (feel free to tell me if that's the case), but why not create the function like this:
CREATE OR REPLACE FUNCTION drop_table(tablename TEXT, oktodrop integer)
RETURNS text AS
$$
DECLARE
result text;
BEGIN
IF EXISTS ( SELECT * FROM information_schema.tables WHERE table_name = tablename ) THEN
IF (oktodrop = 1 ) THEN
execute 'DROP TABLE ' || tablename;
result := 'Dropped';
ELSE
result := 'Not Okay';
END IF;
ELSE
result := 'No such table';
END IF;
return result;
END;
$$
language 'plpgsql';
Then the implementation would be:
select drop_table('foo', 1);
I should also caution that because you have not specified the table_schema field, it's conceivable that your target table exists in another schema, and the actual drop command will fail because it doesn't exist in the default schema.

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');".

Strip parentheses from SQL query

I am tinkering with this snippet of SQL I reappropriated from another SO question:
CREATE OR REPLACE FUNCTION db_to_csv(path TEXT) RETURNS void AS $$
DECLARE
target RECORD;
statement TEXT;
BEGIN
FOR target IN
SELECT DISTINCT table_name
FROM information_schema.columns
WHERE table_schema='public'
AND position('_' in table_name) <> 1
ORDER BY 1
LOOP
statement := 'COPY '
|| target
|| ' TO '''
|| path
|| '/'
|| target
|| '.csv'
||''' DELIMITER '';'' CSV HEADER';
EXECUTE statement;
END LOOP;
return;
end;
$$ LANGUAGE plpgsql;
It very nearly almost works, but the statement it tries to execute looks like:
COPY (Bok_F_Acc) TO '/Users/jgt/dir/(Bok_F_Acc).csv' DELIMIT...
…Whereas I would like the statement to look like:
COPY Bok_F_Acc TO '/Users/jgt/dir/Bok_F_Acc.csv' DELIMIT...
How can I strip those parentheses?
SELECT QUOTE_IDENT(TRIM(BOTH '()' FROM target));
Note that this would remove any number of leading/trailing parentheses.
To remove at most one of each, you could use :
SELECT QUOTE_IDENT(REGEXP_REPLACE(target, E'\^\\(|\\)\$', '', 'g'));