重构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'
    除了Datahoratimestamp ),所有的列都是double precisiontypes的。

  • type'myTable'
    可以是四个表格之一的名称。 每个列都有不同的列,除了公共列Datahora

基础表的定义

variablessensors将保存此处显示的相应表格中的所有列。 例如:如果typepcdmet那么sensors将是'datahora,dirvento,precipitacao,pressaoatm,radsolacum,tempar,umidrel,velvento'

这些variables用于构build存储在resultSELECT语句。 喜欢:

 SELECT Datahora, column1, column2, column3 FROM myTable WHERE id=20 ORDER BY Datahora; 

现在,我的函数返回这个语句作为text 。 我复制粘贴并执行它在pgAdmin或通过psql。 我想自动执行此操作,自动运行查询并返回结果。 我怎样才能做到这一点?

dynamicSQL和RETURNtypes

(我最后保存了最好的,继续阅读!)
你想执行dynamicSQL 。 原则上,在EXECUTE的帮助下,plpgsql很简单。 你不需要光标 – 事实上,大多数情况下你没有显式的光标就没有光标。
通过searchfindSO的例子 。

您遇到的问题:您要返回未定义types的logging 。 函数需要使用RETURNS子句(或OUTINOUT参数)声明返回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); 

但是,如果事先不知道栏目,你甚至会怎么做呢?
你可以使用像jsonjsonbhstorexml这样的结构化文档数据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 EXECUTEUSING子句更加容易混淆。 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

因为我们必须定义一个函数的RETURNtypes,所以在这种情况下我使用ARRAYtypes,它可以包含可变数目的值。 此外,我返回一个列名称的数组,所以你也可以从结果中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中的pcdmetreplace为任何其他表名。

这个怎么用?

  • 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;