Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / PostgreSQL Новый топик    Ответить
 Использование anyelement в функции  [new]
sdkmaster
Member

Откуда:
Сообщений: 18
Всем привет!

Кто подскажет, как присвоить значение колонки для типа данных anyelement?

Ситуация следующая:
  • есть простая таблица
  • на нее навешен триггер на апдейт
  • в триггерной функции мы вызываем функцию сравнения NEW и OLD
  • функция сравнения, должна вернуть строку в которой все поля NULL, кроме тех, что отличаются.

create table public.firdata (
    ida integer,
    city text
);

create trigger tr_firdata
after update on public.firdata
for each row execute procedure arc.chacha();

create schema arc;

create function arc.chacha() returns trigger as $$
declare
    rec record;
begin   
    if (tg_op = 'UPDATE') then
        rec = arc.maker(NEW, OLD);
        -- запишем значение rec в таблицу аудита...
        return NEW;
    end if;
end;
$$ language plpgsql;

create function arc.maker(in NEW anyelement, in OLD anyelement, out RET anyelement) as $$
declare
begin
    -- pg_typeof(NEW) == pg_typeof(OLD) == pg_typeof(RET) == firdata - типы определяются верно.

    raise notice 'old.city = %', OLD.city; -- обращение по названию колонки работает корректно, выводится правильное значение

    RET.city = 'something'; -- попытка записать значение приводит к ошибке - 'ERROR:  "ret.city" is not a known variable'
end;
$$ language plpgsql;


Проблема в следующем - невозможно присвоить значение для столбца city переменной RET.
Постгрес ругается, что такого нет - вроде правильно, ведь указан тип anyelement. Но это полиморфный тип и в рантайме он известен - это тип public.firdata.
Как правильно записать значение в переменную RET ???

Сообщение было отредактировано: 10 янв 20, 16:07
10 янв 20, 16:04    [22056620]     Ответить | Цитировать Сообщить модератору
 Re: Использование anyelement в функции  [new]
Guzya
Member

Откуда:
Сообщений: 405
Не уверен, что anyelement можно возвращать.
10 янв 20, 16:55    [22056669]     Ответить | Цитировать Сообщить модератору
 Re: Использование anyelement в функции  [new]
sdkmaster
Member

Откуда:
Сообщений: 18
Guzya
Не уверен, что anyelement можно возвращать.

Можно. Следующий код работает и возвращает все как надо.
create function arc.maker(in NEW anyelement, in OLD anyelement, out RET anyelement) as $$
declare
begin
    RET = NEW; -- работает, вызывающая функция получает запись RET со значениями равными значениям записи NEW
               -- но присвоить RET.city = 'something' не выходит
end;
$$ language plpgsql;
10 янв 20, 17:10    [22056688]     Ответить | Цитировать Сообщить модератору
 Re: Использование anyelement в функции  [new]
Guzya
Member

Откуда:
Сообщений: 405
А так
RET = NEW;
RET.city = 'something';
10 янв 20, 22:22    [22056903]     Ответить | Цитировать Сообщить модератору
 Re: Использование anyelement в функции  [new]
Troglodit
Member

Откуда:
Сообщений: 488
CREATE FUNCTION add_three_values(v1 anyelement, v2 anyelement, v3 anyelement)
RETURNS anyelement AS $$
DECLARE
       result ALIAS FOR $0;
BEGIN
    result := v1 + v2 + v3; (*)
    RETURN result;
END;
$$ LANGUAGE plpgsql;

Вот так не пробовали? Это из доки.
В вашем случае в (*) result.city:='test';

Единственное не понял, зачем вам anyelement, если в коде явно указан record. Почему использовать этот тип?

Сообщение было отредактировано: 10 янв 20, 22:36
10 янв 20, 22:30    [22056910]     Ответить | Цитировать Сообщить модератору
 Re: Использование anyelement в функции  [new]
sdkmaster
Member

Откуда:
Сообщений: 18
Troglodit
Единственное не понял, зачем вам anyelement, если в коде явно указан record. Почему использовать этот тип?

Anyelement нужен, чтобы иметь одну функцию аудита для разных таблиц. Используя тип anyelement, я могу прокидывать в функцию arc.maker строки любой таблицы. Это удобно и этим надо пользоваться, а не плодить однотипные функции.

Сообщение было отредактировано: 14 янв 20, 09:51
14 янв 20, 09:51    [22058531]     Ответить | Цитировать Сообщить модератору
 Re: Использование anyelement в функции  [new]
sdkmaster
Member

Откуда:
Сообщений: 18
Нашел временное решение:
create function arc.maker(in NEW anyelement, in OLD anyelement, out RET anyelement) as $$
declare
    rec record;
begin
    rec = RET; -- rec получает тот же тип, что и RET, плюс все колонки изначально выставлены в NULL

    -- 
    -- заполняем, изменяем rec
    -- 

    RET = rec; -- присваиваем значение возращаемой переменной RET
end;
$$ language plpgsql;

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

Сообщение было отредактировано: 14 янв 20, 09:58
14 янв 20, 09:58    [22058541]     Ответить | Цитировать Сообщить модератору
 Re: Использование anyelement в функции  [new]
entrypoint
Member

Откуда:
Сообщений: 166
sdkmaster,


Это точно работает ? Как Вы вызываете функцию ?
Переменные типа record похожи на переменные строкового типа, но они не имеют предопределённой структуры. Они приобретают фактическую структуру от строки, которая им присваивается командами SELECT или FOR. Структура переменной типа record может меняться каждый раз при присвоении значения
https://postgrespro.ru/docs/postgresql/9.5/plpgsql-declarations#plpgsql-declaration-records

а в Вашем случае значение переменной RET - не определено, значит не определено и значение переменной rec, что приведёт к ошибке
ERROR:  record "ret" is not assigned yet
DETAIL:  The tuple structure of a not-yet-assigned record is indeterminate.
CONTEXT:  SQL statement "SELECT RET"
PL/pgSQL function inline_code_block line 6 at assignment
SQL-состояние: 55000 

в этом
DO $$
DECLARE 
	rec RECORD;
	RET RECORD;
BEGIN 	
    rec = RET; 
    RAISE NOTICE '%', RET; 	
END; 
$$ language plpgsql;

в этом
DO $$
DECLARE 
	rec RECORD;
	RET RECORD;
BEGIN 	
    SELECT rec INTO RET; 
    RAISE NOTICE '%', RET; 
END; 
$$ language plpgsql;

и в этом
DO $$
DECLARE 
	rec RECORD;
	RET ALIAS FOR rec;
BEGIN 	
    RAISE NOTICE '%', RET; 
END; 
$$ language plpgsql;

случаях
14 янв 20, 11:42    [22058634]     Ответить | Цитировать Сообщить модератору
 Re: Использование anyelement в функции  [new]
sdkmaster
Member

Откуда:
Сообщений: 18
entrypoint
Это точно работает ? Как Вы вызываете функцию ?

Все отлично работает :)

Вы упустили важный момент - функцию arc.maker дергает триггерная функция arc.chacha, которая передает записи NEW и OLD. А т.к. все anyelement в полиморфной функции должны быть одного(!) типа, то OUT переменная RET "наследует" тип IN переменных NEW и OLD. В первом сообщении описаны функции arc.chacha и arc.maker.

Я передаю RET как "out RET anyelement", а в ваших примерах вы везде определяете ее как record, отсюда и ошибка.

Сообщение было отредактировано: 14 янв 20, 12:41
14 янв 20, 12:39    [22058698]     Ответить | Цитировать Сообщить модератору
 Re: Использование anyelement в функции  [new]
entrypoint
Member

Откуда:
Сообщений: 166
sdkmaster,
ОК )))))
14 янв 20, 13:11    [22058723]     Ответить | Цитировать Сообщить модератору
 Re: Использование anyelement в функции  [new]
entrypoint
Member

Откуда:
Сообщений: 166
sdkmaster,

sdkmaster
entrypoint
Это точно работает ? Как Вы вызываете функцию ?

Все отлично работает :)

Вы упустили важный момент - функцию arc.maker дергает триггерная функция arc.chacha, которая передает записи NEW и OLD. А т.к. все anyelement в полиморфной функции должны быть одного(!) типа, то OUT переменная RET "наследует" тип IN переменных NEW и OLD. В первом сообщении описаны функции arc.chacha и arc.maker.

Я передаю RET как "out RET anyelement", а в ваших примерах вы везде определяете ее как record, отсюда и ошибка.


всё же, если как у автора то,
create table public.firdata (
    ida integer,
    city text
);

create schema arc;

create function arc.maker(in NEW anyelement, in OLD anyelement, out RET anyelement) as $$
declare
    rec record;
begin
    rec = RET; 
    RET = rec; 
    RET.city = 'something'; -- !!!!!!!
end;
$$ language plpgsql;
, то
ERROR:  "ret.city" is not a known variable
LINE 14:  RET.city = 'something';
          ^
SQL-состояние: 42601
Символ: 239 


а если так, то
create table public.firdata (
    ida integer,
    city text
);
INSERT INTO public.firdata VALUES(1,2);

create schema arc;

create function arc.maker(in NEW anyelement, in OLD anyelement, out RET anyelement) as $$
declare
    rec record;
begin
    rec = RET; 
    RET = rec; 
end;
$$ language plpgsql;

create function arc.chacha() returns trigger as $$
declare
    rec record;
begin   
    if (tg_op = 'UPDATE') then
        rec = arc.maker(NEW, OLD);
        -- запишем значение rec в таблицу аудита...
		INSERT INTO public.firdata (ida, city) VALUES(rec.ida, rec.city); -- !!!!!!!!!
        return NEW;
    end if;
end;
$$ language plpgsql;

create trigger tr_firdata
after update on public.firdata
for each row execute procedure arc.chacha();

UPDATE public.firdata SET city = 'gggg';

SELECT * FROM public.firdata;
, то нет значений

Что я не так делаю ?!!!!!


К сообщению приложен файл. Размер - 3Kb


Сообщение было отредактировано: 14 янв 20, 13:56
14 янв 20, 13:47    [22058752]     Ответить | Цитировать Сообщить модератору
 Re: Использование anyelement в функции  [new]
entrypoint
Member

Откуда:
Сообщений: 166
Всё
Работает вот так

create table public.firdata (
    ida integer,
    city text
);
INSERT INTO public.firdata VALUES(1,2);

create schema arc;

create function arc.maker(in NEW anyelement, in OLD anyelement, out RET anyelement) as $$
begin
	SELECT NEW.ida, 'something' AS city INTO RET; -- !!!!!!
end;
$$ language plpgsql;

create function arc.chacha() returns trigger as $$
declare
    rec record;
begin   
    if (tg_op = 'UPDATE') then
        rec = arc.maker(NEW, OLD);
        -- запишем значение rec в таблицу аудита...
		INSERT INTO public.firdata (ida, city) VALUES(rec.ida, rec.city); -- !!!!!!!!!
        return NEW;
    end if;
end;
$$ language plpgsql;

create trigger tr_firdata
after update on public.firdata
for each row execute procedure arc.chacha();

UPDATE public.firdata SET city = 'gggg';

SELECT * FROM public.firdata;


Сообщение было отредактировано: 14 янв 20, 14:16
14 янв 20, 14:15    [22058798]     Ответить | Цитировать Сообщить модератору
 Re: Использование anyelement в функции  [new]
Troglodit
Member

Откуда:
Сообщений: 488
sdkmaster
Troglodit
Единственное не понял, зачем вам anyelement, если в коде явно указан record. Почему использовать этот тип?

Anyelement нужен, чтобы иметь одну функцию аудита для разных таблиц. Используя тип anyelement, я могу прокидывать в функцию arc.maker строки любой таблицы. Это удобно и этим надо пользоваться, а не плодить однотипные функции.

Все ваши входящие параметры и есть record.
Либо я может путаю, но в триггере NEW и OLD и есть record.
Так что мне кажется вы перемудрили.

Когда функция на PL/pgSQL срабатывает как триггер, в блоке верхнего уровня автоматически создаются несколько специальных переменных:

NEW
Тип данных RECORD. Переменная содержит новую строку базы данных для команд INSERT/UPDATE в триггерах уровня строки. В триггерах уровня оператора и для команды DELETE эта переменная имеет значение null.

OLD
Тип данных RECORD. Переменная содержит старую строку базы данных для команд UPDATE/DELETE в триггерах уровня строки. В триггерах уровня оператора и для команды INSERT эта переменная имеет значение null.

Доку с про.

Сообщение было отредактировано: 14 янв 20, 19:10
14 янв 20, 19:08    [22059151]     Ответить | Цитировать Сообщить модератору
Все форумы / PostgreSQL Ответить