PostgreSQL - 在存储过程中编写动态sql,返回结果集

时间:2012-08-14 08:02:13

标签: postgresql resultset plpgsql execute dynamic-sql

如何编写包含返回结果集的动态构建的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语句返回结果集。

2 个答案:

答案 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版中测试的,工作正常。