循环记录列

问题描述 投票:0回答:7

我需要按键/索引循环遍历类型

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;
postgresql loops for-loop record plpgsql
7个回答
13
投票

正如 @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 演示


6
投票

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))
是如何工作的,并且上述函数永远不应该被实际使用。


2
投票

plpgsql 不支持此功能 - 记录不是像其他脚本语言那样的哈希数组 - 它类似于 C 或 ADA,在这些语言中此功能是不可能的。您可以使用其他 PL 语言,如 PLPerl 或 PLPython 或一些技巧 - 您可以使用 HSTORE 数据类型(扩展)或通过动态 SQL 进行迭代

参见如何使用动态SQL设置复合变量字段的值

但是请求这个功能通常意味着,你做错了一些事情。当您使用 PL/pgSQL 时,您的想法与使用 Javascript 或 Python 不同


0
投票
FOR data2 IN
    SELECT d
    from  unnest( data1 ) s(d)
LOOP
    RETURN NEXT data2;
END LOOP;

0
投票

如果您在循环之前对结果进行排序,您会实现您想要的结果吗?

for rc in select * from t1 order by t1.key asc loop
 return next rc;
end loop;

将完全满足您的需求。这也是执行此类任务的最快方法。


0
投票

我无法找到循环记录的正确方法,所以我所做的就是首先将记录转换为 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;

0
投票

已经过去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”,而不是确定类型。

© www.soinside.com 2019 - 2024. All rights reserved.