动态 SQL 和RETURN
类型
(我把最好的留到最后,继续阅读!)
你想执行动态 SQL。原则上,在 plpgsql 的帮助下,这很简单EXECUTE
。你不需要光标。事实上,大多数情况下,没有显式游标会更好。
您遇到的问题:您想要返回尚未定义类型的记录。函数需要在RETURNS
子句中声明其返回类型(或使用OUT
或INOUT
参数)。在您的情况下,您将不得不回退到匿名记录,因为number,names和返回列的类型各不相同。像:
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)
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 = $1
ORDER BY datahora'
USING _id;
END
$func$;
变量_sensors
和_type
可以是输入参数。
注意RETURNS TABLE
子句。
注意使用RETURN QUERY EXECUTE
. 这是从动态查询返回行的更优雅的方法之一。
我使用函数参数的名称,只是为了使USING
子句RETURN QUERY EXECUTE
不那么混乱。$1
SQL 字符串中的不是指函数参数,而是指与USING
子句一起传递的值。(在这个简单的例子中,两者恰好都$1
在各自的范围内。)
请注意以下示例值_sensors
:每列都被强制转换为 type text
。
这种代码很容易受到SQL 注入的影响。我quote_ident()
用来防止它。将变量中的几个列名集中在一起会_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;
同样,单个列名可以正确转义,这将是一种干净的方式。
共享相同类型的可变列数
在您的问题更新后,您的返回类型看起来像
- 可变数量的列
- 但相同类型 的所有列
double precision
(别名float8
)
在这种情况下使用ARRAY
类型来嵌套可变数量的值。此外,我返回一个包含列名的数组:
CREATE OR REPLACE FUNCTION data_of(_id integer)
RETURNS TABLE (datahora timestamp, names text[], values float8[])
LANGUAGE plpgsql 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$;
各种完整的表类型
要实际返回table 的所有列,有一个使用多态类型的简单而强大的解决方案:
CREATE OR REPLACE FUNCTION data_of(_tbl_type anyelement, _id int)
RETURNS SETOF anyelement
LANGUAGE plpgsql 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$;
致电(重要!):
SELECT * FROM data_of(NULL::pcdmet, 17);
在调用中替换pcdmet
为任何其他表名。
这是如何运作的?
anyelement
是伪数据类型,多态类型,任何非数组数据类型的占位符。函数中所有出现的anyelement
都评估为运行时提供的相同类型。通过提供一个定义类型的值作为函数的参数,我们隐式地定义了返回类型。
PostgreSQL 自动为每个创建的表定义一个行类型(一种复合数据类型),因此每个表都有一个定义良好的类型。这包括临时表,便于临时使用。
任何类型都可以NULL
。提交一个NULL
值,强制转换为表格类型:NULL::pcdmet
.
现在该函数返回一个定义明确的行类型,我们可以使用它SELECT * FROM data_of()
来分解行并获取各个列。
pg_typeof(_tbl_type)
返回表的名称作为对象标识符类型regtype
。当自动转换为 时,如果需要text
,标识符会自动被双引号和模式限定,从而自动防御 SQL 注入。这甚至可以处理quote_ident()
会失败的模式限定表名。看: