【问题标题】:Dynamic ORDER BY and ASC / DESC in a plpgsql functionplpgsql 函数中的动态 ORDER BY 和 ASC / DESC
【发布时间】:2018-09-21 08:22:22
【问题描述】:

按照this link 中提到的方法,我想将ORDER BY 和排序顺序动态传递给函数。

ORDER BY 工作正常,但我无法通过排序顺序 (ASC / DESC)。

我现在拥有的:

CREATE OR REPLACE FUNCTION list(_limit integer,_offset integer,sort_by varchar(100), _order varchar(100),_category varchar(100))
  RETURNS TABLE(
     id INTEGER,
     name VARCHAR,
     clientname VARCHAR,
     totalcount BIGINT
  ) AS
$$
DECLARE
   empty text := '';
BEGIN
RETURN Query EXECUTE
'SELECT d.id,
d.name,
d.clientname,
 count(*) OVER() AS full_count FROM design_list as d 
    where ($5 = $6 Or d.category Ilike $5) 
        ORDER BY ' || quote_ident(sort_by) || ' LIMIT $1 offset $2'
USING _limit,_offset,sort_by, _order,_category, empty;
END;
$$  LANGUAGE plpgsql;

【问题讨论】:

  • 我一点也不明白。为什么在同一个语句中将串联与 USING/$ 混合?当你不使用$3$4 时,为什么要在USING 中放入5 个参数(排序_by 和_order,碰巧)?你订购了sort_by,根本不使用_order,并说订购有效而排序无效???

标签: postgresql sql-order-by plpgsql dynamic-sql


【解决方案1】:

我会这样做:

CREATE OR REPLACE FUNCTION list(
      _category varchar(100)
    , _limit int
    , _offset int
    , _order_by varchar(100)
    , _order_asc_desc text = 'ASC')  -- last param with default value
  RETURNS TABLE(id int, name varchar, clientname varchar, totalcount bigint)
  LANGUAGE plpgsql AS
$func$
DECLARE
   _empty text := '';
BEGIN
   -- Assert valid _order_asc_desc
   IF upper(_order_asc_desc) IN ('ASC', 'DESC', 'ASCENDING', 'DESCENDING') THEN
      -- proceed
   ELSE
      RAISE EXCEPTION 'Unexpected value for parameter _order_asc_desc.
                       Allowed: ASC, DESC, ASCENDING, DESCENDING. Default: ASC';
   END IF;
   
   RETURN QUERY EXECUTE format(
     'SELECT id, name, clientname, count(*) OVER() AS full_count
      FROM   design_list
      WHERE ($1 = $2 OR category ILIKE $1) 
      ORDER  BY %I %s
      LIMIT  %s
      OFFSET %s'
    , _order_by, _order_asc_desc, _limit, _offset)
   USING _category, _empty;
END
$func$;

核心功能:使用format() 安全优雅地连接您的查询字符串。相关:

ASC/DESC(或ASCENDING/DESCENDING)是固定关键词。我添加了一个手动检查 (IF ...),然后与一个简单的 %s 连接。这是断言合法输入的一种方式。为方便起见,我添加了意外输入的错误消息和参数默认值,因此如果调用中省略了最后一个参数,则函数默认为ASC。相关:

寻址Pavel's valid comment,我直接连接_limit_offset,所以查询已经用这些参数计划好了。

_limit_offsetinteger 参数,所以我们可以使用普通的%s 而不会有SQL 注入的危险。您可能希望在连接之前断言合理的值(排除负值和过高的值)...

其他注意事项:
  • 使用一致的命名约定。我在所有参数和变量前加上下划线_,而不仅仅是一些

  • EXECUTE 中不使用表限定,因为只涉及一个表并且EXECUTE 有其单独的范围。

  • 我重命名了一些参数以澄清。 _order_by 而不是 _sort_by_order_asc_desc 而不是 _order

【讨论】:

  • LIMIT、OFFSET 是优化的重要子句——我不确定通过 USING 子句传递这些参数是否会对执行计划质量产生负面影响。这是单个查询包装的经典示例 - 重要且众所周知的性能反模式。
  • @PavelStehule:好点。我也为此添加了一个替代方案。
【解决方案2】:

非动态sql解决方案。

CREATE OR REPLACE FUNCTION list(
...
in_use_asc boolean default false,
_order_by varchar(100)
..
)
..


CREATE TEMPORARY TABLE tempHolder ON COMMIT DROP AS
SELECT SELECT id, name, clientname, count(*) OVER() AS full_count
      FROM   design_list
      WHERE ($1 = $2 OR category ILIKE $1);

     IF in_use_asc = TRUE THEN
        RETURN QUERY SELECT * FROM tempHolder ORDER BY _order_by asc LIMIT {} OFFSET {};
    ELSE 
        RETURN QUERY SELECT * FROM tempHolder ORDER BY _order_by desc LIMIT {} OFFSET {};
    END IF;

不应该更慢,因为 SQL 无论如何都需要获取所有内容,因为 ORDER BY 加上您避免了动态 SQL。

【讨论】:

  • 我认为这行不通。在您的RETURN QUERY 中,它只会将ORDER BY 应用于传递给_order_by_ 的文字字符串值,这是一个常量值,因此将否定任何排序。我之所以提到这一点,是因为我试图找出同样的问题并尝试了与您所做的相同的事情!
猜你喜欢
  • 1970-01-01
  • 2020-03-09
  • 2020-02-19
  • 2019-06-09
  • 1970-01-01
  • 2015-09-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多