Assignment performance in PL/pgSQL

In my post about choosing the language for stored functions I showed what impact the different languages have.

When choosing PL/pgSQL as the language, there is one performance optimization that is often overlooked: assignment of values to variables. Different approaches have a different performance.

Let’s have a look at the impact of the different assignment options:

Assignment using := and a select statement (unfortunately I do see this a lot):

create or replace function apply_tax_1(p_amount numeric)
  returns numeric
as
$$
declare 
  l_result numeric;
begin  
  l_result := (select p_amount  * 1.19);
  return l_result;
end;    
$$
language plpgsql;

Assignment using select ... into:

create or replace function apply_tax_2(p_amount numeric)
  returns numeric
as
$$
declare 
  l_result numeric;
begin  
  select p_amount * 1.19 
     into l_result;
     
  return l_result;
end;    
$$
language plpgsql;

And finally, direct assignment of the result:

create or replace function apply_tax_3(p_amount numeric)
  returns numeric
as
$$
declare 
  l_result numeric;
begin  
  l_result := p_amount * 1.19;
  return l_result;
end;    
$$
language plpgsql;

Let’s call this function 5 million times:

explain (analyze)
select apply_tax_1(42.1)
from generate_series(1,5e6) t(i);
QUERY PLAN                                                                                                                      
--------------------------------------------------------------------------------------------------------------------------------
Function Scan on generate_series t  (cost=0.00..260.00 rows=1000 width=32) (actual time=798.001..14348.384 rows=5000000 loops=1)
Planning Time: 0.036 ms                                                                                                         
Execution Time: 14506.170 ms                                                                                                    
explain (analyze)
select apply_tax_2(42.1)
from generate_series(1,5e6) t(i);
QUERY PLAN                                                                                                                      
--------------------------------------------------------------------------------------------------------------------------------
Function Scan on generate_series t  (cost=0.00..260.00 rows=1000 width=32) (actual time=790.721..10848.528 rows=5000000 loops=1)
Planning Time: 0.025 ms                                                                                                         
Execution Time: 11003.520 ms                                                                                                    
explain (analyze)
select apply_tax_3(42.1)
from generate_series(1,5e6) t(i);
QUERY PLAN                                                                                                                     
-------------------------------------------------------------------------------------------------------------------------------
Function Scan on generate_series t  (cost=0.00..260.00 rows=1000 width=32) (actual time=834.983..4651.848 rows=5000000 loops=1)
Planning Time: 0.035 ms                                                                                                        
Execution Time: 4747.803 ms                                                                                                    

As we can see, the “assignment” using a select statement is about 3 times slower then a direct assignment.