plpgsql возвращает составной тип с несколькими строками. Использование оператора выбора в качестве параметра IN для функции - PullRequest
0 голосов
/ 09 ноября 2018

Это вопрос из 2 частей. В настоящее время я нахожусь в процессе преобразования некоторых функций оракула в postgres.

Я преобразовал приведенное ниже в postgres, но проблема в том, что он выводит только одну строку:

CREATE TYPE m_t_stts_ot AS (
id INTEGER,
name TEXT,
stts_nm TEXT,
stts_ds TEXT,
f_s_f CHARACTER(1),
t_ts TIMESTAMP WITHOUT TIME ZONE,
e_id INTEGER,
s_efctv_dt TIMESTAMP WITHOUT TIME ZONE,
m_s_id INTEGER,
m_d_o_fl CHARACTER(1)
);


CREATE OR REPLACE FUNCTION get_s_fn(IN in_cur integer[], IN in_ts TIMESTAMP WITHOUT TIME ZONE)
RETURNS SETOF m_t_stts_ot 
AS
$BODY$
DECLARE
    o_rec s_ot;
    v_id s_id%TYPE;
    rec record;

BEGIN
FOR rec IN       
        SELECT sr.*,
             mtsv.id,
             mtsv.name,
             mtsv.status ,
             mtsv.stts_ds ,
             mtsv.ts,
             coalesce(mtsv.ts,sr.crtd_dt),
             mtsv.e_dt,
             mtsv.ovrrd_fl
        FROM RAW sr
        LEFT JOIN (select * from get_task_stts_fn(ARRAY(SELECT distinct id
                                                        FROM prpty
                                                       WHERE value_nb in (select * from unnest(in_cur))
                                                         AND prpty_id in( 20017, 21021, 22017)), in_ts))mtsv
          ON ( sr.event_id = mtsv.event_id AND
               mtsv.f_stts_fl = 'Y'    AND
               mtsv.task_ts <= in_ts )
       WHERE sr.id in(select * from unnest(in_cur))        
         AND sr.crtd_dt <= cast(in_ts as date)
         AND ( mtsv.id is not null OR
               sr.event_id is null
             )
       ORDER BY ( case
                  when mtsv.id is not null then
                       task_ts
                  else
                       sr.crtd_dt
                  end ) asc


    LOOP
            IF ((o_rec).STTS_CD = 'A')
            THEN
                IF (((o_rec).c_cd IN ('E', 'R')) AND (rec.chng_ts > (o_rec).M_DT::DATE - INTERVAL '13 month') AND ((o_rec).ROLL_FL = 'Y')) THEN
                    o_rec.M_DT := (o_rec).M_DT::DATE + INTERVAL '12 month';

                    IF (o_rec).c_cd = 'R' THEN
                        o_rec.C_END_DT := (o_rec).M_DT::DATE - INTERVAL '12 month';
                    END IF;
                END IF;

                IF (((o_rec).c_cd NOT IN ('E', 'R')) AND (rec.chng_ts > (o_rec).M_DT::DATE - INTERVAL '7 month') AND ((o_rec).ROLL_FL = 'Y')) THEN
                    o_rec.M_DT :=(o_rec).M_DT::DATE + INTERVAL '12 month';
                END IF;

                IF (((o_rec).c_cd = 'E') AND (rec.chng_ts > (o_rec).M_DT::DATE - INTERVAL '12 month') AND ((o_rec).ROLL_FL = 'N')) THEN
                    o_rec.c_cd := 'DBT';
                END IF;
            END IF;

            IF ((rec.c_type_cd LIKE 'SUB%' AND rec.c_type_cd NOT LIKE '%-AMNDT%') OR (rec.event_id IS NULL AND rec.c_type_cd IS NULL)) THEN
                o_rec.s_id := rec.s_id;
                o_rec.c_nb := rec.c_nb;
                o_rec.f_nm := rec.f_nm;
                o_rec.f_c_nm := rec.f_c_nm;
                o_rec.f_c_e_tx := rec.f_c_e_tx;
                o_rec.f_c_t_t := rec.f_c_t_t;
                o_rec.f_a_t := rec.f_a_t;
                o_rec.f_c_nm := rec.f_c_nm;
                o_rec.f_s_c := rec.f_s_c;
                o_rec.f_z_t := rec.f_z_t;
                o_rec.g_s_t := rec.g_s_t;
                o_rec.p_a := rec.p_a;
                o_rec.a_b_a := rec.a_b_a;
                o_rec.e_dt := rec.e_dt;
                o_rec.M_DT := rec.M_DT;
                o_rec.t_nb := rec.t_nb;
                o_rec.i_t_cd := rec.i_t_cd;
                o_rec.i_r := rec.i_r;
                o_rec.i_r_tx := rec.i_r_tx;
                o_rec.i_f_tx := rec.i_f_tx;
                o_rec.c_cd := rec.c_cd;
                o_rec.s_t_cd := rec.s_t_cd;
                o_rec.s_s_cd := rec.s_s_cd;
                o_rec.ROLL_FL := rec.ROLL_FL;
                o_rec.C_END_DT := rec.C_END_DT;
                o_rec.f_m_s_l_fl := rec.f_m_s_l_fl;
                o_rec.p_p_p_fl := rec.p_p_p_fl;
                o_rec.a_m_o_e_fl := rec.a_m_o_e_fl;
                o_rec.e_tx := rec.e_tx;
                o_rec.e_l_fl := rec.e_l_fl;
                o_rec.r_u_u_fl := rec.r_u_u_fl;
                o_rec.r_p_fl := rec.r_p_fl;
                o_rec.n_o_n := rec.n_o_n;
                o_rec.n_a := rec.n_a;
                o_rec.n_s_d := rec.n_s_d;
                o_rec.n_f_t := rec.n_f_t;
                o_rec.a_m_o_e_fl := rec.a_m_o_e_fl;
                o_rec.e_tx := rec.e_tx;
                o_rec.s_a_i_a_fl := rec.s_a_i_a_fl;
                o_rec.l_r_t_a_m_fl := rec.l_r_t_a_m_fl;
                o_rec.d_o_fl := rec.d_o_fl;
                o_rec.o_c_nb := rec.o_c_nb;
                o_rec.d_cmt_tx := rec.d_cmt_tx;
                o_rec.f_cmt_tx := rec.f_cmt_tx;

                IF rec.name LIKE 'N L A' THEN
                    IF rec.stts LIKE 'S T F A' THEN
                        o_rec.s_s_cd := 'Approved';
                        o_rec.e_dt := COALESCE(rec.mrdt_sbl_efctv_dt::date, rec.task_ts::date);
                        o_rec.M_DT := (o_rec).e_dt + ((o_rec).t_nb::NUMERIC || ' days')::INTERVAL;
                        o_rec.d_o_fl := rec.m_d_o_fl;
                        IF (o_rec).c_cd = 'R' THEN
                            o_rec.C_END_DT := (o_rec).M_DT::DATE - INTERVAL '12 month';
                        END IF;
                    ELSIF rec.stts LIKE 'D' THEN
                        o_rec.s_s_cd := 'D';
                    ELSIF rec.stts LIKE 'W' THEN
                        o_rec.s_s_cd := 'W';
                    ELSIF rec.stts LIKE 'S C' THEN
                        o_rec.s_s_cd := 'C';
                    ELSE
                        o_rec.s_s_cd := 'S';
                    END IF;
                END IF;
            ELSIF ((rec.event_id IS NOT NULL AND rec.c_type_cd = 'S-R-A' AND rec.lfcyc_stts_ds = 'A') OR (rec.event_id IS NULL AND rec.c_type_cd = 'S-R-A'))
            THEN
                IF (rec.M_DT IS NOT NULL) THEN
                    o_rec.M_DT := rec.M_DT;
                     o_rec.t_nb :=  extract(epoch from age((o_rec).M_DT, (o_rec).e_dt))/3600;

                    IF (rec.ROLL_FL IS NOT NULL) THEN
                        o_rec.ROLL_FL := rec.ROLL_FL;
                    END IF;

                    IF (o_rec).c_cd = 'R' THEN
                        o_rec.C_END_DT := (o_rec).M_DT::DATE - INTERVAL '12 month';
                    END IF;
                END IF;

                IF (rec.i_t_cd IS NOT NULL) THEN
                    o_rec.i_t_cd := rec.i_t_cd;
                    o_rec.i_r := rec.i_r;
                    o_rec.i_r_tx := rec.i_r_tx;
                    o_rec.i_f_tx := rec.i_f_tx;
                END IF;
            ELSIF rec.event_id IS NULL
            THEN
                NULL;
            END IF;


        IF (o_rec).s_id IS NOT NULL
        THEN
            IF ((o_rec).s_s_cd = 'A')
            THEN
                IF (((o_rec).c_cd IN ('E', 'R')) AND (LEAST(current_date, (o_rec).M_DT::date) > (o_rec).M_DT::DATE - INTERVAL '13 month') AND ((o_rec).ROLL_FL = 'Y')) THEN
                    o_rec.M_DT := (o_rec).M_DT::DATE + INTERVAL '12 month';

                    IF (o_rec).c_cd = 'R' THEN
                        o_rec.C_END_DT := (o_rec).M_DT::DATE - INTERVAL '12 month';
                    END IF;
                END IF;

                IF (((o_rec).c_cd NOT IN ('E', 'R')) AND (LEAST(current_date, (o_rec).M_DT::date) > (o_rec).M_DT::DATE - INTERVAL '7 month') AND ((o_rec).ROLL_FL = 'Y')) THEN
                    o_rec.M_DT := (o_rec).M_DT::DATE + INTERVAL '12 month';
                END IF;

                IF (((o_rec).c_cd = 'E') AND (LEAST(current_date, (o_rec).M_DT::date) > (o_rec).M_DT::DATE - INTERVAL '12 month') AND ((o_rec).ROLL_FL = 'N')) THEN
                    o_rec.c_cd := 'DBT';
                END IF;
            END IF;

        END IF;
    END LOOP;        
    RETURN next o_rec;
END;
$BODY$
LANGUAGE  plpgsql;

Затем я вызываю функцию, используя:

select * from get_s_fn(ARRAY(select v_nb::integer from p where p_s_d_p_id in( 20017, 21021, 22017)), now()::timestamp);

Работает нормально, но возвращает только 1 строку. Я хочу, чтобы он мог возвращать несколько строк. Я где-то читал, что составные типы занимают только первую строку, а затем отбрасывают остальные, есть ли способ обойти это? Я ищу ответ, который не требует от меня слишком больших изменений, у меня есть множество этих функций, которые мне нужно преобразовать, и я хочу изменить как можно меньше.

В следующей части я пытаюсь использовать оператор выбора с несколькими строками в качестве параметра IN для типа или курсора. Вот функция, которую я преобразовал (я не проверял, действительно ли она выполняется, потому что она не принимает несколько строк):

create type get_a_fn_type_in as(
s integer,
efctv_dt timestamp without time zone,
p_a float,
i_f_t text,
m_e_dt timestamp without time zone
);

CREATE OR REPLACE FUNCTION get_a_fn(in_cur get_a_fn_in[], IN in_ts TIMESTAMP WITHOUT TIME ZONE)
RETURNS SETOF s_p_n
AS
$BODY$
DECLARE
    o_rec s_p_o;
    v_s_id integer;
    v_i_f_t text;
    v_efctv_dt timestamp without time zone;
    v_p_a integer;
    v_m_e_dt timestamp without time zone;
    v_m_dt timestamp without time zone;
    v_i_r S_R.i_r%TYPE;
    v_i_t_cd text;
    v_a_a integer;
    v_a_d timestamp without time zone;
    rec record;
BEGIN
    LOOP
        select * from unnest(in_cur) INTO v_s_id, v_efctv_dt, v_p_a, v_i_f_t, v_m_e_dt;
        EXIT WHEN (NOT FOUND);


            SELECT
                MAX(p_dt)
                INTO STRICT v_a_d
                FROM s_p_r
                WHERE s_id = v_s_id AND pymnt_type_cd = 'I';

        v_a_d := (CASE
            WHEN v_a_d IS NULL THEN v_efctv_dt
            ELSE v_a_d
        END)::TIMESTAMP WITHOUT TIME ZONE;
        v_i_f_t := UPPER(SUBSTR(TRIM(v_i_f_t), 1, 1));

            v_m_dt := NULL;
            v_i_r := NULL;
            v_a_a := NULL;
            v_a_d := (CASE
                WHEN v_i_f_t = 'M' THEN v_a_d + INTERVAL '1 month'
                WHEN v_i_f_t = 'Q' THEN v_a_d + INTERVAL '3 months'
                WHEN v_i_f_t = 'S' THEN v_a_d + INTERVAL '6 months'
                WHEN v_i_f_t = 'A' THEN v_a_d + INTERVAL '12 months'
                WHEN v_i_f_t = 'D' THEN v_a_d + INTERVAL '1 days'
                ELSE NULL
            END)::TIMESTAMP WITHOUT TIME ZONE;

            BEGIN
              SELECT s_fn.mtrty_dt,
                                   s_fn.intrs_rt,
                                   s_fn.intrs_type_cd
                              INTO v_m_dt,
                                   v_i_r ,
                                   v_i_t_cd
                              FROM
                              (select * from get_s_fn((v_s_id), ci_ts))s_fn; 
                BEGIN
                END;
                EXCEPTION
                    WHEN others THEN
                        NULL;
            END;

            IF ((v_a_d <= v_m_dt - INTERVAL '12 months') AND (v_m_e_dt IS NULL OR v_a_d < v_m_e_dt) AND (v_a_d <= in_ts) AND (v_p_a > 0) AND (v_i_t_cd = 'F'))

            THEN
                v_a_a := ((v_p_a * v_i_r) /
                CASE
                    WHEN v_i_f_t = 'M' THEN 12
                    WHEN v_i_f_t = 'Q' THEN 4
                    WHEN v_i_f_t = 'S' THEN 2
                    WHEN v_i_f_t = 'A' THEN 1
                    WHEN v_i_f_t = 'D' THEN 365

                    ELSE NULL
                END)::NUMERIC;
                v_a_a := ROUND(v_a_a, 2);
            ELSE
                EXIT;

            END IF;


            IF (v_a_a > 0) THEN
                RETURN NEXT ARRAY[ROW (v_s_id, 'I', v_a_d, v_a_a, 'Accrual', v_a_d)::s_p_ot];
            END IF;
    END LOOP;
END;
$BODY$
LANGUAGE  plpgsql;

Я звоню, используя

select * from get_a_fn(ARRAY[(select row(id::integer, efctv_dt::timestamp, p_a::float, i_f_tx::text, m_dt::timestamp) from s_raw)]::get_a_fn_type_in[], current_timestamp::timestamp)

И я получаю сообщение об ошибке: невозможно привести тип записи к get_a_fn_type_in. Так что проблема похожа на выше, за исключением того, что я хочу использовать несколько строк для in.

В обоих случаях я изучал использование курсоров, но я не слишком знаком с ними. Кроме того, для примеров функций, которые использовали курсоры, все они использовали курсор с оператором select внутри фактической функции, тогда как я хочу передать курсор в качестве параметра. Опять же, я хочу изменить как можно меньше, потому что есть много функций, которые я должен преобразовать, которые следуют подобному образцу. Я где-то читал о создании временной таблицы для хранения значений, но это не то, что я могу сделать, если это не может быть каким-то образом сделано внутри функции. Функция должна работать из простого выбора * из функции (a, b);

Ответы [ 2 ]

0 голосов
/ 20 ноября 2018

Я закончил тем, что превратил входные данные в строку, и моя функция взяла строку, а затем выполнила ее.

так:

select * from function_ex('select 1', current_timestamp::timestamp);

тогда внутри функции, которую я сделал

execute in_cur
0 голосов
/ 09 ноября 2018

Ваша процедура довольно длинная и плохо читаемая - и проблема, вероятно, связана с плохой структурой. Строка создается командой RETURN NEXT. Когда я читаю первый пример, то эта команда выполняется только один раз, потому что она находится вне тела цикла. Смотрите конец вашей функции

    END LOOP;        
    RETURN next o_rec;
END;
$BODY$
LANGUAGE  plpgsql;

Некоторые заметки. Использование бесполезных скобок плохо, а читаемость кода меньше.

   IF (o_rec).s_id IS NOT NULL
   THEN
        IF ((o_rec).s_s_cd = 'A')
        THEN

Просто напишите

   IF o_rec.s_id IS NOT NULL
   THEN
        IF o_rec.s_s_cd = 'A'
        THEN

Второй пример - строки

 LOOP
        select * from unnest(in_cur) INTO v_s_id, v_efctv_dt, v_p_a, v_i_f_t, v_m_e_dt;
        EXIT WHEN (NOT FOUND);

не имеет никакого смысла. Если вы хотите перебрать массив, используйте оператор FOREACH IN ARRAY. Сообщение об ошибке «невозможно привести запись типа к get_a_fn_type_in» - возможно, вам следует сначала преобразовать запись в get_a_fn_type_in, а затем создать массив. Приведение к get_a_fn_type_in [] вне возможностей PostgreSQL (или, возможно, у вас неправильное количество столбцов, типов).

Запрос select * from get_a_fn(ARRAY[(select row(id::integer, efctv_dt::timestamp, p_a::float, i_f_tx::text, m_dt::timestamp) from s_raw)]::get_a_fn_type_in[], current_timestamp::timestamp)

Я создал функцию fx(get_a_fn_type_in[]) и таблицу foo с необходимыми столбцами:

postgres=# select fx(ARRAY[(select row(id::integer, efctv_dt::timestamp, p_a::float, i_f_tx::text, m_dt::timestamp) from foo)]::get_a_fn_type_in[]);
ERROR:  cannot cast type record to get_a_fn_type_in
LINE 1: select fx(ARRAY[(select row(id::integer, efctv_dt::timestamp...
                    ^

Итак, я перенес кастинг на вложенный выбор, и теперь все нормально:

postgres=# select fx(ARRAY[(select row(id, efctv_dt, p_a, i_f_tx, m_dt)::get_a_fn_type_in from foo)]);
┌────┐
│ fx │
╞════╡
│    │
└────┘
(1 row)
...