plpgsql 函数中的动态 ORDER BY 和 ASC / DESC
Dynamic ORDER BY and ASC / DESC in a plpgsql function
按照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 ( = Or d.category Ilike )
ORDER BY ' || quote_ident(sort_by) || ' LIMIT offset '
USING _limit,_offset,sort_by, _order,_category, empty;
END;
$$ LANGUAGE plpgsql;
我会这样做:
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 ( = OR category ILIKE )
ORDER BY %I %s
LIMIT %s
OFFSET %s'
, _order_by, _order_asc_desc, _limit, _offset)
USING _category, _empty;
END
$func$;
核心功能:使用 format()
安全优雅地连接您的查询字符串。相关:
- INSERT with dynamic table name in trigger function
- Format specifier for integer variables in format() for EXECUTE?
ASC
/DESC
(或ASCENDING
/DESCENDING
)为固定关键词。我添加了一个手动检查 (IF ...
),然后用一个简单的 %s
连接。这是断言合法输入的一种 方式。为了方便起见,我添加了意外输入的错误消息和参数默认值,因此如果在调用中省略最后一个参数,则函数默认为 ASC
。相关:
- Optional argument in PL/pgSQL function
- ERROR: input parameters after one with a default value must also have defaults in Postgres
寻址 ,我直接连接 _limit
和 _offset
,所以查询已经用这些参数计划好了。
_limit
和 _offset
是 integer
参数,所以我们可以使用普通的 %s
而没有 SQL 注入的危险。您可能希望在连接之前声明合理的值(排除负值和太高的值)...
其他注意事项:
使用一致的命名约定。我在所有参数和变量前加了下划线 _
,而不仅仅是 some.
不在 EXECUTE
中使用 table 限定,因为只涉及一个 table 并且 EXECUTE
有其单独的范围。
我重命名了一些参数以澄清。 _order_by
而不是 _sort_by
; _order_asc_desc
而不是 _order
.
非动态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 ( = OR category ILIKE );
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.
按照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 ( = Or d.category Ilike )
ORDER BY ' || quote_ident(sort_by) || ' LIMIT offset '
USING _limit,_offset,sort_by, _order,_category, empty;
END;
$$ LANGUAGE plpgsql;
我会这样做:
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 ( = OR category ILIKE )
ORDER BY %I %s
LIMIT %s
OFFSET %s'
, _order_by, _order_asc_desc, _limit, _offset)
USING _category, _empty;
END
$func$;
核心功能:使用 format()
安全优雅地连接您的查询字符串。相关:
- INSERT with dynamic table name in trigger function
- Format specifier for integer variables in format() for EXECUTE?
ASC
/DESC
(或ASCENDING
/DESCENDING
)为固定关键词。我添加了一个手动检查 (IF ...
),然后用一个简单的 %s
连接。这是断言合法输入的一种 方式。为了方便起见,我添加了意外输入的错误消息和参数默认值,因此如果在调用中省略最后一个参数,则函数默认为 ASC
。相关:
- Optional argument in PL/pgSQL function
- ERROR: input parameters after one with a default value must also have defaults in Postgres
寻址 _limit
和 _offset
,所以查询已经用这些参数计划好了。
_limit
和 _offset
是 integer
参数,所以我们可以使用普通的 %s
而没有 SQL 注入的危险。您可能希望在连接之前声明合理的值(排除负值和太高的值)...
使用一致的命名约定。我在所有参数和变量前加了下划线
_
,而不仅仅是 some.不在
EXECUTE
中使用 table 限定,因为只涉及一个 table 并且EXECUTE
有其单独的范围。我重命名了一些参数以澄清。
_order_by
而不是_sort_by
;_order_asc_desc
而不是_order
.
非动态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 ( = OR category ILIKE );
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.