451

How do I declare a variable for use in a PostgreSQL 8.3 query?

In MS SQL Server I can do this:

DECLARE @myvar INT
SET @myvar = 5

SELECT *
FROM somewhere
WHERE something = @myvar

How do I do the same in PostgreSQL? According to the documentation variables are declared simply as "name type;", but this gives me a syntax error:

myvar INTEGER;

Could someone give me an example of the correct syntax?

ypercubeᵀᴹ
  • 113,259
  • 19
  • 174
  • 235
EMP
  • 59,148
  • 53
  • 164
  • 220
  • 3
    It can be done in just PostgreSQL. See the answer to this related question: http://stackoverflow.com/questions/766657/how-do-you-use-variables-in-a-simple-postgresql-script#6990059 – Sean the Bean Jun 29 '12 at 17:35
  • 4
    This related answer has better answers: http://stackoverflow.com/questions/13316773/is-there-a-way-to-define-a-named-constant-in-a-postgresql-query – Erwin Brandstetter Apr 06 '15 at 12:20

15 Answers15

398

I accomplished the same goal by using a WITH clause, it's nowhere near as elegant but can do the same thing. Though for this example it's really overkill. I also don't particularly recommend this.

WITH myconstants (var1, var2) as (
   values (5, 'foo')
)
SELECT *
FROM somewhere, myconstants
WHERE something = var1
   OR something_else = var2;
fei0x
  • 4,259
  • 4
  • 17
  • 37
  • 5
    This works great for most instances where you would want variables. However, if you wanted to use a variable for LIMIT (which can't contain variables), then you'd want to use `\set` as suggested in Shahriar Aghajani's answer. – cimmanon Oct 30 '13 at 15:20
  • 2
    This is ideal for when I have a migration script where I want to import some relational data. Obviously I won't know the sequence id the relational data is given. – Relequestual Feb 13 '15 at 09:40
  • 4
    I just tried this approach, and found a perhaps better way: `JOIN myconstants ON true` and then there is no need to do the sub-select. – vektor Jul 08 '15 at 16:59
  • 24
    This only works within a single query, you can't share a `WITH` CTE across queries in a transaction. – Daenyth Jul 05 '16 at 13:42
  • 1
    If anyone stumbled upon this solution and couldn't get it to work: You have to add one record to the "anywhere_unimportant" table. – Bernhardt Scherer Aug 10 '16 at 13:06
  • 3
    Old question, but here here’s a variation: `WITH constants AS (SELECT 5 AS var) SELECT * FROM somewhere CROSS JOIN constants WHERE someting=var;`. The CROSS JOIN, being a with a single-row table expression, virtually duplicates the data for all of the rows in the real table, and simplifies the expression. – Manngo Oct 08 '16 at 05:27
  • 1
    "FROM anywhere_unimportant" is optional, so if you are looking to just set some values and not actually pull from a table, drop it. – Westy92 Jan 23 '18 at 21:03
  • I went with Manngo's CROSS JOIN suggestion, but for edification how would one update this when there is already a LEFT JOIN, making the "," join syntax break? – androidguy Mar 15 '18 at 06:31
209

There is no such feature in PostgreSQL. You can do it only in pl/PgSQL (or other pl/*), but not in plain SQL.

An exception is WITH () query which can work as a variable, or even tuple of variables. It allows you to return a table of temporary values.

WITH master_user AS (
    SELECT
      login,
      registration_date
    FROM users
    WHERE ...
)

SELECT *
FROM users
WHERE master_login = (SELECT login
                      FROM master_user)
      AND (SELECT registration_date
           FROM master_user) > ...;
J.Wincewicz
  • 849
  • 12
  • 19
  • 3
    I tried this method of CTEs being used as vriables. But than i quickly ran into a problem where different data modifying queries in CTEs are not guaranteed to see each other's effects. I had to use multiple CTEs as i needed to use that variable in multiple queries. – Zia Ul Rehman Mughal Jan 11 '19 at 09:45
151

You could also try this in PLPGSQL:

DO $$
DECLARE myvar integer;
BEGIN
    SELECT 5 INTO myvar;
    
    DROP TABLE IF EXISTS tmp_table;
    CREATE TEMPORARY TABLE tmp_table AS
    SELECT * FROM yourtable WHERE   id = myvar;
END $$;

SELECT * FROM tmp_table;

The above requires Postgres 9.0 or later.

Saaru Lindestøkke
  • 2,067
  • 1
  • 25
  • 51
Dario Barrionuevo
  • 3,007
  • 1
  • 18
  • 17
109

Dynamic Config Settings

you can "abuse" dynamic config settings for this:

-- choose some prefix that is unlikely to be used by postgres
set session my.vars.id = '1';

select *
from person 
where id = current_setting('my.vars.id')::int;

Config settings are always varchar values, so you need to cast them to the correct data type when using them. This works with any SQL client whereas \set only works in psql

The above requires Postgres 9.2 or later.

For previous versions, the variable had to be declared in postgresql.conf prior to being used, so it limited its usability somewhat. Actually not the variable completely, but the config "class" which is essentially the prefix. But once the prefix was defined, any variable could be used without changing postgresql.conf

Rob Bednark
  • 25,981
  • 23
  • 80
  • 125
  • 4
    @BrijanElwadhi: yes that's transactional. –  Feb 20 '17 at 22:24
  • 2
    As a side note: some words are reserved, for example changing `set session my.vars.id = '1';` to `set session my.user.id = '1';` will yield `ERROR: syntax error at or near "user"` – dominik Jul 09 '17 at 19:13
  • 3
    @BrijanElwadhi: To make variable transaction specific you must use: `SET LOCAL ...`. The `session` variable will be in effect as long as you connection is. The `local` is scoped to transaction. – Eugen Konkov Aug 13 '18 at 14:07
  • @dominik You can get around that limitation with quotes, eg., `set session "my.user.id" = '1';` The `current_setting('my.user.id')` call works as expected. – Miles Elam Jan 28 '20 at 22:11
  • But it seems to be impossible to deal with datetime values in session variable. Something like `SET SESSION "vars.tomorrow" = CURRENT_DATE + '1 DAY'::interval;` does not work, even with casting to text. In my opinion a great restriction. – Dave_B. Sep 15 '20 at 11:19
  • This worked great, but ended up being way slower than using a constant value. A simple query to get the count of records with a date field being greater than `current_setting('my.vars.cutoff_date')::timestamp` took 130-150 ms, but the same query with a string for the date took 25 ms. – Jeffrey Harmon Nov 13 '20 at 02:30
  • perform set_config('my.acct_id', '123', false); – Charlie 木匠 Apr 10 '21 at 02:18
82

It depends on your client.

However, if you're using the psql client, then you can use the following:

my_db=> \set myvar 5
my_db=> SELECT :myvar  + 1 AS my_var_plus_1;
 my_var_plus_1 
---------------
             6

If you are using text variables you need to quote.

\set myvar 'sometextvalue'
select * from sometable where name = :'myvar';
Kemin Zhou
  • 6,264
  • 2
  • 48
  • 56
49

This solution is based on the one proposed by fei0x but it has the advantages that there is no need to join the value list of constants in the query and constants can be easily listed at the start of the query. It also works in recursive queries.

Basically, every constant is a single-value table declared in a WITH clause which can then be called anywhere in the remaining part of the query.

  • Basic example with two constants:
WITH
    constant_1_str AS (VALUES ('Hello World')),
    constant_2_int AS (VALUES (100))
SELECT *
FROM some_table
WHERE table_column = (table constant_1_str)
LIMIT (table constant_2_int)

Alternatively you can use SELECT * FROM constant_name instead of TABLE constant_name which might not be valid for other query languages different to postgresql.

Jorge Luis
  • 651
  • 7
  • 7
  • Very neat, I'll be using this often. Just curious - what does the TABLE keyword do in this context? I'm having no luck searching for it since it's such a generic term. – user323094 Jul 16 '20 at 13:56
  • @user323094 it's same as 'select * from XX' – tcpiper Aug 28 '21 at 12:50
  • 2
    it works only once. if you write select query to use same value twice, it gives error saying "SQL Error [42P01]: ERROR: relation "constant_1_str" does not exist Position: 20" – Satish Patro Dec 14 '21 at 11:52
  • 2
    @SatishPatro Yes that's the only downside to the CTE approach - it only exists for the first query that follows the creating of the CTE. This example is probably the nicest version of the CTE approach to variables though, IMO – devklick Feb 16 '22 at 10:13
28

Using a Temp Table outside of pl/PgSQL

Outside of using pl/pgsql or other pl/* language as suggested, this is the only other possibility I could think of.

begin;
select 5::int as var into temp table myvar;
select *
  from somewhere s, myvar v
 where s.something = v.var;
commit;
Evan Carroll
  • 78,363
  • 46
  • 261
  • 468
21

I want to propose an improvement to @DarioBarrionuevo's answer, to make it simpler leveraging temporary tables.

DO $$
    DECLARE myvar integer = 5;
BEGIN
    CREATE TEMP TABLE tmp_table ON COMMIT DROP AS
        -- put here your query with variables:
        SELECT * 
        FROM yourtable
        WHERE id = myvar;
END $$;

SELECT * FROM tmp_table;
Community
  • 1
  • 1
bluish
  • 26,356
  • 27
  • 122
  • 180
20

As you will have gathered from the other answers, PostgreSQL doesn’t have this mechanism in straight SQL, though you can now use an anonymous block. However, you can do something similar with a Common Table Expression (CTE):

WITH vars AS (
    SELECT 5 AS myvar
)
SELECT *
FROM somewhere,vars
WHERE something = vars.myvar;

You can, of course, have as many variables as you like, and they can also be derived. For example:

WITH vars AS (
    SELECT
        '1980-01-01'::date AS start,
        '1999-12-31'::date AS end,
        (SELECT avg(height) FROM customers) AS avg_height
)
SELECT *
FROM customers,vars
WHERE (dob BETWEEN vars.start AND vars.end) AND height<vars.avg_height;

The process is:

  • Generate a one-row cte using SELECT without a table (in Oracle you will need to include FROM DUAL).
  • CROSS JOIN the cte with the other table. Although there is a CROSS JOIN syntax, the older comma syntax is slightly more readable.
  • Note that I have cast the dates to avoid possible issues in the SELECT clause. I used PostgreSQL’s shorter syntax, but you could have used the more formal CAST('1980-01-01' AS date) for cross-dialect compatibility.

Normally, you want to avoid cross joins, but since you’re only cross joining a single row, this has the effect of simply widening the table with the variable data.

In many cases, you don’t need to include the vars. prefix if the names don’t clash with the names in the other table. I include it here to make the point clear.

Also, you can go on to add more CTEs.

This also works in all current versions of MSSQL and MySQL, which do support variables, as well as SQLite which doesn’t, and Oracle which sort of does and sort of doesn’t.

Manngo
  • 14,066
  • 10
  • 88
  • 110
  • 1
    Downvoted as this only works for a single select block. The reason we use variables is so that it effects multiple lines of code – user1034912 Jun 08 '22 at 03:05
  • @user1034912 I did say _similar_. The reason we use variables is _also_ to set arbitrary values or to pre-calculate values to be used later in the query, and they might well be used multiple times in the query. You will notice that OP’s sample could very easily have been done with a CTE. – Manngo Jun 08 '22 at 03:15
  • Using "JOIN cte_name ON TRUE" instead of "FROM cte_name" is better. – Tomex Ou Jan 04 '23 at 12:51
18

You may resort to tool special features. Like for DBeaver own proprietary syntax:

@set name = 'me'
SELECT :name;
SELECT ${name};

DELETE FROM book b
WHERE b.author_id IN (SELECT a.id FROM author AS a WHERE a.name = :name);
gavenkoa
  • 45,285
  • 19
  • 251
  • 303
  • 4
    This is closer to usable: i'm going to look into whether DBeaver supports lists and looping: i need to apply the same sql to multiple schemas and the list would be of the schemas to apply them to. – WestCoastProjects Mar 26 '20 at 17:01
17

True, there is no vivid and unambiguous way to declare a single-value variable, what you can do is

with myVar as (select "any value really")

then, to get access to the value stored in this construction, you do

(select * from myVar)

for example

with var as (select 123)    
... where id = (select * from var)
  • 1
    I am able to use only once, 2nd time I am trying to use it, it's showing "SQL Error [42P01]: ERROR: relation "varName" does not exist Position: 143" – Satish Patro Dec 14 '21 at 11:47
  • 1
    @SatishPatro Look up common table expressions in the docs. You just need to use it from the right scope in your queries. – oligofren May 02 '23 at 13:24
  • This worked for me in a CASE. `WITH var AS (SELECT SUM(qty) FROM table1 WHERE someval = 12) SELECT id, CASE WHEN (SELECT * FROM var) < 3 THEN 'small' WHEN (SELECT * FROM var) < 10 THEN 'medium' ELSE 'large' END AS "size" FROM table2 WHERE this = that;` – Andy Norris Jun 26 '23 at 19:30
11

Here is an example using PREPARE statements. You still can't use ?, but you can use $n notation:

PREPARE foo(integer) AS
    SELECT  *
    FROM    somewhere
    WHERE   something = $1;
EXECUTE foo(5);
DEALLOCATE foo;
Martin
  • 4,042
  • 2
  • 19
  • 29
10

In DBeaver you can use parameters in queries just like you can from code, so this will work:

SELECT *
FROM somewhere
WHERE something = :myvar

When you run the query DBeaver will ask you for the value for :myvar and run the query.

The Coder
  • 4,981
  • 2
  • 29
  • 36
4

Here is a code segment using plain variable in postges terminal. I have used it a few times. But need to figure a better way. Here I am working with string variable. Working with integer variable, you don't need the triple quote. Triple quote becomes single quote at query time; otherwise you got syntax error. There might be a way to eliminate the need of triple quote when working with string variables. Please update if you find a way to improve.

\set strainname '''B.1.1.7'''

select *
from covid19strain
where name = :strainname ;
Kemin Zhou
  • 6,264
  • 2
  • 48
  • 56
3

In psql, you can use these 'variables' as macros. Note that they get "evaluated" every time they are used, rather than at the time that they are "set".

Simple example:

\set my_random '(SELECT random())'
select :my_random;  -- gives  0.23330629315990592
select :my_random;  -- gives  0.67458399344433542

this gives two different answers each time.

However, you can still use these as a valuable shorthand to avoid repeating lots of subselects.

\set the_id '(SELECT id FROM table_1 WHERE name = ''xxx'' LIMIT 1)'

and then use it in your queries later as

:the_id 

e.g.

INSERT INTO table2 (table1_id,x,y,z) VALUES (:the_id, 1,2,3)

Note you have to double-quote the strings in the variables, because the whole thing is then string-interpolated (i.e. macro-expanded) into your query.