PostgreSQL参数化表函数中的Order By/Limit

Jos*_*shi 16 sql database postgresql dynamic-sql plpgsql

我有一个sql函数,它执行一个简单的sql select语句:

CREATE OR REPLACE FUNCTION getStuff(param character varying)
  RETURNS SETOF stuff AS
$BODY$
    select *
    from stuff
    where col = $1
$BODY$
  LANGUAGE sql;
Run Code Online (Sandbox Code Playgroud)

现在我调用这个函数是这样的:

select * from getStuff('hello');
Run Code Online (Sandbox Code Playgroud)

如果我需要订购并限制结果order bylimit条款,我有哪些选择?

我想这样的查询:

select * from getStuff('hello') order by col2 limit 100;
Run Code Online (Sandbox Code Playgroud)

效率不高,因为表中的所有行都stuff将由函数返回,getStuff然后按限制排序和切片.

但即使我是对的,如何通过sql语言函数的参数传递顺序也没有简单的方法.只能传递值,而不能传递sql语句的一部分.

另一种选择是用plpgsql语言创建函数,在那里可以构造查询并通过它来执行EXECUTE.但这也不是一个非常好的方法.

那么,有没有其他方法可以实现这一目标?或者你会选择什么选择?在函数外部订购/限制,还是plpgsql?

我正在使用postgresql 9.1.

编辑

我修改了CREATE FUNCTION语句,如下所示:

CREATE OR REPLACE FUNCTION getStuff(param character varying, orderby character varying)
  RETURNS SETOF stuff AS
$BODY$
    select t.*
    from stuff t
    where col = $1
    ORDER BY
        CASE WHEN $2 = 'parent' THEN t.parent END,
        CASE WHEN $2 = 'type' THEN t."type" END, 
        CASE WHEN $2 = 'title' THEN t.title END

$BODY$
  LANGUAGE sql;
Run Code Online (Sandbox Code Playgroud)

抛出:

错误:CASE类型字符变化,整数不能匹配ŘÁDKA13:WHEN $ 1 ='parent'那么t.parent

stuff表如下所示:

CREATE TABLE stuff
    (
      id integer serial,
      "type" integer NOT NULL,
      parent integer,
      title character varying(100) NOT NULL,
      description text,
      CONSTRAINT "pkId" PRIMARY KEY (id),
    )
Run Code Online (Sandbox Code Playgroud)

EDIT2

我读错了Dems代码.我已经纠正了它的问题.这段代码对我有用.

Erw*_*ter 27

plpgsql函数没有任何问题.对于任何更复杂的东西来说,它是最优雅,最快速的解决方案.性能可能受到影响的唯一情况是嵌套plpgsql函数时,因为查询规划器无法在外部查询的上下文中进一步优化所包含的代码,这可能会或可能不会使其变慢.
后面的答案中有更多细节:

在这种情况下,它比CASE查询中的许多子句简单得多:

CREATE OR REPLACE FUNCTION get_stuff(_param text, _orderby text, _limit int)
  RETURNS SETOF stuff AS
$func$
BEGIN
   RETURN QUERY EXECUTE '
      SELECT *
      FROM   stuff
      WHERE  col = $1
      ORDER  BY ' || quote_ident(_orderby) || ' ASC
      LIMIT  $2'
   USING _param, _limit;
END
$func$  LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)

呼叫:

SELECT * FROM get_stuff('hello', 'col2', 100);
Run Code Online (Sandbox Code Playgroud)

笔记

  • 用于RETURN QUERY EXECUTE一次性返回查询结果.
  • 使用quote_ident()标识符,以防止SQLI.或者format()更复杂的东西.有关:
  • 使用USING子句输入参数值以避免再次使用,引用和SQLi.
  • 注意不要在参数和列名之间创建命名冲突.我_在示例中使用下划线()作为参数名称的前缀.只是我个人的偏好.

编辑后的第二个函数无法工作,因为您只parent在声明返回类型时返回SETOF stuff.您可以声明任何您喜欢的返回类型,但实际返回值必须与声明匹配.您可能想要使用RETURNS TABLE它.


小智 5

如果您的函数稳定(不修改数据库),查询规划器通常会内联它。因此,这样做SELECT * FROM getStuff('x') LIMIT 10将产生与限制在内部相同的查询计划getStuff()

但是,您需要通过如下声明来告诉 PG 您的函数是稳定的:

CREATE OR REPLACE FUNCTION getStuff(param varchar)
RETURNS setof STUFF
LANGUAGE SQL
STABLE
AS $$ ... $$;
Run Code Online (Sandbox Code Playgroud)

现在这样做EXPLAIN SELECT * FROM getStuff('x') LIMIT 1应该会产生与编写等效查询相同的查询计划。

内联也应该适用于ORDER BY函数外部的子句。但是如果你想参数化函数来确定排序依据,你可以这样做来控制排序方向:

CREATE FUNCTION sort_stuff(sort_col TEXT, sort_dir TEXT DEFAULT 'asc')
RETURNS SETOF stuff
LANGUAGE SQL
STABLE
AS $$
    SELECT *
    FROM stuff
    ORDER BY
      -- Simplified to NULL if not sorting in ascending order.
      CASE WHEN sort_dir = 'asc' THEN
          CASE sort_col
              -- Check for each possible value of sort_col.
              WHEN 'col1' THEN col1
              WHEN 'col2' THEN col2
              WHEN 'col3' THEN col3
              --- etc.
              ELSE NULL
          END
      ELSE
          NULL
      END
      ASC,

      -- Same as before, but for sort_dir = 'desc'
      CASE WHEN sort_dir = 'desc' THEN
          CASE sort_col
              WHEN 'col1' THEN col1
              WHEN 'col2' THEN col2
              WHEN 'col3' THEN col3
              ELSE NULL
          END
      ELSE
          NULL
      END
      DESC
$$;
Run Code Online (Sandbox Code Playgroud)

只要 和sort_colsort_dir查询中保持不变,查询规划器就应该能够将冗长的查询简化为

SELECT *
FROM stuff
ORDER BY <sort_col> <sort_dir>
Run Code Online (Sandbox Code Playgroud)

您可以使用 进行验证EXPLAIN