0

我有以下错误,这个错误只发生在第二次执行导致计划缓存

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

character varying这意味着以下函数中的变量或值与第 22 行中的/不匹配text。没有明确定义文本值,我想它发生在查询字符串的串联中:

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;
4

2 回答 2

1

由于EXECUTE应该在每次运行时重新计划,因此错误不应该来自计划缓存问题。

所以除此之外,这个函数似乎有两个潜在的错误:

1) whencolumna<>''为 false,动态查询中没有参数,但EXECUTE尝试用USING valor.

2) 当columna<>''为真时,character varying即使该列的类型不能与该值进行隐式比较,它也会将该列与类型值进行比较。大概这需要对文本进行显式转换:

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

并具有valor参数类型text(或保留character varying但使用CAST语法)。

于 2013-09-23T18:41:18.870 回答
1
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;

要点:

  • 为了简单起见,使用text代替character varying

  • 用于concat()更轻松的格式化。需要 Postgres 9.1+。

  • 使用人类可读的格式!您发布的字符串几乎无法维护。

  • 1第一种情况开始生成数字,因为无论如何你都排除了这种情况0。因此,修剪现在多余的HAVING子句。

  • plpgsql 中的赋值运算符:=不是=- 通常有效,但它是一个未记录的功能,可能会在未来版本中消失。

  • 正确使用美元报价。

  • 最后使用 simpleRETURN QUERY代替整个LOOP构造。

  • 不要query用作变量名,它是 plpgsql 中的保留字。代替_query了。

  • 为避免@Daniel 描述的潜在类型不匹配,请在查询中提供字符串文字。这是规则的罕见例外!通常,更好的方法是使用子句传递值,就像你有它一样。但是为了提供一系列可能变化的类型,您最好的选择是提供可以自动强制转换为任何类型的无类型字符串文字。这样,表达式仍然是sargable,并且可以使用该列可能存在的任何索引。valorUSING

  • 通过正确转义所有标识符和字符串来避免 SQL 注入。我format()主要使用。需要 Postgres 9.1+。dba.SE 上此相关答案中的详细信息。

于 2013-09-23T21:31:30.137 回答