我有以下错误,此错误仅在第二次执行时发生导致计划缓存
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
这意味着下面函数中的变量或值与第22行中的character varying
/ 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;
答案 0 :(得分:1)
由于EXECUTE
应该在每次运行时重新计划,因此错误不应来自计划缓存问题。
除此之外,这个函数似乎有两个潜在的错误:
1)当columna<>''
为false时,动态查询中没有参数,但EXECUTE
尝试使用USING valor
提交值。
2)当columna<>''
为真时,即使该列的类型无法与此值进行隐式比较,它也会将此列与类型character varying
的值进行比较。据推测,这需要明确转换为文本:
limite = ' where ' || columna || '::text = $1';
并让valor
参数的类型为text
(或保留character varying
,但使用CAST
语法。
答案 1 :(得分: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
子句。
assignment operator in plpgsql is :=
不是=
- 这通常有效,但是在将来的版本中可能会消失的未记录的功能。
正确使用美元报价。
最后使用简单的RETURN QUERY
代替整个LOOP
构造。
不要使用query
作为变量名,它是plpgsql中的保留字。取而代之的是_query
。
为了避免潜在的类型不匹配为described by @Daniel,请在查询中提供valor
作为字符串文字。这是规则的罕见例外!通常,优越的方法是使用USING
子句传递值,就像你拥有它一样。但是为了提供一系列可能不同的类型,您最好的选择是提供一个可以自动强制转换为任何类型的无类型字符串文字。这样,表达式仍为sargable,并且可以使用该列可能存在的任何索引。
通过正确转义所有标识符和字符串来避免SQL注入。我主要使用format()
。需要Postgres 9.1+。 Details in this related answer on dba.SE.