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

时间:2011-11-15 16:25:11

标签: 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;

现在我正在调用这个函数:

select * from getStuff('hello');

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

我想这样的查询:

select * from getStuff('hello') order by col2 limit 100;

效率不高,因为表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;

<击> 抛出:

<击>   

错误:CASE类型字符变化且整数无法匹配   ŘÁDKA13:当$ 1 ='父'时,那么显而易见   

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),
    )

EDIT2

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

4 个答案:

答案 0 :(得分:25)

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;

呼叫:

SELECT * FROM get_stuff('hello', 'col2', 100);

注释

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

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

答案 1 :(得分:2)

如果您的函数是stable(不修改数据库),查询计划程序通常会inline它。因此,执行SELECT * FROM getStuff('x') LIMIT 10将生成相同的查询计划,就好像限制在getStuff()内一样。

但是,你需要通过声明它来告诉PG你的功能是否稳定:

CREATE OR REPLACE FUNCTION getStuff(param varchar)
RETURNS setof STUFF
LANGUAGE SQL
STABLE
AS $$ ... $$;

现在执行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
$$;

只要sort_colsort_dir在查询中保持不变,查询规划器就应该能够将详细查询简化为

SELECT *
FROM stuff
ORDER BY <sort_col> <sort_dir>

您可以使用EXPLAIN进行验证。

答案 2 :(得分:1)

关于ORDER BY你可以尝试这样的事情:

SELECT
    <column list>
FROM
    Stuff
WHERE
    col1 = $1
ORDER BY
    CASE $2
        WHEN 'col1' THEN col1
        WHEN 'col2' THEN col2
        WHEN 'col3' THEN col3
        ELSE col1  -- Or whatever your default should be
    END

您可能必须进行一些数据类型转换,以便CASE结果中的所有数据类型都匹配。只需注意将数字转换为字符串 - 您必须预先添加0才能使它们正确排序。日期/时间值也是如此。按照年份后跟月份后跟日期等格式订购

我在SQL Server中完成了这项工作,但从未在PostgreSQL中完成此操作,并且我在这台计算机上没有PostgreSQL的副本,所以这是未经测试的。

答案 3 :(得分:0)

您可以将限制值作为函数参数传递而不会出现任何问题。至于订购,您可以将ODER BY与CASE语句结合使用。不幸的是,这不适用于像

这样的东西
ORDER BY CASE condition_variable
WHEN 'asc' THEN column_name ASC
ELSE column_name DESC
END;