7

In PostgreSQL exists some ways to make a statement using bulk collect into like in Oracle?

Example in Oracle:

create or replace procedure prc_tst_bulk_test is

type typ_person is table of tb_person%rowtype;
v_tb_person typ_person;

begin

select *
bulk collect into v_tb_person
from tb_person;

-- make a selection in v_tb_person, for instance    
select name, count(*) from v_tb_person where age > 50
union 
select name, count(*) from v_tb_person where gender = 1

end;
3
  • 1
    What does "bulk collect into" do? I'm not familiar with that syntax at all. Commented Apr 29, 2013 at 22:30
  • Looks like it's a performance optimisation hint. dba-oracle.com/t_oracle_bulk_collect.htm . It seems to fetch blocks of rows into a temporary in-memory table. Commented Apr 30, 2013 at 0:01
  • Similar question coming from the opposite direction: stackoverflow.com/q/8109475/939860 Commented Dec 11, 2022 at 2:04

4 Answers 4

12

In PostgreSQL 10 you can use array_agg:

declare
    v_ids int[];   
begin
    select array_agg(id) INTO v_ids
      from mytable1
     where host = p_host;

    --use v_ids...

end;

You'll have array and it can be used to make select from it using unnest:

select * from unnest(v_ids) where ...
Sign up to request clarification or add additional context in comments.

1 Comment

For the record: array_agg() has been in Postgres since version 8.4. And before that, you could use an ARRAY constructor - like you still can today. See: dba.stackexchange.com/a/159717/3684
6

There is no such syntax in PostgreSQL, nor a close functional equivalent.

You can create a temporary table in your PL/PgSQL code and use that for the desired purpose. Temp tables in PL/PgSQL are a little bit annoying because the names are global within the session, but they work correctly in PostgreSQL 8.4 and up.

A better alternative for when you're doing all the work within a single SQL statement is to use a common table expression (CTE, or WITH query). This won't be suitable for all situations.

The example above would be much better solved by a simple RETURN QUERY in PL/PgSQL, but I presume your real examples are more complex.

Assuming that tb_person is some kind of expensive-to-generate view that you don't just want to scan in each branch of the union, you could do something like:

CREATE OR REPLACE FUNCTION prc_tst_bulk()
RETURNS TABLE (name text, rowcount integer) AS 
$$
BEGIN
    RETURN QUERY
    WITH v_tb_person AS (SELECT * FROM tb_person)
    select name, count(*) from v_tb_person where age > 50
    union 
    select name, count(*) from v_tb_person where gender = 1;
END;
$$ LANGUAGE plpgsql;

This particular case can be further simplified into a plain SQL function:

CREATE OR REPLACE FUNCTION prc_tst_bulk()
RETURNS TABLE (name text, rowcount integer) AS 
$$
    WITH v_tb_person AS (SELECT * FROM tb_person)
    select name, count(*) from v_tb_person where age > 50
    union 
    select name, count(*) from v_tb_person where gender = 1;
$$ LANGUAGE sql;

2 Comments

Craig Ringer, thanks for your reply. Thats is all need. Is it possible to change (select * from tb_person) for a dinamic statement like (execute sql_command). I tried it but with no success.
@Leandro Yes, in PL/PgSQL only. Use RETURN QUERY EXECUTE. If you continue to have issues post a new detailed question with the full text of your code the exact error messages, etc and link back to this one for context.
3

You can use a PostgreSQL arrays too - it is similar to Oracle's collections:

postgres=# create table _foo(a int, b int);
CREATE TABLE
postgres=# insert into _foo values(10,20);
INSERT 0 1

postgres=# create or replace function multiply()
returns setof _foo as $$
/*
 * two tricks are here
 * table name can be used as type name
 * table name can be used as fictive column that packs all fields
 */
declare a _foo[] = (select array(select _foo from _foo));
begin
  return query select * from unnest(a) 
           union
           all select * from unnest(a);
end;
$$ language plpgsql;

CREATE FUNCTION
postgres=# select * from multiply();
 a  | b  
----+----
 10 | 20
 10 | 20
(2 rows)

But in your case Craig Ringer's proposal is perfect and should be preferable.

Comments

0
-- Fetch the next 5 rows in the cursor_01:
FETCH FORWARD 5 FROM cursor_01;

PostgreSQL 10+ works.

https://www.postgresql.org/docs/10/sql-fetch.html

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.