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

pri*_*nce 9 postgresql resultset execute dynamic-sql plpgsql

如何编写包含动态生成的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;
Run Code Online (Sandbox Code Playgroud)

此代码返回错误:

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
Run Code Online (Sandbox Code Playgroud)

我尝试过其他方式而不是这个:

RETURN QUERY EXECUTE sql;
Run Code Online (Sandbox Code Playgroud)

方式1:

RETURN EXECUTE sql;
Run Code Online (Sandbox Code Playgroud)

方式2:

sql = 'RETURN QUERY SELECT * FROM....
/*later*/
EXECUTE sql;
Run Code Online (Sandbox Code Playgroud)

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

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

Erw*_*ter 29

有改进的余地:

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
Run Code Online (Sandbox Code Playgroud)

主要观点

  • PostgreSQL 8.4引入了该USING子句EXECUTE,由于几个原因这很有用.回顾一下手册:

    命令字符串可以使用参数值,这些参数值在命令中引用为$1, $2,等等.这些符号表示USING子句中提供的值.这种方法通常比将数据值作为文本插入命令字符串更好:它避免了将值转换为文本和返回的运行时开销,并且它不太容易受到SQL注入攻击,因为不需要引用或逃跑.

    IOW,它比使用参数的文本表示构建查询字符串更安全,更快,即使在使用时进行清理quote_literal().
    请注意,$1, $2在查询字符串中,请参阅USING子句中提供的值,而不是函数参数.

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

    RETURNS SETOF lookups.countries
    
    Run Code Online (Sandbox Code Playgroud)

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

    这可能是也可能不是 - 这通常是!如果改变表格,您希望了解副作用.你拥有它的方式,你的函数会默默地破坏并在下一次调用时引发异常.

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

    SELECT * FROM report_get_countries_new('Zaire');
    
    Run Code Online (Sandbox Code Playgroud)

    代替:

    SELECT * FROM report_get_countries_new('Zaire', NULL);
    
    Run Code Online (Sandbox Code Playgroud)

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

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

  • 您可以在声明时分配变量.节省额外的一步.

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

     starts_with ALIAS FOR $1;
     ends_with ALIAS FOR $2;
    
    Run Code Online (Sandbox Code Playgroud)


Fra*_*ens 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;
Run Code Online (Sandbox Code Playgroud)

这是在9.1版中测试的,工作正常.