我在PostgreSQL 9.2.9中有一个需要返回多列的plpgsql函数。大多数列来自单个表,所以我已经声明了虚拟表(该术语是否正确?)rc
为:
rc "Sequence"%rowtype;
这很有效,除了我还有2列要添加到返回的数据。如果我用以下内容替换上述声明,该函数可以正常工作:
rc RECORD;
以及其他一些小代码更改。但是,我需要始终使用包含所有列定义的非常长SELECT
命令来明确调用该函数。
如何充分利用这两个方面,即返回"Sequence"
表格的所有列,并添加2个新列("AverageSED"
和"avDailySED"
),其中包含以下内容:
SELECT * FROM production1('2016-02-27 00:00:00','2016-03-11 00:00:00');
这是我的一般功能,作为使用RECORD类型的示例(为了便于阅读而减少)。
CREATE OR REPLACE FUNCTION production1(tme1 timestamp without time zone, tme2 timestamp without time zone, mn integer)
RETURNS SETOF record AS
$BODY$
DECLARE
-- Could possibly use (CREATE TYPE rcholder %rowtype) and use a function with a sub-function that then takes the rc RECORD and recasts it to type %rowtype. This would mean the column descriptor in the SELECT call could be dropped
rc record;
AverageSED Real;
avDailySED Real;
BEGIN
-- Calculate average of value from Log_Alpha table
SELECT AVG("logSED")::Real
FROM "Log_Alpha"
WHERE "logTime" >= tme1 AND "logTime" < tme2
INTO averageSED;
-- Select the required row from the sequence data table
FOR rc IN
SELECT *, AverageSED, avDailySED
FROM "Sequence"
WHERE "Sequence"."seqMinute" = mn AND "Sequence"."seqTime" >= tmeA AND "Sequence"."seqTime" <= tmeB
ORDER BY "Sequence"."seqTime"
LOOP
rc."seqTime" = rc."seqTime" - '1 day'::interval;
-- Use a subquery to calculate the average SED for all data for the day pertaining to this record date field
SELECT AVG("logSED")::Real into rc.avDailySED FROM "Log_Alpha"
WHERE "Log_Alpha"."logTime" >= rc."seqTime" and "Log_Alpha"."logTime" < (rc."seqTime" + '1 day'::interval);
RETURN NEXT rc ;-- (AverageSED,avDailySED);
END LOOP;
END
$BODY$
LANGUAGE plpgsql;
因此,由于SELECT
表中可用的所有列,此函数需要超过1400个字符的非常长的"Sequence"
查询。
答案 0 :(得分:1)
你的问题是函数的返回类型, 不 函数中的变量rc
,它可以正常工作。
您可以在RETURNS SETOF <row_type>
,like @Pavel demonstrates中创建另一种类型。 (但是不要在这里使用继承,这是一个学术的问题,就像帕维尔已经提到的那样。)
或 您可以使用RETURNS TABLE ()
代替。 (这就是我要做的。)
此外,您的整个函数可以从根本上简化为具有LATERAL
子查询的单个SQL查询(需要 Postgres 9.3 + ,请考虑升级到当前版本!):
CREATE OR REPLACE FUNCTION production1(tme1 timestamp, tme2 timestamp, mn int)
RETURNS TABLE ( -- list all columns of "Sequence" here! Plus ...
, average_sed real
, av_daily_sed real) AS
$func$
SELECT s.*, a1.average_sed, a2.av_daily_sed
FROM "Sequence" s
CROSS JOIN ( -- safe for an aggregate that always returns a row
SELECT AVG("logSED")::real AS average_sed
FROM "Log_Alpha"
WHERE "logTime" >= tme1
AND "logTime" < tme2
) a1
LEFT JOIN LATERAL (
SELECT AVG("logSED")::real AS av_daily_sed
FROM "Log_Alpha" a
WHERE a."logTime" >= (s."seqTime" - interval '1 day')
and a."logTime" < s."seqTime"
) a2 ON TRUE
WHERE s."seqMinute" = mn
AND s."seqTime" >= tme1
AND s."seqTime" < tme2 -- I assume you want '<' like above
ORDER BY s."seqTime";
$func$ LANGUAGE sql STABLE;
唯一的区别是:"seqTime"
会保持不变,而不是"seqTime" - '1 day'::interval
,就像你的功能一样(不确定是否有意)。
相关:
没办法 来&#34;添加&#34;列到plpgsql中的任何行类型变量。无论是众所周知的行类型(%ROWTYPE
)还是匿名record
- 都可以重新分配,但一旦分配就不会添加列。
所以我已声明虚拟表(该术语是否正确?)
rc
为:rc "Sequence"%rowtype;
复合类型的变量称为行变量(或行型变量)。
阅读那一章(推荐!)时也注意到:
可以将行变量声明为与a的行具有相同的类型 现有的表格或视图,使用
table_name
%ROWTYPE
表示法;要么 它可以通过给出复合类型的名称来声明。 (因为每一个 table实际上有一个相同名称的关联复合类型 在PostgreSQL中无论你是否写%ROWTYPE
都无关紧要。但%ROWTYPE
的表单更具便携性。)
我强烈建议您专门使用合法的小写不带引号的标识符。你的CaMeL案例标识符是一个加载的脚枪,特别是当你有时双引号,有时候不会。
答案 1 :(得分:0)
有两种解决方案:
使用合成
create table foo1(a int, b int, c int);
create type extended_foo AS (f foo1, a1 int, b1 int);
create or replace function fx1()
returns extended_foo as $$ select null::extended_foo $$
language sql;
postgres=# select * from fx1();
┌───┬────┬────┐
│ f │ a1 │ b1 │
╞═══╪════╪════╡
│ │ │ │
└───┴────┴────┘
postgres=# select (f).*, a1, b1 from fx1();
┌───┬───┬───┬────┬────┐
│ a │ b │ c │ a1 │ b1 │
╞═══╪═══╪═══╪════╪════╡
│ │ │ │ │ │
└───┴───┴───┴────┴────┘
继承 - PostgreSQL支持表继承(但我不确定,如果它通常是想法,但对于这个用例它应该工作 - 注意:Postgres中的OOP来自旧学年龄并且更新完成) :
create table foo2(a1 int, b1 int) inherits(foo1);
create or replace function fx2()
returns foo2 as $$ select null::foo2 $$
language sql;
postgres=# select * from fx2();
┌───┬───┬───┬────┬────┐
│ a │ b │ c │ a1 │ b1 │
╞═══╪═══╪═══╪════╪════╡
│ │ │ │ │ │
└───┴───┴───┴────┴────┘