Search code examples
postgresqlplpgsqldynamic-sqlquoting

Error when calling a plpgsql function


Following is my function created in PostgreSQL 9.2

CREATE OR REPLACE function fn_exp_settl_cr
(branchid int
 ,vrid int
 ,ddate date
 ,acid int
) 
returns 
TABLE ( trno character varying(15), trdate timestamp without time zone, netamt money, duedate 
        timestamp without time zone,tranid integer, acyrid integer, custrefno  
        charactervarying(26), orderdate timestamp without time zone) as $$
declare 
sql text;
begin
execute 'select string_agg(s.qry,'' UNION ALL '')  from (select ''SELECT 
         TrNo,TrDate,NetAmt,DueDate,TranId,AcYrId,custrefno,orderdate FROM '||t.relname||' WHERE 
         AdjInBill = false And  ExpSettled = false And (BranchID = 1) AND (VrId = 29) and   
         DueDate <= ''2014-07-24'' and AcId=76 '' qry from (select relname from 
         pg_stat_user_tables where relname ilike ''gtab09%'') t )s ' into sql;
execute sql;
end;
$$ language plpgsql

It creates successfully but when calling this function, i.e.:

select * from fn_exp_settl_cr(1,2,'2014-07-24',4)

I get the following error:

ERROR: missing FROM-clause entry for table "t" LINE 1: ...DueDate,TranId,AcYrId,custrefno,orderdate FROM '||t.relname|...


Solution

  • CREATE OR REPLACE function fn_exp_settl_cr(_branchid int, _vrid int
                                             , _ddate date, _acid int)
      RETURNS TABLE (
       trno character varying(15)
     , trdate timestamp
     , netamt money
     , duedate timestamp
     , tranid integer
     , acyrid integer
     , custrefno  character varying(26)
     , orderdate timestamp
    ) AS
    $func$
    BEGIN
    
    RETURN QUERY EXECUTE (
       SELECT string_agg
                (format
                   ($$SELECT TrNo,TrDate,NetAmt,DueDate,TranId,AcYrId
                            ,custrefno,orderdate
                      FROM   %I
                      WHERE  AdjInBill = false
                      AND    ExpSettled = false
                      AND    BranchID = $1
                      AND    VrId = $2
                      AND    DueDate <= $3
                      AND    AcId = $4
                    $$
                   ,t.relname
                   )
                ,' UNION ALL '
                )
       FROM   pg_class t
       WHERE  t.relkind = 'r' -- ordinary table
       AND    t.relname ILIKE 'gtab09%'
       )
    USING _branchid, _vrid, _ddate, _acid;
    
    END
    $func$ LANGUAGE plpgsql;
    

    Major points

    • Your format is hardly readable, which is probably the main reason you can't find the problem.

    • Typo in charactervarying(26). Fix: character varying(26). I would just use varchar or text, though.

    • You got confused with multiple layers of quoting. The fix is to use dollar-quoting and format() to build your string. Also, one or two layers can just be simplified away.

    • You are not actually using your input parameters. Probably because you got stuck and are testing with literal values. While being at it, I built those in, too, with a USING clause.

    • I suggest not to use the same identifiers as parameter names. Those are visible in the function body everywhere and easily lead to naming conflicts. Prepended with '_'.

    • You didn't actually return anything. Use RETURN QUERY EXECUTE instead of just EXECUTE for that end.

    And why retrieve table names from the statistics view pg_stat_user_tables? It's cheaper and more reliable to get it from the information schema or the catalog table pg_class. Changed that, too.