关于postgresql:如何在plpgsql中使用记录类型变量?

How to use a record type variable in plpgsql?

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

用这样的表:

1
2
3
CREATE TABLE test (id INT, tags text[]);
INSERT INTO test VALUES (1,'{a,b,c}'),
                        (2,'{c,d,e}');

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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
$$;

...但是没有用:

1
SELECT func('test');
1
2
3
4
5
6
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),而不包含表(众所周知类型的0-n行)。在Postgres或PL / pgSQL中没有"表变量"。根据任务,有多种选择:

  • PostgreSQL表变量
  • 将多个行和列选择到一个记录变量中

因此,不能将多个行分配给record类型的变量。在此语句中:

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

... Postgres仅分配第一行,而丢弃其余行。由于查询中的"第一个"定义不正确,因此最终会被任意选择。显然是由于一开始提到的误解。

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

relation"t" does not exist

现在应该很清楚,count(*)开头毫无意义,因为t只是单个记录/行-无论如何还是不可能的。

最后(即使其余的都可以),您的返回类型似乎是错误的: (t TEXT[], e TEXT[]) 。由于在t中选择了id, tags,因此需要返回类似(id int, e TEXT[])的内容。

您要尝试执行的操作如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;

调用(注意语法!):

1
SELECT * FROM func('test');

有关:

  • Postgres从动态sql字符串创建本地临时表(在提交删除时)

只是概念证明。在选择整个表时,您将只使用基础表。实际上,查询中将包含一些WHERE子句。

注意潜伏类型不匹配时,count()返回bigint,您无法将其分配给integer变量。需要强制转换:count(*)::int

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

1
GET DIAGNOSTICS _ct = ROW_COUNT;

手册中的详细信息。

为什么ANALYZE

  • 9.1下是否仍建议使用常规VACUUM ANALYZE?

撇开:普通SQL中的CTE通常可以胜任:

  • 从plpgsql中的FOR循环切换到基于集合的SQL命令