Question

I have the below error, this error only happens in the second execution cause the planning cache

ERROR: el tipo del parámetro 11 (character varying) no coincide aquel con que fue preparado el plan (text)
Where: PL/pgSQL function graficar(character varying,character varying,character varying) line 22 at asignación

It means that variables or values in the below function don't match character varying / text in line 22. There is no explicit text value defined, I suppose it happens in the concatenation of the query string:

CREATE OR REPLACE FUNCTION graficar(tabla character varying, columna character varying, valor character varying) RETURNS SETOF resultado As $$
    DECLARE
        r resultado;
        i record;
        query character varying = '';
        limite character varying = '';
    BEGIN
    IF columna <> '' THEN
       limite = ' where ' || columna || ' =  $1';
    END IF;

        IF tabla = 'edad' THEN
            query =   'WITH ranges AS ( SELECT (ten*10)::text ||''-''||(ten*10+9)::text AS range, ten*10 AS r_min, ten*10+9 AS r_max FROM generate_series(0,9) AS t(ten)) SELECT r.range as nombre, count(s.*) as cuenta FROM ranges r  LEFT JOIN ( select * from persona '|| limite ||' ) as s ON  s.edad BETWEEN r.r_min AND r.r_max GROUP BY r.range HAVING range NOT IN (''0-9'') ORDER BY r.range;';
        ELSEIF tabla = 'ingreso' THEN
           query =   'WITH ranges AS ( SELECT (ten*10)::text|| ''-''||((ten*10+199))::text || '' mil'' AS range, ten*10/1000 AS r_min, (ten*10+199999)/1000 AS r_max FROM generate_series(0,(SELECT max(ingreso)/10000 FROM persona), 20) AS t(ten)) SELECT r.range as nombre, count(s.*) as cuenta FROM ranges r  LEFT JOIN ( select * from persona '|| limite ||' ) as s ON s.ingreso BETWEEN r.r_min AND r.r_max GROUP BY r.range  ORDER BY r.range;';
        ELSE
            query = 'select p.nombre, count( e.* ) as cuenta from ' || tabla::regclass ||' p left join ( select * from persona ' || limite ||' ) as e on p.nombre = e.'|| tabla::regclass ||' group by p.nombre ';
    END IF;

    FOR i IN EXECUTE query USING valor LOOP  -- I DONT KNOW EXACTLY WHERE LINE 22 I SUPOSSE IS THIS
        r = (i.nombre, i.cuenta);
        RETURN NEXT r;
    END LOOP;
    END
$$ LANGUAGE plpgsql;
Was it helpful?

Solution

CREATE OR REPLACE FUNCTION graficar(tabla text, columna text, valor text)
  RETURNS SETOF resultado As
$func$
DECLARE
   _query text;
   limite text := '';
BEGIN
IF columna <> '' THEN
   limite := format(' AND %I = %L', columna, valor); -- properly escaped
END IF;

_query :=
   CASE tabla
   WHEN 'edad' THEN
   $q$WITH ranges AS (
         SELECT concat(ten, '0-', ten, '9') AS range
               ,ten*10 AS r_min, ten*10+9 AS r_max
         FROM   generate_series(1,9) ten)
      SELECT r.range AS nombre, count(p.*)::int AS cuenta
      FROM   ranges       r
      LEFT   JOIN persona p ON p.edad BETWEEN r.r_min AND r.r_max$q$
      || limite || '
      GROUP  BY r.range
      ORDER  BY r.range'

   WHEN 'ingreso' THEN
   $q$WITH ranges AS (
         SELECT concat(ten, '0-', ten*10 + 199, ' mil') AS range
               ,ten*10/1000 AS r_min, (ten*10+199999)/1000 AS r_max
         FROM   generate_series(0,(SELECT max(ingreso)/10000 FROM persona)
                                                             , 20) AS ten)
      SELECT r.range AS nombre, count(p.*)::int AS cuenta
      FROM   ranges r
      LEFT   JOIN persona p ON p.ingreso BETWEEN r.r_min AND r.r_max$q$
      || limite || '
      GROUP  BY r.range
      ORDER  BY r.range'

   ELSE
      format(
   $q$SELECT t.nombre, count(p.*)::int AS cuenta
      FROM   %1$I t
      LEFT   JOIN persona p on p.%1$I = t.nombre$q$ || limite || '
      GROUP  BY t.nombre'
      , tabla)
   END;

RETURN QUERY EXECUTE _query;

END
$func$ LANGUAGE plpgsql;

Major points:

  • Using text in place of character varying for simplicity.

  • Using concat() for easier formatting. Requires Postgres 9.1+.

  • Use a human-readable format! The strings you posted can hardly be maintained.

  • Generating numbers starting from 1 for the first case, since you exclude the case for 0 at the end anyway. Consequently, trim the now redundant HAVING clause.

  • The assignment operator in plpgsql is := not = - which generally works, but is an undocumented feature that may go away in future versions.

  • Make proper use of dollar quotes.

  • Use a simple RETURN QUERY in place of the whole LOOP construct at the end.

  • Don't use query as variable name, it is a reserved word in plpgsql. Substituted _query instead.

  • To avoid a potential type mismatch as described by @Daniel, provide valor as string literal in the query. This is a rare exception to the rule! Normally, the superior approach is to pass values with the USING clause like you had it. But to provide for a range of potentially varying types, your best choice is to supply an untyped string literal that can be coerced to any type automatically. This way, the expression remains sargable and any index potentially existing for the column can be used.

  • Avoid SQL injection by properly escaping all identifiers and strings. I am using format() mostly. Requires Postgres 9.1+. Details in this related answer on dba.SE.

OTHER TIPS

Since EXECUTE is supposed to re-plan each time it's run, the error shouldn't come from a plan cache problem.

So aside from that, there seems to be two potential errors in this function:

1) when columna<>'' is false, there is no parameter in the dynamic query, yet the EXECUTE tries to submit a value with USING valor.

2) when columna<>'' is true, it's comparing this column with a value of type character varying even if the column is of a type that can't be implicitly compared to this value. Presumably this would need an explicit cast to text:

limite = ' where ' || columna || '::text =  $1';

and have the valor parameter be of type text (or keep the character varying but use the CAST syntax).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top