13

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

方式一:

RETURN EXECUTE sql;

方式二:

sql = 'RETURN QUERY SELECT * FROM....
/*later*/
EXECUTE sql;

在所有情况下都没有成功。

最终,我想编写一个包含动态 sql 语句并从动态 sql 语句返回结果集的存储过程。

4

2 回答 2

36

有改进的余地:

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 引入了USINGfor 子句EXECUTE,这有几个原因。在手册中回顾

    命令字符串可以使用参数值,这些参数值在命令中引用为$1, $2等。这些符号指的是USING子句中提供的值。这种方法通常比将数据值作为文本插入到命令字符串中更可取:它避免了将值转换为文本并返回的运行时开销,并且由于不需要引用或逃脱。

    IOW,即使使用quote_literal().
    请注意,$1, $2在查询字符串中,指的是USING子句中提供的值,而不是函数参数。

  • 当您返回SELECT * FROM lookups.countries时,您可以简化RETURN声明,如下所示:

    RETURNS SETOF lookups.countries
    

    在 PostgreSQL 中,自动为每个表定义了一个复合类型。用它。结果是函数取决于类型,如果您尝试更改表,则会收到错误消息。在这种情况下删除并重新创建函数。

    这可能是可取的,也可能不是可取的——通常是这样!如果您更改表,您希望了解副作用。按照您的方式,您的函数将静默中断并在下一次调用时引发异常。

  • 如果您像演示的那样为声明中的第二个参数提供显式默认值,则可以(但不必)简化调用,以防您不想设置上限ends_with

    SELECT * FROM report_get_countries_new('Zaire');
    

    代替:

    SELECT * FROM report_get_countries_new('Zaire', NULL);
    

    请注意此上下文中的函数重载

  • 'plpgsql'即使可以容忍(目前),也不要引用语言名称。这是一个标识符。

  • 您可以在声明时分配一个变量。节省了额外的步骤。

  • 参数在标题中命名。删除无意义的行:

     starts_with ALIAS FOR $1;
     ends_with ALIAS FOR $2;
    
于 2012-08-21T01:17:58.707 回答
6

使用quote_literal()避免SQL 注入(!!!) 并修复您的引用问题:

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

于 2012-08-14T08:57:00.307 回答