【问题标题】:Add a column to a record variable of %rowtype向 %rowtype 的记录变量添加一列
【发布时间】:2017-08-15 10:06:45
【问题描述】:

我在 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;

因此,由于"Sequence" 表中的所有可用列,此函数需要超过 1400 个字符的非常长的 SELECT 查询。

【问题讨论】:

  • 不清楚是要缩短函数定义的RETURNS 子句还是函数体中的一些SQL 命令——还是两者兼而有之?提供您使用的完整功能的最小示例将非常有帮助(即使它还没有工作)
  • 我已经用我的缩写函数修改了这个问题。希望有帮助。我想要做的是将 SELECT 查询减少到我的问题中看到的,而不是有一个非常非常长的查询。 SELECT 查询最终嵌套在 Delphi 中用于 FastReport 报告目的,并且维护起来很麻烦。我真的不介意这是否会导致很长的 RETURNS 子句,尽管避免两者都是理想的。
  • tmeA, tmeBtme1, tme2?请使功能一致。还有:Postgres 9.2.9? Urgently consider upgrading 至少到最新的次要版本。
  • 您的问题似乎不是变量定义(record 变量工作正常),而是函数的返回类型(RETURNS 子句)。你的问题在那里有点误导 - 或者这是基本的误解。
  • type records 或 %rowtype 对我有用,但是如果有意义的话,我一直未能找到一种方法来向 %rowtype “虚拟表”添加额外的列。如果匿名你的意思是我每列没有列名或数据类型,那么我认为这实际上是我试图用 RECORD 类型克服的问题,因为我必须在函数的查询中继续定义它。即 SELECT * FROM production1('2016-02-27 00:00:00','2016-03-11 00:00:00') AS table(col1 Integer, col2 Integer, col3 Real...等);

标签: sql postgresql function types plpgsql


【解决方案1】:

有两种解决方案:

  1. 使用组合

    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 │
    ╞═══╪═══╪═══╪════╪════╡
    │   │   │   │    │    │
    └───┴───┴───┴────┴────┘
    
  2. inheritance - 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 │
    ╞═══╪═══╪═══╪════╪════╡
    │   │   │   │    │    │
    └───┴───┴───┴────┴────┘
    

【讨论】:

    【解决方案2】:

    您的问题是函数的返回类型,不是函数内部的变量rc,它工作得很好。

    您可以创建另一个类型以在RETURNS SETOF &lt;row_type&gt;like @Pavel demonstrates 中使用。 (但不要在这里使用继承,这是一个学术问题,就像 Pavel 已经提到的那样。)

    或者您可以改用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 一样(不确定这是否是有意的)。

    相关:

    旁白

    没有办法将列“添加”到 plpgsql 中的 any 行类型变量。无论是众所周知的行类型 (%ROWTYPE) 还是匿名的 record - 都可以重新分配,但一旦分配就不能添加列。


    所以我已经声明了虚拟表(这个术语正确吗?)rc 为:

    rc "Sequence"%rowtype;
    

    The manual clarifies:

    复合类型的变量称为行变量(或行类型变量)。

    在阅读该章节时(推荐!)还请注意:

    可以将行变量声明为与 现有表或视图,使用 table_name%ROWTYPE 表示法;要么 它可以通过给出复合类型的名称来声明。 (由于每 表有一个关联的同名复合类型,它实际上 在 PostgreSQL 中你是否写 %ROWTYPE 都没有关系。但 %ROWTYPE 的表单更便携。)


    我强烈建议只使用合法的、小写的、不带引号的标识符。你的 CaMeL 案例标识符是一个加载的脚枪,尤其是当你有时双引号时,有时不。

    【讨论】:

    • 非常感谢 Erwin,您一口气为我澄清了许多我一直在努力寻找答案的问题。 PS CamelCase 是多年前数据库的结转。我不到一年前才开始使用它。
    • 查看您的答案@Erwin,并同意这似乎最合乎逻辑。返回的表能否继承自序列表(类似于Pavels的解释)?这将节省输入所有序列表列名称和类型。不是必需品,但绝对值得拥有。
    • @witenitenz:将RETURNS TABLECREATE FUNCTION 一起使用时,您必须提供具有现有数据类型的列列表。继承不适用于那里。只有表可以继承。函数可以像任何其他函数一样使用通过继承创建的表类型,但不能“继承”自身的返回类型。如果您想避免重复"Sequence" 表的列而不使函数调用复杂化,Pavel 的解决方案2. 是唯一的方法。但我宁愿为CREATE FUNCTION复制表once的列定义列表。
    猜你喜欢
    • 2010-09-30
    • 1970-01-01
    • 1970-01-01
    • 2012-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多