133

I have a somewhat detailed query in a script that uses ? placeholders. I wanted to test this same query directly from the psql command line (outside the script). I want to avoid going in and replacing all the ? with actual values, instead I'd like to pass the arguments after the query.

Example:

SELECT  * 
FROM    foobar
WHERE   foo = ?
   AND  bar = ?
    OR  baz = ?  ;

Looking for something like:

%> {select * from foobar where foo=? and bar=? or baz=? , 'foo','bar','baz' };
lospejos
  • 1,976
  • 3
  • 19
  • 35
vol7ron
  • 40,809
  • 21
  • 119
  • 172
  • More context, please. Is this query in a SQL file, in a Perl/Python/Ruby/ script, or in some other place? –  Sep 12 '11 at 14:30
  • @Jack: I'm looking to do this directly from the psql prompt (command line). I'm taking my code from a script, but don't want to go through the whole find/replace process. – vol7ron Sep 12 '11 at 14:31
  • @Vol7ron, please see my answer below for a psql command line example. – MAbraham1 Apr 26 '12 at 16:29
  • 1
    @MAbraham1: nice. I should have given some more background to my question. I have a lot of scripts that have SQL in open text. Sometimes its useful to take those and hit those directly against the database, with custom values for debugging. I was looking for a way to easily do it inside Postgres w/o needing to save additional files. – vol7ron Apr 26 '12 at 17:33
  • @Vol7ron, thanks. I was thinking in terms of batch jobs, however you should be able to use the tokens in open SQL, as well. Don't forget to vote-up if you liked my answer. – MAbraham1 Apr 26 '12 at 20:01

7 Answers7

237

You can use the -v option e.g:

$ psql -v v1=12 -v v2="'Hello World'" -v v3="'2010-11-12'"

and then refer to the variables in SQL as :v1, :v2 etc:

select * from table_1 where id = :v1;

Please pay attention to how we pass string/date values using two quotes " '...' " But this way of interpolation is prone to SQL injections, because it's you who's responsible for quoting. E.g. need to include a single quote? -v v2="'don''t do this'".

A better/safer way is to let PostgreSQL handle it:

$ psql -c 'create table t (a int, b varchar, c date)'
$ echo "insert into t (a, b, c) values (:'v1', :'v2', :'v3')" \
  | psql -v v1=1 -v v2="don't do this" -v v3=2022-01-01
x-yuri
  • 16,722
  • 15
  • 114
  • 161
Gavin
  • 6,180
  • 3
  • 25
  • 25
  • 2
    +1 Interesting, passing named arguments. Are you aware of any way to do this once logged in? – vol7ron Sep 12 '11 at 14:41
  • 14
    Sure, just use `\set v3 'another value'`. Just remember, when you need to quote the value in SQL statement, use apostrophes around variable name, like this: `SELECT * FROM foo WHERE bar = :'v3';` – Cromax Feb 08 '17 at 07:04
  • 2
    I guess they got that from `awk` – Neil McGuigan Mar 01 '17 at 22:02
  • 1
    Can @ be used instead of : like sqlserver – Awais Mahmood Feb 27 '18 at 18:11
  • 1
    Worked like charm! One more thing - Is it possible to have a default value if it is not provided as a parameter? – Ajit Singh Jun 14 '19 at 11:18
  • 17
    Note, upon reading this I hoped to find that variables set with -v would be available to commands executed with -c, but, alas they are not. In other words, `psql -v v1=12 -v v2="'Hello World'" -v v3="'2010-11-12'" -c 'select * from table_1 where id = :v1;'` will generate a syntax error. However, if bash is your shell, you might try: `psql -v v1=12 -v v2="'Hello World'" -v v3="'2010-11-12'" <<< 'select * from table_1 where id = :v1;'` to good effect. – malcook Aug 30 '19 at 18:26
  • @AjitSingh You should ask a new question about that. I believe it's possible using `\if` and `:{?var}` but I've never tried it myself. – zwol Jul 29 '20 at 15:37
  • It's sort of useful, but it doesn't really solve the escaping problem: `export V1="Ben O'Shawnessy"; echo 'select :v1' | psql -v v1="'${V1}'"` fails with `ERROR: unterminated quoted string at or near "'"`. I feel we should be escaping, but you can use dollar quoting: `echo 'select :v1' | psql -v v1="\$ABC\$${V1}\$ABC\$"` – Forbesmyester Feb 10 '21 at 08:05
38

Found out in PostgreSQL, you can PREPARE statements just like you can in a scripting language. Unfortunately, you still can't use ?, but you can use $n notation.

Using the above example:

PREPARE foo(text,text,text) AS
    SELECT  * 
    FROM    foobar
    WHERE   foo = $1
       AND  bar = $2
        OR  baz = $3  ;
EXECUTE foo('foo','bar','baz');
DEALLOCATE foo;
Community
  • 1
  • 1
vol7ron
  • 40,809
  • 21
  • 119
  • 172
  • @IvanBlack was there something else you meant to include with that? :) deallocation is automatically performed at the end of a session – vol7ron Mar 19 '16 at 21:13
  • 1
    Just note that now the `foo` is busy and another `PREPARE` should have another name while current session isn't closed. If you play with `PREPARE` into `psql` it's hard to invent each time a new name and `DEALLOCATE` can help with it =) – Ivan Black Mar 19 '16 at 23:19
  • This solution is IMO very good. A useful side effect - you can easily call the prepared statement multiple times. – Yuri Jan 04 '18 at 13:06
15

In psql there is a mechanism via the

\set name val

command, which is supposed to be tied to the -v name=val command-line option. Quoting is painful, In most cases it is easier to put the whole query meat inside a shell here-document.

Edit

oops, I should have said -v instead of -P (which is for formatting options) previous reply got it right.

das-g
  • 9,718
  • 4
  • 38
  • 80
wildplasser
  • 43,142
  • 8
  • 66
  • 109
10

You can also pass-in the parameters at the psql command-line, or from a batch file. The first statements gather necessary details for connecting to your database.

The final prompt asks for the constraint values, which will be used in the WHERE column IN() clause. Remember to single-quote if strings, and separate by comma:

@echo off
echo "Test for Passing Params to PGSQL"
SET server=localhost
SET /P server="Server [%server%]: "

SET database=amedatamodel
SET /P database="Database [%database%]: "

SET port=5432
SET /P port="Port [%port%]: "

SET username=postgres
SET /P username="Username [%username%]: "

SET /P bunos="Enter multiple constraint values for IN clause [%constraints%]: "
ECHO you typed %constraints%
PAUSE
REM pause
"C:\Program Files\PostgreSQL\9.0\bin\psql.exe" -h %server% -U %username% -d %database% -p %port% -e -v v1=%constraints% -f test.sql

Now in your SQL code file, add the v1 token within your WHERE clause, or anywhere else in the SQL. Note that the tokens can also be used in an open SQL statement, not just in a file. Save this as test.sql:

SELECT * FROM myTable
WHERE NOT someColumn IN (:v1);

In Windows, save the whole file as a DOS BATch file (.bat), save the test.sql in the same directory, and launch the batch file.

Thanks for Dave Page, of EnterpriseDB, for the original prompted script.

MAbraham1
  • 1,717
  • 4
  • 28
  • 45
4

I would like to offer another answer inspired by @malcook's comment (using bash).

This option may work for you if you need to use shell variables within your query when using the -c flag. Specifically, I wanted to get the count of a table, whose name was a shell variable (which you can't pass directly when using -c).

Assume you have your shell variable

$TABLE_NAME='users'

Then you can get the results of that by using

psql -q -A -t -d databasename -c <<< echo "select count(*) from $TABLE_NAME;"

(the -q -A -t is just to print out the resulting number without additional formatting)

I will note that the echo in the here-string (the <<< operator) may not be necessary, I originally thought the quotes by themselves would be fine, maybe someone can clarify the reason for this.

Broper
  • 2,000
  • 1
  • 14
  • 15
3

It would appear that what you ask can't be done directly from the command line. You'll either have to use a user-defined function in plpgsql or call the query from a scripting language (and the latter approach makes it a bit easier to avoid SQL injection).

Community
  • 1
  • 1
2

I've ended up using a better version of @vol7ron answer:

DO $$
BEGIN
    IF NOT EXISTS(SELECT 1 FROM pg_prepared_statements WHERE name = 'foo') THEN
        PREPARE foo(text,text,text) AS
            SELECT  * 
            FROM    foobar
            WHERE   foo = $1
                AND bar = $2
                OR  baz = $3;
    END IF;
END$$;
EXECUTE foo('foo','bar','baz');

This way you can always execute it in this order (the query prepared only if it does not prepared yet), repeat the execution and get the result from the last query.

Konard
  • 2,298
  • 28
  • 21