重构PL / pgSQL函数以返回各种SELECT查询的输出
我写了一个函数,输出一个PostgreSQL SELECT
查询很好地形成了文本forms。 现在我不想输出一个文本了,但实际上运行生成的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
types的。 -
type
:'myTable'
可以是四个表格之一的名称。 每个列都有不同的列,除了公共列Datahora
。
基础表的定义 。
variablessensors
将保存此处显示的相应表格中的所有列。 例如:如果type
是pcdmet
那么sensors
将是'datahora,dirvento,precipitacao,pressaoatm,radsolacum,tempar,umidrel,velvento'
这些variables用于构build存储在result
的SELECT
语句。 喜欢:
SELECT Datahora, column1, column2, column3 FROM myTable WHERE id=20 ORDER BY Datahora;
现在,我的函数返回这个语句作为text
。 我复制粘贴并执行它在pgAdmin或通过psql。 我想自动执行此操作,自动运行查询并返回结果。 我怎样才能做到这一点?
dynamicSQL和RETURN
types
(我最后保存了最好的,继续阅读!)
你想执行dynamicSQL 。 原则上,在EXECUTE
的帮助下,plpgsql很简单。 你不需要光标 – 事实上,大多数情况下你没有显式的光标就没有光标。
通过searchfindSO的例子 。
您遇到的问题:您要返回未定义types的logging 。 函数需要使用RETURNS
子句(或OUT
或INOUT
参数)声明返回types。 在你的情况下,你将不得不退回到匿名logging,因为数量 , 名称和返回列的types不同。 喜欢:
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
这样的结构化文档数据types:
- 如何在数据库中存储数据表(或List <KeyValuePair <int,Object >>或Dictionary)?
但为了这个问题的目的,让我们假设你想尽可能地返回个别的,正确的types和命名的列。
固定返回types的简单解决scheme
列datahora
似乎是一个给定的,我会假设数据types的timestamp
,并且总是有两个不同的名称和数据types的列。
名称,我们将放弃在返回types中的通用名称。
我们也将放弃types ,并将所有types转换为text
因为每个数据types都可以转换为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;
这个怎么用?
-
variables
_sensors
和_type
可以是input参数。 -
请注意
RETURNS TABLE
子句。 -
请注意使用
RETURN QUERY EXECUTE
。 这是从dynamic查询返回行的更优雅的方法之一。 -
我使用函数参数的名称,只是为了使
RETURN QUERY EXECUTE
的USING
子句更加容易混淆。 SQLstring中的$1
不引用函数参数,而是引用USING
子句传递的值。 (在这个简单的例子中,它们各自的范围都是$1
)。 -
请注意
_sensors
的示例值:将每个列转换为键入text
。 -
这种代码很容易被SQL注入 。 我使用
quote_ident()
来防止它。 将variables_sensors
的几个列名集合在一起可以防止使用quote_ident()
(通常是一个坏主意!)。 确保没有什么不好的东西可以用其他方式存在,比如通过quote_ident()
单独运行列名。 一个VARIADIC
参数想到…
使用PostgreSQL 9.1+更简单
使用版本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;
同样,单个列名可以被正确地转义,并且将是干净的方式。
可变数量的列共享相同的types
你的问题更新后,它看起来像你的返回types
- 可变数量的列
- 但相同types的所有列的
double precision
(别名为float8
)
因为我们必须定义一个函数的RETURN
types,所以在这种情况下我使用ARRAY
types,它可以包含可变数目的值。 此外,我返回一个列名称的数组,所以你也可以从结果中parsing出名字:
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;
各种完整的表格types
如果你实际上试图返回一个表的所有列 (例如链接页面中的一个表) ,那么使用这个简单的,非常强大的多态types的解决scheme:
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
中的pcdmet
replace为任何其他表名。
这个怎么用?
-
anyelement
是伪数据types,多态types,任何非数组数据types的占位符。 函数中任何anyelement
所有出现次数都与运行时提供的相同types相同。 通过为函数提供一个定义types的参数作为参数,我们隐含地定义了返回types。 -
PostgreSQL为每个创build的表自动定义一个行types(一个复合数据types),所以每个表都有一个定义好的types。 这包括临时表,便于临时使用。
-
任何types都可以是
NULL
。 所以我们提交一个NULL
值,转换为表types。 -
现在函数返回一个定义良好的行types,我们可以使用
SELECT * FROM data_of(...)
来分解行并获得单独的列。 -
pg_typeof(_tbl_type)
以对象标识符typesregtype
返回表的名字。 当自动转换为text
,如果需要,标识符将自动被双引号和模式限定 。 因此,SQL注入是不可能的。 这甚至可以处理模式限定的表名,其中quote_ident()
会失败 。
你可能会想要返回一个游标 。 尝试像这样(我没有尝试过):
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;
我很抱歉地说,但你的问题是非常不清楚的。 然而下面你会发现一个自包含的例子,如何创build和使用返回游标variables的函数。 希望它有帮助!
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;