我写了一个函数,输出一个以文本形式形成的PostgreSQL SELECT
查询。现在我不想再输出文本,但实际上对数据库运行生成的SELECT
语句并返回结果 - 就像查询本身一样。
CREATE OR REPLACE FUNCTION data_of(integer)
RETURNS text AS
$BODY$
DECLARE
sensors varchar(100); -- holds list of column names
type varchar(100); -- holds name of table
result text; -- holds SQL query
-- declare more variables
BEGIN
-- do some crazy stuff
result := 'SELECT\r\nDatahora,' || sensors ||
'\r\n\r\nFROM\r\n' || type ||
'\r\n\r\nWHERE\r\id=' || $1 ||'\r\n\r\nORDER BY Datahora;';
RETURN result;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
ALTER FUNCTION data_of(integer) OWNER TO postgres;
sensors
包含表type
的列名列表。这些是在功能过程中声明和填写的。最终,他们拥有如下价值观:
sensors
:'column1, column2, column3'
除Datahora
(timestamp
)外,所有列均为double precision
类型。
type
:'myTable'
可以是四个表之一的名称。每个列都有不同的列,但公共列Datahora
除外。
Definition of the underlying tables.
变量sensors
将为type
中的相应表格显示此处显示的所有列。例如:如果type
为pcdmet
,那么sensors
将为'datahora,dirvento,precipitacao,pressaoatm,radsolacum,tempar,umidrel,velvento'
变量用于构建存储在SELECT
中的result
语句。像:
SELECT Datahora, column1, column2, column3
FROM myTable
WHERE id=20
ORDER BY Datahora;
现在,我的函数将此语句作为text
返回。我复制粘贴并在pgAdmin或psql中执行它。我想自动执行此操作,自动运行查询并返回结果。我怎么能这样做?
答案 0 :(得分:69)
RETURN
类型 (我最后保存了最好的,继续阅读!)
您想要执行动态SQL 。原则上,在EXECUTE
的帮助下,这在plpgsql中很简单。你不需要一个光标 - 事实上,大多数情况下你没有明确的游标会更好。
Find examples on SO with a search
您遇到的问题:您希望返回尚未定义类型的记录。函数需要使用RETURNS
子句(或使用OUT
或INOUT
参数)声明返回类型。在您的情况下,您将不得不回退到匿名记录,因为返回列的数字,名称和类型会有所不同。像:
CREATE FUNCTION data_of(integer)
RETURNS SETOF record AS ...
然而,这不是特别有用。这样,您必须在每次调用函数时提供列定义列表。像:
SELECT * FROM data_of(17)
AS foo (colum_name1 integer
, colum_name2 text
, colum_name3 real);
但是,如果事先不知道这些列,你甚至会怎么做呢?
您可以使用结构较少的文档数据类型,例如json
,jsonb
,hstore
或xml
:
但是出于这个问题的目的,让我们假设你想尽可能多地返回单独的,正确输入和命名的列。
列datahora
似乎是给定的,我假设数据类型为timestamp
,并且总共还有两列具有不同的名称和数据类型。
名称我们将放弃支持返回类型中的通用名称
类型我们也将放弃,并将所有内容全部投放到text
,因为每个数据类型都可以转换为text
。
CREATE OR REPLACE FUNCTION data_of(_id integer)
RETURNS TABLE (datahora timestamp, col2 text, col3 text) AS
$func$
DECLARE
_sensors text := 'col1::text, col2::text'; -- cast each col to text
_type text := 'foo';
BEGIN
RETURN QUERY EXECUTE '
SELECT datahora, ' || _sensors || '
FROM ' || quote_ident(_type) || '
WHERE id = $1
ORDER BY datahora'
USING _id;
END
$func$ LANGUAGE plpgsql;
变量_sensors
和_type
可能是输入参数。
请注意RETURNS TABLE
子句。
请注意RETURN QUERY EXECUTE
的使用。这是从动态查询中返回行的更优雅的方法之一。
我为函数参数使用了一个名称,只是为了使USING
RETURN QUERY EXECUTE
子句更容易混淆。 SQL字符串中的$1
不是引用函数参数,而是引用USING
子句传递的值。 (在这个简单的例子中,两者都在$1
的各自范围内。)
请注意_sensors
的示例值:每列都会投放到text
类型。
此类代码非常容易受SQL injection攻击。我使用quote_ident()
来防范它。将变量_sensors
中的几个列名集中在一起可以防止使用quote_ident()
(通常是一个坏主意!)。确保不会有其他任何不良内容,例如通过quote_ident()
单独运行列名称。想到VARIADIC
参数......
使用9.1或更高版本,您可以使用format()
进一步简化:
RETURN QUERY EXECUTE format('
SELECT datahora, %s -- identifier passed as unescaped string
FROM %I -- assuming the name is provided by user
WHERE id = $1
ORDER BY datahora'
,_sensors, _type)
USING _id;
同样,单个列名称可以正确转义,并且是干净的方式。
在您的问题更新后,它看起来像您的返回类型
double precision
(别名float8
)由于我们必须定义函数的RETURN
类型,在这种情况下我采用ARRAY
类型,它可以包含可变数量的值。另外,我返回一个包含列名的数组,因此您也可以解析结果中的名称:
CREATE OR REPLACE FUNCTION data_of(_id integer)
RETURNS TABLE (datahora timestamp, names text[], values float8[] ) AS
$func$
DECLARE
_sensors text := 'col1, col2, col3'; -- plain list of column names
_type text := 'foo';
BEGIN
RETURN QUERY EXECUTE format('
SELECT datahora
, string_to_array($1) -- AS names
, ARRAY[%s] -- AS values
FROM %s
WHERE id = $2
ORDER BY datahora'
, _sensors, _type)
USING _sensors, _id;
END
$func$ LANGUAGE plpgsql;
如果您实际上尝试返回表格的所有列(例如tables at the linked page之一),请使用这个简单,非常强大的解决方案polymorphic type:
CREATE OR REPLACE FUNCTION data_of(_tbl_type anyelement, _id int)
RETURNS SETOF anyelement AS
$func$
BEGIN
RETURN QUERY EXECUTE format('
SELECT *
FROM %s -- pg_typeof returns regtype, quoted automatically
WHERE id = $1
ORDER BY datahora'
, pg_typeof(_tbl_type))
USING _id;
END
$func$ LANGUAGE plpgsql;
呼叫:
SELECT * FROM data_of(NULL::pcdmet, 17);
将呼叫中的pcdmet
替换为任何其他表名。
anyelement
是伪数据类型,多态类型,任何非数组数据类型的占位符。函数中出现的所有anyelement
都会计算为运行时提供的相同类型。通过提供定义类型的值作为函数的参数,我们隐式定义了返回类型。
PostgreSQL自动为每个创建的表定义行类型(复合数据类型),因此每个表都有一个定义良好的类型。这包括临时表,便于临时使用。
任何类型都可以是NULL
。所以我们提交NULL
值,转换为表格类型。
现在该函数返回一个定义良好的行类型,我们可以使用SELECT * FROM data_of(...)
来分解行并获取各列。
pg_typeof(_tbl_type)
将表格的名称作为object identifier type regtype
返回。自动转换为text
后,如果需要,标识符会自动双引号和架构限定。因此,SQL注入是不可能的。这甚至可以处理模式限定的表名where quote_ident()
would fail。
答案 1 :(得分:3)
您可能想要返回cursor。尝试这样的事情(我还没试过):
CREATE OR REPLACE FUNCTION data_of(integer)
RETURNS refcursor AS
$BODY$
DECLARE
--Declaring variables
ref refcursor;
BEGIN
-- make sure `sensors`, `type`, $1 variable has valid value
OPEN ref FOR 'SELECT Datahora,' || sensors ||
' FROM ' || type ||
' WHERE nomepcd=' || $1 ||' ORDER BY Datahora;';
RETURN ref;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
ALTER FUNCTION data_of(integer) OWNER TO postgres;
答案 2 :(得分:1)
我很遗憾地说,但你的问题很不清楚。但是下面你会找到一个自包含的例子,如何创建和使用一个返回游标变量的函数。希望它有所帮助!
begin;
create table test (id serial, data1 text, data2 text);
insert into test(data1, data2) values('one', 'un');
insert into test(data1, data2) values('two', 'deux');
insert into test(data1, data2) values('three', 'trois');
create function generate_query(query_name refcursor, columns text[])
returns refcursor
as $$
begin
open query_name for execute
'select id, ' || array_to_string(columns, ',') || ' from test order by id';
return query_name;
end;
$$ language plpgsql;
select generate_query('english', array['data1']);
fetch all in english;
select generate_query('french', array['data2']);
fetch all in french;
move absolute 0 from french; -- do it again !
fetch all in french;
select generate_query('all_langs', array['data1','data2']);
fetch all in all_langs;
-- this will raise in runtime as there is no data3 column in the test table
select generate_query('broken', array['data3']);
rollback;
答案 3 :(得分:0)
# copy paste me into bash shell directly
clear; IFS='' read -r -d '' sql_code << 'EOF_SQL_CODE'
CREATE OR REPLACE FUNCTION func_get_all_users_roles()
-- define the return type of the result set as table
-- those datatypes must match the ones in the src
RETURNS TABLE (
id bigint
, email varchar(200)
, password varchar(200)
, roles varchar(100)) AS
$func$
BEGIN
RETURN QUERY
-- start the select clause
SELECT users.id, users.email, users.password, roles.name as roles
FROM user_roles
LEFT JOIN roles ON (roles.guid = user_roles.roles_guid)
LEFT JOIN users ON (users.guid = user_roles.users_guid)
-- stop the select clause
;
END
$func$ LANGUAGE plpgsql;
EOF_SQL_CODE
# create the function
psql -d db_name -c "$sql_code";
# call the function
psql -d db_name -c "select * from func_get_all_users_roles() "