我编写了一个函数,该函数输出格式正确的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=' ||  ||'\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 precision



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


基础表的定义。
变量sensors将保存type中对应表的所有此处显示的列。例如:如果typepcdmet,则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执行它。我要自动执行此操作,自动运行查询并返回结果。我该怎么办?

评论

我冒昧地重写了您的问题以澄清问题-现在,我认为我基本上已经明白了。如果您不同意,请随时回滚。

同意:)谢谢您的改进!我喜欢这个社区的工作方式

#1 楼

动态SQL和RETURN类型
(我把最好的留到最后,请继续阅读!)
要执行动态SQL。原则上,借助EXECUTE在plpgsql中很简单。您不需要游标-实际上,大多数情况下,最好不要使用显式游标。通过搜索在SO上查找示例。
遇到的问题:您想返回尚未定义类型的记录。函数需要使用RETURNS子句(或OUTINOUT参数)声明返回类型。在您的情况下,您将不得不使用匿名记录,因为返回列的数量,名称和类型会有所不同。像:
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

如何在数据库中存储数据表?

但是出于这个问题的目的,让我们假设您要返回的单个,正确键入和命名的列尽可能多
返回值类型固定的简单解决方案
datahora列似乎是给定的,我假设数据类型为timestamp,并且总会有另外两列的名称和数据类型不同。 >我们将放弃名称,而使用返回类型中的通用名称。我们也将放弃类型,并将其全部转换为text,因为每种数据类型都可以转换为text
CREATE OR REPLACE FUNCTION data_of(_id integer)
  RETURNS TABLE (datahora timestamp, col2 text, col3 text)
  LANGUAGE plpgsql 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 = 
      ORDER  BY datahora'
   USING  _id;

END
$func$;

如何


变量_sensors_type可以代替输入参数。


请注意RETURNS TABLE子句。


N请使用RETURN QUERY EXECUTE。这是从动态查询返回行的更优雅的方法之一。


我为函数参数使用一个名称,只是为了使USINGRETURN QUERY EXECUTE子句更容易混淆。 SQL字符串中的不是引用函数参数,而是引用USING子句传递的值。 (在这个简单的示例中,两者在各自的范围内碰巧都是。) >
这种代码非常容易受到SQL注入的攻击。我使用_sensors进行保护。将变量text中的几个列名汇总在一起会阻止使用quote_ident()(通常是个坏主意!)。确保没有其他问题,例如通过_sensors单独运行列名称。想到了一个quote_ident()参数...


在PostgreSQL 9.1+中更简单
在9.1版或更高版本中,您可以使用quote_ident()进一步简化:
RETURN QUERY EXECUTE format('
   SELECT datahora, %s  -- identifier passed as unescaped string
   FROM   %I            -- assuming the name is provided by user
   WHERE  id = 
   ORDER  BY datahora'
  ,_sensors, _type)
USING  _id;

同样,可以正确地转义各个列的名称,这是一种干净的方法。
共享相同类型的可变列数
问题更新后,看起来您的返回类型具有
< br数目可变的列
,但是所有相同类型的列VARIADIC(别名format()

由于我们必须定义函数的double precision类型,因此我在这种情况下,可以容纳可变数量的值。另外,我返回了一个带有列名的数组,因此您也可以从结果中解析名称:
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()  -- AS names
           , ARRAY[%s]            -- AS values
      FROM   %s
      WHERE  id = 
      ORDER  BY datahora'
    , _sensors, _type)
   USING  _sensors, _id;
END
$func$  LANGUAGE plpgsql;

各种完整的表类型
如果您实际上试图返回的是一个表(例如链接页面上的表之一),然后使用具有多态类型的简单,非常强大的解决方案:
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 = 
      ORDER  BY datahora'
    , pg_typeof(_tbl_type))
   USING  _id;
END
$func$ LANGUAGE plpgsql;

(重要!):
SELECT * FROM data_of(NULL::pcdmet, 17);

用任何其他表名替换呼叫中的float8
这是如何工作的?


RETURN是伪数据类型,多态类型,任何非数组数据类型的占位符。该函数中所有出现的ARRAY都将评估为运行时提供的相同类型。通过向函数提供已定义类型的值作为参数,我们隐式定义了返回类型。


PostgreSQL自动为创建的每个表定义行类型(复合数据类型),因此,每个表都有一个定义明确的类型。这包括临时表,方便临时使用。


任何类型都可以是pcdmet。因此,我们将一个anyelement值传递给表类型:anyelement。现在,该函数返回定义明确的行类型,我们可以使用NULL分解行并获取单个行列。


NULL返回表的名称作为对象标识符类型NULL::pcdmet。当自动转换为SELECT * FROM data_of(...)时,标识符将自动加双引号,并在需要时通过模式限定。因此,SQL注入是不可能的。这甚至可以处理pg_typeof(_tbl_type)失败的架构限定表名。



评论


非常感谢你!所有这些帮助都是无价的!并确保它解决了问题!

–waldyr.ar
2012年8月2日,0:13

好的答案,但不禁感到前两个解决方案只是最后一个很酷的解决方案的前戏;-)

– Beldaz
15年2月18日在2:50

最终解决方案是否可以包装在以关系的文本名称作为参数的函数中?我已经遇到过几次这个问题,其中我的Web应用程序具有类似getData(String RelationName,Int rowId)之类的功能,并且我想通过调用上述内容来实现,但存在对RelationName进行SQL注入的危险。

– Beldaz
2015年2月18日,下午3:29

@beldaz:请对此问题开始一个新问题。评论不是地方。您可以随时为该答案添加链接以获取上下文。

–欧文·布兰德斯特(Erwin Brandstetter)
2015年2月18日在3:43



@BrianPreslopsky:pg_typeof(_tbl_type)::文本

–欧文·布兰德斯特(Erwin Brandstetter)
18年5月29日在21:46

#2 楼

您可能需要返回一个游标。尝试这样的事情(我还没有尝试过):

CREATE OR REPLACE FUNCTION data_of(integer)
  RETURNS refcursor AS
$BODY$
DECLARE
      --Declaring variables
      ref refcursor;
BEGIN
      -- make sure `sensors`, `type`,  variable has valid value
      OPEN ref FOR 'SELECT Datahora,' || sensors ||
      ' FROM ' || type ||
      ' WHERE nomepcd=' ||  ||' ORDER BY Datahora;';
      RETURN ref;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
ALTER FUNCTION data_of(integer) OWNER TO postgres;


评论


没用它返回一列名为refcursor的行,并带有“ <未命名的门户网站XX>”的行!我不是PL / PgSQL的专家,但我并不傻。这是我的第一次尝试。顺便说一句,谢谢您的尝试! :)

–waldyr.ar
2012年7月31日13:10

您是否从返回的游标中抓取?像FETCH ref INTO目标一样;请向游标文档咨询有关如何使用游标的详细信息。 postgresql.org/docs/8.1/static/plpgsql-cursors.html

–bpgergo
2012年7月31日下午13:14

惊人,非常优雅且功能强大的方法。与PgAdmin一起很好地工作,但与PSQLODBC驱动程序和ADODB不能一起工作。在PgAdmin中,我们必须在中添加所有显式提取以使其正常运行。

– Analyly Alekseev
18年5月31日在0:38

一个人怎么用呢?正在抓取data_of(1)?我在那里不断收到语法错误

– Vinicius Dantas
19-6-25在16:30



#3 楼

很抱歉,您的问题尚不清楚。但是,下面您将找到一个自包含的示例,该示例如何创建和使用返回游标变量的函数。希望对您有所帮助!

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;


评论


与其说那是一个不清楚的问题,不如告诉我缺少了什么,或者您想知道什么。尽管我一直在努力提出一个很好的问题,但仍不清楚我很抱歉。我可以根据您的考虑进行改进。

–waldyr.ar
2012年7月31日在19:13

@ waldyr.ar:您更新的版本现在好多了。只是不清楚您要实现什么,还缺少一些细节(那些传感器和类型是什么,以及它们如何获得其值)。如果问题包含说明问题的最简单的可能的工作代码(即可以由其他人执行),通常会很有帮助。

–user272735
2012年8月1日在5:48



#4 楼

# 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() "


评论


有错问题吗?

–cstork
12月5日16:24