HiveBrain v1.2.0
Get Started
← Back to all entries
patternsqlMinor

PostgreSQL: Using schema and table names from other tables?

Submitted by: @import:stackexchange-dba··
0
Viewed 0 times
postgresqltablesothernamesschemausingandfromtable

Problem

Consider the situation where I have all my schema names in one table and all my table names in another table.

Is it possible to do something like in the following (pseudo-code)?

SELECT value FROM (SELECT schema_name FROM schemas).(SELECT table_name FROM tables)


Or do I have to break up the query into three SELECT's?

Solution

Jack has demonstrated the way to go. However, I feel there is room for improvement.

Test setup:

-- meta tables for schema and table name    
CREATE TABLE schma(schma_id int, schma text);
INSERT INTO  schma VALUES (1, 'x');

CREATE TABLE tbl(tbl_id int, tbl text);
INSERT INTO  tbl VALUES (1, 't1'), (2, 't2');

-- dummy tables to be used in example query:
CREATE TABLE t1(id int);
INSERT INTO  t1 VALUES (1),(2);

CREATE TABLE t2(foo text);
INSERT INTO  t2 VALUES ('some text'), ('some more text');


Function updated with format() in 2017; formatted in 2021:

CREATE OR REPLACE FUNCTION f_dynaquery(_schma_id int
                                     , _tbl_id int
                                     , _col text
                                     , _type anyelement)
  RETURNS TABLE(col anyelement)
  LANGUAGE plpgsql AS
$func$
BEGIN
   RETURN QUERY EXECUTE format(
      'SELECT %I FROM %I.%I'
    , _col
    , (SELECT schma FROM schma WHERE schma_id = _schma_id)
    , (SELECT tbl   FROM tbl   WHERE tbl_id   = _tbl_id)
   );
END
$func$;

COMMENT ON FUNCTION f_dynaquery(int, int, text, anyelement)
IS 'Query any column from a dynamically assembled tablename.
$1 .. id of schema
$2 .. id of table
$3 .. name of column
$4 .. type of column (only data type matters, not the value)';


db<>fiddle here

Call:

SELECT col FROM f_dynaquery(1, 1, 'id', NULL::int);
  col
-----
   1
   2

SELECT col FROM f_dynaquery(1, 2, 'foo', NULL::text);
  col
----------------
 some text
 some more text


The function can return any column of any type.

Read the manual about Polymorphic Types and Declaring Function Parameters.

Use format() to defend against SQL injection. See:

  • PL/pgSQL regclass quoting of table named like keyword



Retrieve the table name by query as requested.

Name the returned column, so it is easier to reference.
Original answer:

CREATE OR REPLACE FUNCTION f_dynaquery_old(int, int, _col text, _type anyelement, OUT col anyelement)
  RETURNS SETOF anyelement
  LANGUAGE plpgsql AS
$func$
BEGIN
   RETURN QUERY EXECUTE '
   SELECT ' || quote_ident(_col) || '
   FROM   ' || (
       (SELECT schma FROM schma WHERE schma_id = $1) || '.' ||
       (SELECT tbl   FROM tbl   WHERE tbl_id   = $2))::regclass;
END
$func$;


Defend against SQL injection by using quote_ident() and casting to regclass.

Code Snippets

-- meta tables for schema and table name    
CREATE TABLE schma(schma_id int, schma text);
INSERT INTO  schma VALUES (1, 'x');

CREATE TABLE tbl(tbl_id int, tbl text);
INSERT INTO  tbl VALUES (1, 't1'), (2, 't2');

-- dummy tables to be used in example query:
CREATE TABLE t1(id int);
INSERT INTO  t1 VALUES (1),(2);

CREATE TABLE t2(foo text);
INSERT INTO  t2 VALUES ('some text'), ('some more text');
CREATE OR REPLACE FUNCTION f_dynaquery(_schma_id int
                                     , _tbl_id int
                                     , _col text
                                     , _type anyelement)
  RETURNS TABLE(col anyelement)
  LANGUAGE plpgsql AS
$func$
BEGIN
   RETURN QUERY EXECUTE format(
      'SELECT %I FROM %I.%I'
    , _col
    , (SELECT schma FROM schma WHERE schma_id = _schma_id)
    , (SELECT tbl   FROM tbl   WHERE tbl_id   = _tbl_id)
   );
END
$func$;

COMMENT ON FUNCTION f_dynaquery(int, int, text, anyelement)
IS 'Query any column from a dynamically assembled tablename.
$1 .. id of schema
$2 .. id of table
$3 .. name of column
$4 .. type of column (only data type matters, not the value)';
SELECT col FROM f_dynaquery(1, 1, 'id', NULL::int);
  col
-----
   1
   2

SELECT col FROM f_dynaquery(1, 2, 'foo', NULL::text);
  col
----------------
 some text
 some more text
CREATE OR REPLACE FUNCTION f_dynaquery_old(int, int, _col text, _type anyelement, OUT col anyelement)
  RETURNS SETOF anyelement
  LANGUAGE plpgsql AS
$func$
BEGIN
   RETURN QUERY EXECUTE '
   SELECT ' || quote_ident(_col) || '
   FROM   ' || (
       (SELECT schma FROM schma WHERE schma_id = $1) || '.' ||
       (SELECT tbl   FROM tbl   WHERE tbl_id   = $2))::regclass;
END
$func$;

Context

StackExchange Database Administrators Q#4721, answer score: 8

Revisions (0)

No revisions yet.