循环通过RECORD列

时间:2012-10-25 09:41:17

标签: postgresql loops for-loop record plpgsql

我需要按键/索引遍历类型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;

5 个答案:

答案 0 :(得分:12)

正如@Pavel所解释的那样,不仅可以遍历记录,就像你可以遍历一个数组一样。但有几种方法可以解决它 - 具体取决于您的具体要求。最终,由于您想要返回同一列中的所有值,您需要将它们转换为相同的类型 - text是明显的共同点,因为每种类型都有一个文本表示。

又快又脏

假设您有一张包含integertextdate列的表格。

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扩展程序并使用like @Craig demonstrates

答案 1 :(得分:5)

PL / pgSQL并不是真的适合您想要做的事情。它不认为记录是可迭代的,它是可能不同且不兼容的数据类型的元组。

PL / pgSQL对于动态SQL有EXECUTE,但EXECUTE查询不能直接引用PL / pgSQL变量,如NEW或其他记录。

可以做的是将记录转换为hstore键/值结构,然后遍历hstore。使用each(hstore(the_record)),它会生成key,value个元组的行集。所有值都会转换为text表示形式。

此玩具功能通过创建匿名ROW(..)来展示对记录的迭代 - 其中包含列名f1f2f3 - 然后将其转换为{{ 1}},遍历其列/值对,并返回每对。

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

答案 2 :(得分:2)

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

请参阅How to set value of composite variable field using dynamic SQL

但是对此功能的请求通常意味着,所以你做错了。当您使用PL / pgSQL时,您认为与使用Javascript或Python

不同

答案 3 :(得分:0)

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

答案 4 :(得分:0)

如果你在循环之前订购结果,你会完成你想要的吗?

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

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