如何在plpgsql中使用记录类型变量?

How to use a record type variable in plpgsql?

如何将存储在记录类型变量中的查询结果用于同一存储函数中的另一个查询?我使用 Postgres 9.4.4.

像这样的table:

create table test (id int, tags text[]);
insert into test values (1,'{a,b,c}'),
                        (2,'{c,d,e}');

我写了一个函数(简化)如下:

CREATE OR REPLACE FUNCTION func(_tbl regclass)
RETURNS TABLE (t TEXT[], e TEXT[])
LANGUAGE plpgsql AS $$
DECLARE
  t RECORD;
  c INT;
BEGIN
  EXECUTE format('SELECT id, tags FROM %s', _tbl) INTO t;
  SELECT count(*) FROM t INTO c;
  RAISE NOTICE '% results', c;
  SELECT * FROM t;
END
$$;

...但没有成功:

select func('test');
ERROR:  42P01: relation "t" does not exist
LINE 1: SELECT count(*) FROM t
                             ^
QUERY:  SELECT count(*) FROM t
CONTEXT:  PL/pgSQL function func(regclass) line 7 at SQL statement
LOCATION:  parserOpenTable, parse_relation.c:986

核心误解:record变量保存行(或为NULL),而不是table(a的0-n行众所周知的类型)。在 Postgres 中 没有 "table variables" 或 PL/pgSQL。根据任务的不同,有多种选择:

  • PostgreSQL table variable
  • SELECT multiple rows and columns into a record variable

因此,您不能将 多个 行分配给 record 类型变量。在此声明中:

EXECUTE format('SELECT id, tags FROM %s', _tbl) INTO t;

... Postgres 仅分配 第一行 并丢弃其余行。由于 "the first" 在您的查询中没有明确定义,您最终会随意选择。显然是因为开头提到的误会。

A record 变量也不能用于代替 SQL 查询中的 tables。这是您收到错误的主要原因:

relation "t" does not exist

现在应该很清楚了,count(*) 一开始没有任何意义,因为 t 只是一个记录/行 - 而且无论如何都是不可能的。

最后(即使其余的都行得通),您的 return 类型似乎有误:(t TEXT[], e TEXT[])。因为你 select id, tags 进入 t,你想要 return 类似 (id int, e TEXT[]).

你正在尝试做的事情会像这样工作:

CREATE OR REPLACE FUNCTION func(_tbl regclass)
  RETURNS TABLE (id int, e text[]) AS
$func$
DECLARE
   _ct int;
BEGIN
   EXECUTE format(
      'CREATE TEMP TABLE tmp ON COMMIT DROP AS
       SELECT id, tags FROM %s'
    , _tbl);

   GET DIAGNOSTICS _ct = ROW_COUNT; -- cheaper than another count(*)

   -- ANALYZE tmp;  -- if you are going to run multiple queries

   RAISE NOTICE '% results', _ct;

   RETURN QUERY TABLE tmp;
END
$func$ LANGUAGE plpgsql;

调用(注意语法!):

SELECT * FROM func('test');

相关:

  • Postgres creating a local temp table (on commit drop) from a dynamic sql string

只是概念验证。当您 select 访问整个 table 时,您只需使用基础 table 即可。实际上,您在查询中会有一些 WHERE 子句 ...

注意潜在的类型不匹配,count() returns bigint,您不能将其分配给 integer 变量。需要演员表:count(*)::int.

但我完全替换了它,在 EXECUTE 之后运行 更便宜:

GET DIAGNOSTICS _ct = ROW_COUNT; 

Details in the manual.

为什么 ANALYZE


旁白:普通的 CTE SQL 通常可以完成这项工作:

  • Switching from FOR loops in plpgsql to set-based SQL commands