在PostgreSQL中为记录类型变量动态传递列名

19

使用PostgreSQL,从表中获取第一条记录的列值并将其存储在记录变量中。例如:假设变量名为:recordvar

recordvar.columnname

返回指定列名的值。我将把columname定义为一个变量:

var := columnname

如果我用变量recordvar.var替换columnname,它就不能工作。

请告诉我如何在这种情况下继续。以下是示例代码:

CREATE OR REPLACE FUNCTION getrowdata(id numeric, table_name character varying)
RETURNS SETOF void AS
$BODY$ 
DECLARE

srowdata record;
reqfield character varying;
value numeric;


BEGIN

RAISE NOTICE 'id: %',id; 
reqfield:= 'columnname';

EXECUTE 'select * from datas.'||table_name||' WHERE id = '||id into srowdata;

RAISE NOTICE 'srowdata: %',srowdata; 

RAISE NOTICE 'srowdatadata.columnname: %',srowdata.columnname;

value:= srowdata.reqfield;

RAISE NOTICE 'value: %',value;


END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
ROWS 1000;
1个回答

24

使用这个虚拟表进行工作

CREATE TEMP TABLE foo (id int, my_num numeric);
INSERT INTO foo VALUES (1, 12.34)

首先,我精简并清理了您的示例:

  • 去除了与问题无关的一些干扰。

  • RETURNS SETOF void 几乎没有意义。我使用 RETURNS void 替代。

  • 为了简单起见,我使用 text 代替了 character varying

  • 使用动态 SQL 时,您必须防范 SQL 注入攻击,我在这种情况下使用了 format()%I。还有其他方法

基本问题是 SQL 的类型和标识符非常严格。您正在操作 动态表名记录的动态字段名,原始示例中的一个匿名记录。Pl/pgSQL 不擅长处理此类情况。Postgres 不知道匿名记录的内容。只有将记录分配给已知类型后,才能引用单个字段。
这里有一个相关的问题,试图使用动态名称设置记录的字段:
如何使用动态SQL设置复合变量字段的值

基本函数

CREATE OR REPLACE FUNCTION getrowdata1(table_name text, id int)
  RETURNS void AS
$func$ 
DECLARE
   srowdata record;
   reqfield text := 'my_num';   -- assigning at declaration time for convenience
   value    numeric;
BEGIN

RAISE NOTICE 'id: %', id; 

EXECUTE format('SELECT * FROM %I WHERE id = $1', table_name)
USING  id
INTO   srowdata;

RAISE NOTICE 'srowdata: %', srowdata;

RAISE NOTICE 'srowdatadata.my_num: %', srowdata.my_num;

/* This does not work, even with dynamic SQL
EXECUTE format('SELECT ($1).%I', reqfield)
USING srowdata
INTO value;

RAISE NOTICE 'value: %', value;
*/

END
$func$ LANGUAGE plpgsql;

呼叫:

SELECT * from getrowdata1('foo', 1);

以下是翻译的结果:

注释部分会引发异常:

无法在记录数据类型中识别“my_num”列:SELECT * from getrowdata(1,'foo')

hstore

您需要安装附加模块hstore。每个数据库只需执行一次以下操作:

CREATE EXTENSION hstore;

然后所有的操作都可以按照以下步骤进行:
CREATE OR REPLACE FUNCTION getrowdata2(table_name text, id int)
  RETURNS void AS
$func$ 
DECLARE
   hstoredata hstore;
   reqfield   text := 'my_num';
   value      numeric;
BEGIN

RAISE NOTICE 'id: %', id; 

EXECUTE format('SELECT hstore(t) FROM %I t WHERE id = $1', table_name)
USING  id
INTO   hstoredata;

RAISE NOTICE 'hstoredata: %', hstoredata;

RAISE NOTICE 'hstoredata.my_num: %', hstoredata -> 'my_num';

value := hstoredata -> reqfield;

RAISE NOTICE 'value: %', value;

END
$func$ LANGUAGE plpgsql;

电话:

SELECT * from getrowdata2('foo', 1);

多态类型

无需安装其他模块即可使用替代方法。

由于您将整行选择到记录变量中,因此根据定义,它具有明确定义的类型。 使用它。 关键字是多态类型

CREATE OR REPLACE FUNCTION getrowdata3(_tbl anyelement, id int)
  RETURNS void AS
$func$ 
DECLARE
   reqfield text := 'my_num';
   value    numeric;
BEGIN

RAISE NOTICE 'id: %', id; 

EXECUTE format('SELECT * FROM %s WHERE id = $1', pg_typeof(_tbl))
USING  id
INTO   _tbl;

RAISE NOTICE '_tbl: %', _tbl;

RAISE NOTICE '_tbl.my_num: %', _tbl.my_num;

EXECUTE 'SELECT ($1).' || reqfield   -- requfield must be SQLi-safe or escape
USING _tbl
INTO  value;

RAISE NOTICE 'value: %', value;

END
$func$ LANGUAGE plpgsql;

呼叫:

SELECT * from getrowdata3(NULL::foo, 1);

-> SQLfiddle

  • 在这里,我滥用了输入参数_tbl来实现三个目的:

    • 提供记录的合适类型
    • 提供表的名称,自动加入模式限定符
    • 作为变量使用。
  • 有关更多解释,请参见以下相关答案(最后一章):
    重构PL/pgSQL函数以返回各种SELECT查询的输出


嗨,我遇到了一个错误:记录“_tbl”没有字段“my_num”,你能帮忙吗? - Ismail Sahin
@ismail:你需要更加具体,并且提出一个新的“问题”,评论不适合。您可以始终链接到此以供参考。 - Erwin Brandstetter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接