我需要按键/索引循环遍历类型
RECORD
项目,就像我可以使用其他编程语言中的数组结构来做到这一点。
例如:
DECLARE
data1 record;
data2 text;
...
BEGIN
...
FOR data1 IN
SELECT
*
FROM
sometable
LOOP
FOR data2 IN
SELECT
unnest( data1 ) -- THIS IS DOESN'T WORK!
LOOP
RETURN NEXT data1[data2]; -- SMTH LIKE THIS
END LOOP;
END LOOP;
正如 @Pavel 所解释的,不能像遍历数组那样简单地遍历记录。但有几种方法可以解决这个问题 - 具体取决于您的具体要求。最终,由于您想要返回同一列中的所有值,因此需要将它们转换为相同的类型 -
text
是明显的共同点,因为每种类型都有一个文本表示形式。
假设您有一个带有
integer
、text
和 date
列的表。
CREATE TEMP TABLE tbl(a int, b text, c date);
INSERT INTO tbl VALUES
(1, '1text', '2012-10-01')
,(2, '2text', '2012-10-02')
,(3, ',3,ex,', '2012-10-03') -- text with commas
,(4, '",4,"ex,"', '2012-10-04') -- text with commas and double quotes
那么解决方案可以很简单:
SELECT unnest(string_to_array(trim(t::text, '()'), ','))
FROM tbl t;
适用于前两行,但不适用于第 3 行和第 4 行的特殊情况。
您可以通过文本表示中的逗号轻松解决问题:
SELECT unnest(('{' || trim(t::text, '()') || '}')::text[])
FROM tbl t
WHERE a < 4;
这可以正常工作 - 除了第 4 行,它在文本表示中带有双引号。通过将它们加倍来逃脱它们。但数组构造函数需要将它们通过
\
转义。不知道为什么会出现这种不兼容的情况...
SELECT ('{' || trim(t::text, '()') || '}') FROM tbl t WHERE a = 4
产量:
{4,""",4,""ex,""",2012-10-04}
但是你需要:
SELECT '{4,"\",4,\"ex,\"",2012-10-04}'::text[]; -- works
如果您事先知道列名称,一个干净的解决方案将很简单:
SELECT unnest(ARRAY[a::text,b::text,c::text])
FROM tbl
由于您操作的是众所周知类型的记录,因此您只需查询系统目录即可:
SELECT string_agg(a.attname || '::text', ',' ORDER BY a.attnum)
FROM pg_catalog.pg_attribute a
WHERE a.attrelid = 'tbl'::regclass
AND a.attnum > 0
AND a.attisdropped = FALSE
将其放入具有动态 SQL 的函数中:
CREATE OR REPLACE FUNCTION unnest_table(_tbl text)
RETURNS SETOF text LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY EXECUTE '
SELECT unnest(ARRAY[' || (
SELECT string_agg(a.attname || '::text', ',' ORDER BY a.attnum)
FROM pg_catalog.pg_attribute a
WHERE a.attrelid = _tbl::regclass
AND a.attnum > 0
AND a.attisdropped = false
) || '])
FROM ' || _tbl::regclass;
END
$func$;
致电:
SELECT unnest_table('tbl') AS val
退货:
val
-----
1
1text
2012-10-01
2
2text
2012-10-02
3
,3,ex,
2012-10-03
4
",4,"ex,"
2012-10-04
无需安装额外模块即可工作。另一种选择是安装 hstore 扩展并使用它 就像 @Craig 演示。
PL/pgSQL 并不是真正为您想做的事情而设计的。它不认为记录是可迭代的,它是可能不同且不兼容的数据类型的元组。
PL/pgSQL 有
EXECUTE
用于动态 SQL,但是 EXECUTE
查询不能直接引用 PL/pgSQL 变量(如 NEW
)或其他记录。
您可以做的是将记录转换为
hstore
键/值结构,然后迭代hstore
。使用 each(hstore(the_record))
,它会生成 key,value
元组的行集。所有值都会转换为其 text
表示形式。
这个玩具函数通过创建一个匿名
ROW(..)
来演示对记录的迭代 - 它将具有列名称 f1
、f2
、f3
- 然后将其转换为 hstore
,迭代其列/值对,并返回每对。
CREATE EXTENSION hstore;
CREATE OR REPLACE FUNCTION hs_demo()
RETURNS TABLE ("key" text, "value" text)
LANGUAGE plpgsql AS
$$
DECLARE
data1 record;
hs_row record;
BEGIN
data1 = ROW(1, 2, 'test');
FOR hs_row IN SELECT kv."key", kv."value" FROM each(hstore(data1)) kv
LOOP
"key" = hs_row."key";
"value" = hs_row."value";
RETURN NEXT;
END LOOP;
END;
$$;
实际上你永远不会这样写,因为整个循环可以用一个简单的
RETURN QUERY
语句替换,并且它执行与 each(hstore)
相同的操作 - 所以这只是 来展示 each(hstore(record))
是如何工作的,并且上述函数永远不应该被实际使用。
plpgsql 不支持此功能 - 记录不是像其他脚本语言那样的哈希数组 - 它类似于 C 或 ADA,在这些语言中此功能是不可能的。您可以使用其他 PL 语言,如 PLPerl 或 PLPython 或一些技巧 - 您可以使用 HSTORE 数据类型(扩展)或通过动态 SQL 进行迭代
但是请求这个功能通常意味着,你做错了一些事情。当您使用 PL/pgSQL 时,您的想法与使用 Javascript 或 Python 不同
FOR data2 IN
SELECT d
from unnest( data1 ) s(d)
LOOP
RETURN NEXT data2;
END LOOP;
如果您在循环之前对结果进行排序,您会实现您想要的结果吗?
for rc in select * from t1 order by t1.key asc loop
return next rc;
end loop;
将完全满足您的需求。这也是执行此类任务的最快方法。
我无法找到循环记录的正确方法,所以我所做的就是首先将记录转换为 json 并循环 json
declare
_src_schema varchar := 'db_utility';
_targetjson json;
_key text;
_value text;
BEGIN
select row_to_json(c.*) from information_schema.columns c where c.table_name = prm_table and c.column_name = prm_column
and c.table_schema = _src_schema into _targetjson;
raise notice '_targetjson %', _targetjson;
FOR _key, _value IN
SELECT * FROM jsonb_each_text(_targetjson)
LOOP
-- do some math operation on its corresponding value
RAISE NOTICE '%: %', _key, _value;
END LOOP;
return true;
end;
已经过去10多年了,仍然没有机会动态循环记录列。但我有一个解决方案,有点帮助。以下函数将创建并返回 json 对象中的记录定义。
CREATE OR REPLACE FUNCTION transactions.f_get_record_definitions(p_txt_sql text)
RETURNS json
LANGUAGE plpgsql
AS $function$
declare
j_rec_defs json;
rec record;
rec2 record;
txt_type text;
txt_j_build text;
begin
execute p_txt_sql limit 1 into rec;
txt_j_build := '{';
j_rec_defs := row_to_json(rec)::json;
for rec2 in
select * from json_each(j_rec_defs)
loop
case json_typeof(rec2.value::json)
when 'object' then txt_type := 'json';
when 'string' then txt_type := 'text';
when 'array' then txt_type := 'array';
when 'boolean' then txt_type := 'bool';
when 'number' then txt_type := 'numeric';
else
txt_type := 'json';
end case;
txt_j_build := txt_j_build||'"'||rec2.key||'":"'||txt_type||'",';
end loop;
txt_j_build := left(txt_j_build, -1)||'}';
return txt_j_build::json;
end;
$function$
;
嗯,你只能使用json或jsonb的类型,但是很多情况下这是可以的。如果有人知道如何使用 pg_type 直接从 rec 获取类型,那就太好了。
我真的不知道,这是否解决了这里的初始问题,但是使用此函数,您将能够创建类型列表以在其他函数中动态返回记录或记录集。如果您不喜欢这些定义,您还可以在循环中执行简单的“返回下一个rec2”,而不是确定类型。