我有一个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 by
和limit
条款订购和限制结果,我有哪些选择?
我想这样的查询:
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),
)
我读错了Dems代码。我已经纠正了它的问题。这段代码对我有用。
答案 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_col
和sort_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;