228

How to assign the result of a query to a variable in PL/pgSQL?

I have a function:

CREATE OR REPLACE FUNCTION test(x numeric)
  RETURNS character varying
  LANGUAGE plpgsql AS
$BODY$
DECLARE
   name character varying(255);
BEGIN
   name = 'SELECT name FROM test_table where id = ' || x;
  
   if name = 'test' then
      -- do something
   else
      -- do something else
   end if;

   return ...  -- return my process result here
END
$BODY$;

In the above function I need to store the result of this query to the variable name:

'SELECT name FROM test_table where id = ' || x;

How to process this?

8 Answers 8

306

I think you're looking for SELECT select_expressions INTO:

select test_table.name into name from test_table where id = x;

That will pull the name from test_table where id is your function's argument and leave it in the name variable. Don't leave out the table name prefix on test_table.name or you'll get complaints about an ambiguous reference.

Sign up to request clarification or add additional context in comments.

12 Comments

What if I need multiple variables. Like select test_table.name, test_table.id, test_table.ssn?
@DaoLam: From the documentation I liked to: "The result of a SQL command yielding a single row (possibly of multiple columns) can be assigned to a record variable, row-type variable, or list of scalar variables."
There is no example in documentation (or I missed it), but as @muistooshort noted, you can select into multiple variables with single select: SELECT test_table.column1, test_table.column2 INTO variable1, variable2 FROM test_table WHERE id = x;
Just FYI I just found out that in my old version of Postgres that NONE of these solutions work if followed by a Dynamic Query. If your main query is dynamic, you have to make the first query dynamic also using EXECUTE .... INTO variable. This took me a looong time to figure out.
The Postgres docs refer to this syntax as SELECT select_expressions INTO (which does not create a table), not SELECT INTO (which does). More info
@EdwardBrey INTO keyword and variable list can come right after the SELECT, or the expression list, or the whole query. Plain SQL, table-spawning variant of SELECT INTO is simply disabled in PL/pgSQL, similar to how EXECUTE keyword is used to run dynamic statements in PL/pgSQL while in non-procedural PostgreSQL it's for prepared statements. Doc: this form..is not available in ECPG or PL/pgSQL, because they interpret the INTO clause differently
I intended to start by saying it's just for clarification but I removed that running out of characters, so: just clarifying. The doc doesn't strictly refer to it this way, it's just one method of Executing a Command with a Single-Row Result, which includes insert|update|delete|merge returning, not just the select. The SELECT select_expressions INTO is just the first syntax/pseudo-code example. I also couldn't fit a demo, so here it is: dbfiddle.uk/WDtV0i1t
|
157

1. SELECT INTO var ...

This assigns one or more variables in a single assignment, taken from the first returned row. It discards additional rows silently or assigns null if no row was found:

SELECT INTO [STRICT] var  [, var2 , ...]
                     expr [, expr2, ...] FROM ...;

The manual about the STRICT option:

If the STRICT option is specified, the command must return exactly one row or a run-time error will be reported.

2. var := (subquery)

You can use plain assignment in a PL/pgSQL code block, with a scalar subquery to the right. Assigns a single variable:

name := (SELECT t.name from test_table t where t.id = x);

Some notable differences to SELECT INTO:

  • SELECT INTO is slightly faster in my tests on Postgres 14.
    (Plain assignment of a constant, without involving SELECT, is 10x faster, still.)

  • SELECT INTO also sets the special variable FOUND (if any row has been found, or unconditionally with STRICT), while plain assignment does not. You may want one or the other.

  • SELECT INTO can assign multiple variables at once. See:

  • SELECT INTO assigns values from the first returned row and discards additional rows silently, while this raises an exception if more than one row is returned:

    "ERROR: more than one row returned by a subquery used as an expression"

    (Again, adding STRICT also forces this exception.)

3. Hybrid assignment

This also works:

name := t.name FROM test_table t WHERE t.id = x;

A SELECT statement without leading SELECT. It's much like the above, but "no row" results in no assignment, while the subquery expression above converts "no row" to a null value and assigns that. This can matter. See:

Unless you specifically need this behavior, rather stick to one of the first two, clearer, documented methods.

4 Comments

This is not good idea - this feature is not documented and it is ugly
PL/pgSQL allows mix of SQL and PL - and sometimes you can create really strange creatures, but is better mix PL and SQL cleanly - in isolated statements.
@PavelStehule: I agree, your form is preferable.
Actually i do prefere your syntax, but the problem is when you wana handle errors, your statement doesnt send FOUND to true in the opposite of the select into statement, checkout (postgresql.org/docs/9.1/plpgsql-statements.html)
24

The usual pattern is EXISTS(subselect):

BEGIN
  IF EXISTS(SELECT name
              FROM test_table t
             WHERE t.id = x
               AND t.name = 'test')
  THEN
     ---
  ELSE
     ---
  END IF;

This pattern is used in PL/SQL, PL/pgSQL, SQL/PSM, ...

1 Comment

Where is the variable here?
7

For example, you create person table, then insert 2 rows into it as shown below as shown below:

CREATE TABLE person (
  id INT,
  name VARCHAR(20)
);

INSERT INTO person (id, name) VALUES (1, 'John'), (2, 'David');

Then, you can create my_func() which can store a query result into person_name, then return it as shown below:

CREATE FUNCTION my_func()
RETURNS VARCHAR(20)
AS $$
DECLARE
  person_id public.person.id%TYPE := 2;
  person_name public.person.name%TYPE;
BEGIN
  SELECT name INTO person_name FROM person WHERE id = person_id;
  RETURN person_name;
END;
$$ LANGUAGE plpgsql;

Then, calling my_func() returns David as shown below:

postgres=# SELECT my_func();
 my_func
---------
 David
(1 row)

And, you can create my_func() which can store a query result into person_row, then return it as shown below:

CREATE FUNCTION my_func()
RETURNS person
AS $$
DECLARE
  person_row public.person%ROWTYPE; -- Here
  -- person_row RECORD; -- Here
BEGIN
  SELECT * INTO person_row FROM person WHERE id = 2;
  RETURN person_row;
END;
$$ LANGUAGE plpgsql;

Then, calling my_func() returns a row as shown below:

postgres=# SELECT my_func();
  my_func
-----------
 (2,David)
(1 row)

Comments

3

Create Learning Table:

CREATE TABLE "public"."learning" (
    "api_id" int4 DEFAULT nextval('share_api_api_id_seq'::regclass) NOT NULL,
    "title" varchar(255) COLLATE "default"
);

Insert Data Learning Table:

INSERT INTO "public"."learning" VALUES ('1', 'Google AI-01');
INSERT INTO "public"."learning" VALUES ('2', 'Google AI-02');
INSERT INTO "public"."learning" VALUES ('3', 'Google AI-01');

Step: 01

CREATE OR REPLACE FUNCTION get_all (pattern VARCHAR) RETURNS TABLE (
        learn_id INT,
        learn_title VARCHAR
) AS $$
BEGIN
    RETURN QUERY SELECT
        api_id,
        title
    FROM
        learning
    WHERE
        title = pattern ;
END ; $$ LANGUAGE 'plpgsql';

Step: 02

SELECT * FROM get_all('Google AI-01');

Step: 03

DROP FUNCTION get_all();

Demo: enter image description here

Comments

3

Per Executing a Query with a Single-Row Result, use this syntax:

SELECT select_expressions INTO [STRICT] target FROM ...

where target can be a record variable, a row variable, or a comma-separated list of simple variables and record/row fields.

Unlike the SELECT INTO, SELECT select_expressions INTO does not create a table.

In your example, you have a single simple variable name, so the select statement would be:

SELECT test_table.name INTO name FROM test_table WHERE test_table.id = x;

Comments

1

Many answers here omit important parts of using functions, and given the popularity I think many arrive here looking for a quick overall primer on using functions.

Here's an example of using functions in postgres (including declaration, variables, args, return values, and running). Below is an over-baked way of updating the tweet on the bottom right "blurb" with "hello world".

id (serial) pub_id (text) tweet (text)
1 abc hello world
2 def blurb
-- Optional drop if replace fails below.
drop function if exists sync_tweets(text, text);

create or replace function sync_tweets(
    src_pub_id text, -- function arguments
    dst_pub_id text
) returns setof tweets as -- i.e. rows. int, text work too
$$
declare
    src_id    int; -- temp function variables (not args)
    dest_id   int;
    src_tweet text;
begin
    -- query result into a temp variable
    src_id := (select id from tweets where pub_id = src_pub_id);

    -- query result into a temp variable (another way)
    select tweet into src_tweet from tweets where id = src_id;

    dest_id := (select id from tweets where pub_id = dst_pub_id);
    update tweets set tweet=src_tweet where id = dest_id;

    return query -- i.e. rows, return 0 with return int above works too
        select * from tweets where pub_id in (src_pub_id, dst_pub_id);
end
$$ language plpgsql; -- need the language to avoid ERROR 42P13

-- Run it!
select * from sync_tweets('abc', 'def');

/*
  Outputs
   __________________________________________________ 
  |  id (serial)  |  pub_id (text)  |  tweet (text)  |
  |---------------|-----------------|----------------|
  |  1            |  abc            |  hello world   |
  |  2            |  def            |  blurb         |
  --------------------------------------------------
*/

Comments

-3

You can use the following example to store a query result in a variable using PL/pgSQL:

 select * into demo from maintenanceactivitytrack ; 
    raise notice'p_maintenanceid:%',demo;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.