如何编写包含返回结果集的动态构建的SQL语句的存储过程?这是我的示例代码:
CREATE OR REPLACE FUNCTION reporting.report_get_countries_new (
starts_with varchar,
ends_with varchar
)
RETURNS TABLE (
country_id integer,
country_name varchar
) AS
$body$
DECLARE
starts_with ALIAS FOR $1;
ends_with ALIAS FOR $2;
sql VARCHAR;
BEGIN
sql = 'SELECT * FROM lookups.countries WHERE lookups.countries.country_name >= ' || starts_with ;
IF ends_with IS NOT NULL THEN
sql = sql || ' AND lookups.countries.country_name <= ' || ends_with ;
END IF;
RETURN QUERY EXECUTE sql;
END;
$body$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
COST 100 ROWS 1000;
此代码返回错误:
ERROR: syntax error at or near "RETURN"
LINE 1: RETURN QUERY SELECT * FROM omnipay_lookups.countries WHERE o...
^
QUERY: RETURN QUERY SELECT * FROM omnipay_lookups.countries WHERE omnipay_lookups.countries.country_name >= r
CONTEXT: PL/pgSQL function "report_get_countries_new" line 14 at EXECUTE statement
我尝试过其他方式而不是:
RETURN QUERY EXECUTE sql;
方式1:
RETURN EXECUTE sql;
方式2:
sql = 'RETURN QUERY SELECT * FROM....
/*later*/
EXECUTE sql;
在所有情况下都没有成功。
最终我想编写一个包含动态sql语句的存储过程,并从动态sql语句返回结果集。
答案 0 :(得分:28)
还有改进的余地:
CREATE OR REPLACE FUNCTION report_get_countries_new (starts_with text
, ends_with text = NULL)
RETURNS SETOF lookups.countries AS
$func$
DECLARE
sql text := 'SELECT * FROM lookups.countries WHERE country_name >= $1';
BEGIN
IF ends_with IS NOT NULL THEN
sql := sql || ' AND country_name <= $2';
END IF;
RETURN QUERY EXECUTE sql
USING starts_with, ends_with;
END
$func$ LANGUAGE plpgsql;
-- the rest is default settings
PostgreSQL 8.4为USING
引入了 EXECUTE
子句,这有几个原因很有用。 Recap in the manual:
命令字符串可以使用参数中的参数值 命令为
$1, $2
等。这些符号表示提供的值USING
条款。该方法通常优于插入数据 将值作为文本输入到命令字符串中:它避免了运行时开销 将值转换为文本和返回,并且它更不容易发生 SQL注入攻击,因为不需要引用或转义。
IOW,即使用quote_literal()
进行消毒,它也比使用参数的文本表示构建查询字符串更安全,更快。
请注意,查询字符串中的$1, $2
引用USING
子句中提供的值,而不是函数参数。
当您返回SELECT * FROM lookups.countries
时,您可以简化 RETURN
声明,如下所示:
RETURNS SETOF lookups.countries
在PostgreSQL中,有一个自动为每个表定义的复合类型。用它。结果是函数取决于类型,如果您尝试更改表,则会收到错误消息。掉落&amp;在这种情况下重新创建函数。
这可能是也可能不是 - 这通常是!如果改变表格,您希望了解副作用。你拥有它的方式,你的功能会默默地破坏并在下次调用时引发异常。
如果您为声明中的第二个参数提供了explicit default,那么您可以(但不必)简化调用,以防您不想设置上限与ends_with
。
SELECT * FROM report_get_countries_new('Zaire');
而不是:
SELECT * FROM report_get_countries_new('Zaire', NULL);
在此背景下注意function overloading。
即使可以容忍(目前),也不要引用语言名称
。这是一个标识符。'plpgsql'
您可以在声明时指定变量。节省了额外的一步。
参数在标题中命名。删除无意义的行:
starts_with ALIAS FOR $1;
ends_with ALIAS FOR $2;
答案 1 :(得分:6)
使用quote_literal()来避免SQL injection(!!!)并修复引用问题:
CREATE OR REPLACE FUNCTION report_get_countries_new (
starts_with varchar,
ends_with varchar
)
RETURNS TABLE (
country_id integer,
country_name varchar
) AS
$body$
DECLARE
starts_with ALIAS FOR $1;
ends_with ALIAS FOR $2;
sql VARCHAR;
BEGIN
sql := 'SELECT * FROM lookups.countries WHERE lookups.countries.country_name ' || quote_literal(starts_with) ;
IF ends_with IS NOT NULL THEN
sql := sql || ' AND lookups.countries.country_name <= ' || quote_literal(ends_with) ;
END IF;
RETURN QUERY EXECUTE sql;
END;
$body$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
COST 100 ROWS 1000;
这是在9.1版中测试的,工作正常。