2

I was trying to write a statement to check if an table contains rows:

SELECT COUNT(*) FROM $1 ;

If figured I would pass in the table name into: $1

I get the following error message:

syntax error at or near "$1"

What is wrong with my statement ?

Rahul Iyer
  • 19,924
  • 21
  • 96
  • 190

3 Answers3

0

You can get this information from the system catalog a lot cheaper and faster than querying the table itself.

CREATE OR REPLACE FUNCTION table_empty(tablename text, tableschema text)
RETURNS BOOLEAN AS $$
BEGIN
RETURN (SELECT CASE WHEN (reltuples::integer > 0) 
    THEN false 
    ELSE (SELECT count(*) = 0 
        FROM quote_ident(tableschema || '.' || tablename)  )
    END
        FROM pg_namespace nc 
        JOIN pg_class c 
        ON nc.oid = c.relnamespace 
        WHERE relname=tablename AND nc.nspname = tableschema);

END;
$$
LANGUAGE plpgsql;

SELECT table_empty('pg_class','pg_catalog');
table_empty
-----------
f 

1 row
Kirk Roybal
  • 17,273
  • 1
  • 29
  • 38
  • `reltuples` is only an **estimate** it might not be accurate. –  Oct 02 '17 at 07:21
  • True, but any number greater than 0 proves existence. The OP didn't ask how many rows are in the table, and the estimate will return at least a page worth of rows. – Kirk Roybal Oct 09 '17 at 18:27
  • dawned on me what @a_horse_with_no_name meant... modified for very small tables. – Kirk Roybal Oct 09 '17 at 20:26
0

The base driver does only the basic query formatting that's supported on the server level, which doesn't support dynamic table names.

This means the table name needs to be escaped on your side. You can either do this manually, or you can rely on a library that supports it, like the example below that uses pg-promise:

db.one('SELECT count(*) FROM $1:name', table, a => +a.count)
    .then(count => {
        // count = the integer record count
    })
    .catch(error => {
        // either table doesn't exist, or a connectivity issue
    });

Or, by using Named Parameters:

db.one('SELECT count(*) FROM ${table:name}', {table}, a => +a.count)
    .then(count => {
        // count = the integer record count
    })
    .catch(error => {
        // either table doesn't exist, or a connectivity issue    
    });

Filter :name tells the formatting engine to escape it as an SQL Name. This filter also has a shorter version ~, if you prefer, i.e. $1~ or ${table~} accordingly.

Also, we are using method one, because that query always expects one-row result back.

vitaly-t
  • 24,279
  • 15
  • 116
  • 138
0

you can't do it with prepared statement. use function as Kirk suggests. The only difference, maybe you are safer to select first row, like :

t=# create or replace function tempty(tn text) returns boolean as
$$
declare
 c int;
begin
  execute format('select 1 from %I limit 1',tn) into c;
  return NOT coalesce(c,0) > 0;
end;
$$ language plpgsql
;
CREATE FUNCTION
t=# create table empty(i int);
CREATE TABLE
t=# select tempty('empty');
 tempty
--------
 t
(1 row)

t=# select tempty('pg_class');
 tempty
--------
 f
(1 row)

docs do not say directly that values you pass to execute prepared statement can't be identifiers, yet everywhere they are mentionned in a way that identifier could not be, eg:

A generic plan assumes that each value supplied to EXECUTE is one of the column's distinct values and that column values are uniformly distributed.

($1 is a column value with or without some properties.)

Vao Tsun
  • 47,234
  • 13
  • 100
  • 132