Postgresql – How to return rows from multiple tables selected from information_schema

dynamic-sqlplpgsqlpostgresql

I need to return rows from a set of tables found in information_schema. I can find the table names, and I have a function that compiles and executes, but it returns an empty set, so there must be something wrong with my logic.

CREATE OR REPLACE FUNCTION get_prices() 
  RETURNS TABLE(price FLOAT) AS $func$
    DECLARE
      tn TEXT;
      BEGIN
        FOR tn in SELECT table_name 
          FROM information_schema.tables 
          WHERE table_name ~~ '%price%'  
          AND table_schema ~~ 'stg0' LOOP
            EXECUTE 'SELECT price FROM stg0.' || tn;
          END LOOP;
      END;
  $func$ LANGUAGE plpgsql;

When I execute the following

SELECT * FROM get_prices();

I get a table with no rows and a single column, price.

The SELECT in the FOR loop is returning all the needed table names. There are many rows in those tables in the price column. What should change to return those rows?

UPDATE: I should have made it more clear that I have a large, undetermined number of source tables for the query at compile time, so I was looking to query the information_schema at runtime in order to pull the source table names dynamically.

The given query works and actually returns data by replacing the EXECUTE line with

RETURN QUERY EXECUTE 'SELECT price FROM stg0.' || tn;

as suggested in the comments.

Best Answer

That's 0-fun. Just generate one statement using UNION ALL and now you don't need cursors.

CREATE TABLE foo_1(price) AS VALUES (1);
CREATE TABLE foo_2(price) AS VALUES (1);
CREATE TABLE foo_3(price) AS VALUES (1);

SELECT STRING_AGG(
  FORMAT(
    'SELECT price FROM %I.%I.%I',
    table_catalog,
    table_schema,
    table_name
  ), ' UNION ALL ') ||';'
FROM information_schema.tables
WHERE table_name like '%foo_%';

You can put it in a function like this (first we get the result as a query string, and then we execute it)

CREATE FUNCTION get_prices()
RETURNS setof int AS $func$
  DECLARE
    sql text := string_agg(
      FORMAT(
        'SELECT price::int FROM %I.%I.%I',
        table_catalog,
        table_schema,
        table_name
      ), ' UNION ALL ') ||';'
    FROM information_schema.tables
    WHERE table_name like '%foo_%';
  BEGIN
    RETURN QUERY EXECUTE sql;
  END;
$func$ LANGUAGE plpgsql;

As a special note, if the list of table_name ~~ '%price%' AND table_schema ~~ 'stg0' is immutable (that means you're not still adding to it) you're better of doing this one time and saving it as a VIEW