Перебрать столбцы записи

Мне нужно перебрать тип 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;

7 ответов

Решение

Как объяснил @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

см. Как установить значение поля составной переменной с использованием динамического SQL

Но запрос на эту функциональность обычно означает, что вы делаете что-то не так. Когда вы используете PL/pgSQL, вы думаете по-другому, чем вы используете Javascript или Python

Я не смог найти правильный способ зацикливания записи, поэтому я сначала преобразовал запись в 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;
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;

будет делать именно то, что вам нужно. Это также самый быстрый способ выполнить такую ​​задачу.

Прошло уже более 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, было бы здорово.

Я действительно не знаю, решит ли это первоначальную проблему, но с помощью этой функции вы сможете создавать списки типов для динамической возвращаемой записи или набора записей в других функциях. Если вам не нравятся определения, вы также можете выполнить в цикле простой «return next Rec2» вместо определения типов.

Другие вопросы по тегам