Почему NEW.name вернуть значение NULL? PostgreSQL 11

#postgresql #triggers #database-partitioning

#postgresql #триггеры #разделение базы данных

Вопрос:

Я написал функцию, которая должна автоматически создавать новые разделы для таблицы. Я создал триггер, но когда срабатывает триггер и вызывается функция, ничего не происходит, просто появляется ошибка:

 ERROR: query string argument of EXECUTE is null
  

Код функции:

 CREATE FUNCTION public.auto_part()
    RETURNS trigger
    LANGUAGE 'plpgsql'
    COST 100
    VOLATILE NOT LEAKPROOF 
AS $BODY$DECLARE
    partition_date TEXT;
    partition TEXT;
    startdate TEXT;
    enddate TEXT;
    query TEXT;
BEGIN
    partition_date := TO_CHAR(new.created_on,'YYYY-MM');
    startdate := partition_date || '-01';
    enddate := to_char(to_timestamp('YYYY-MM',partition_date)   '1 MONTH'::interval,'YYYY-MM') || '-01';
    
    partition := TG_TABLE_NAME || '_' || partition_date;
    
    IF NOT EXISTS(SELECT relname FROM pg_class WHERE relname=partition) THEN
        RAISE NOTICE 'TABLE %',TG_TABLE_NAME;
        RAISE NOTICE 'ID %',new.id;
        RAISE NOTICE 'NAME %',new.username;
        RAISE NOTICE 'CREATED_ON %',new.created_on;
        RAISE NOTICE 'PARTITION_DATE %',partition_date;
        RAISE NOTICE 'STARTDATE %',startdate;
        RAISE NOTICE 'A partition has been created %',partition;
        EXECUTE 'CREATE TABLE ' || partition || ' PARTITION OF ' || TG_TABLE_NAME || ' FOR VALUES FROM ('|| startdate || ') TO (' || enddate || ');';
        --RAISE NOTICE query;
        --EXECUTE query;
        RETURN 1;
    END IF;
END
$BODY$;
  

Вывод «ПОДНЯТЬ УВЕДОМЛЕНИЕ»:

 NOTICE:  TABLE users_sec
NOTICE:  ID <NULL>
NOTICE:  NAME <NULL>
NOTICE:  CREATED_ON <NULL>
NOTICE:  PARTITION_DATE <NULL>
NOTICE:  STARTDATE <NULL>
NOTICE:  A partition has been created <NULL>

ERROR:  query string argument of EXECUTE is null
CONTEXT:  PL/pgSQL function auto_part() line 22 at EXECUTE
SQL state: 22004
  

Код триггера:

 CREATE TRIGGER auto_part_trigger
    BEFORE INSERT OR UPDATE 
    ON public.users_sec
    FOR EACH STATEMENT
    EXECUTE PROCEDURE public.auto_part();
  

Вставить пример:

 INSERT INTO users_sec(
    username, password, created_on, last_logged_on)
    VALUES (
        'qwerty',
        random_string( 20 ),
        '2021-03-23',
        '2021-03-24'
    );
  

Код создания таблицы:

 CREATE TABLE public.users_sec
(
    id integer NOT NULL DEFAULT nextval('users_sec_id_seq'::regclass) ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
    username text COLLATE pg_catalog."default" NOT NULL,
    password text COLLATE pg_catalog."default",
    created_on timestamp with time zone NOT NULL,
    last_logged_on timestamp with time zone NOT NULL,
    CONSTRAINT users_sec_pkey PRIMARY KEY (id, created_on)
) PARTITION BY RANGE (created_on) 
WITH (
    OIDS = FALSE
)
TABLESPACE pg_defau<
  

Ответ №1:

Если вы хотите NEW содержать строку, которая будет вставлена, вы должны использовать FOR EACH ROW триггер уровня.

Поскольку у вас не может быть BEFORE триггера FOR EACH ROW для секционированной таблицы, это своего рода уловка 22.

Возможный способ, которым мы можем быть такими:

  • Создайте DEFAULT раздел. Все строки, которые не соответствуют существующему разделу, будут вставлены туда.

  • Определите BEFORE триггер FOR EACH ROW в разделе по умолчанию. Триггер создает новый раздел соответствующим образом и вставляет строку в этот раздел. Функция триггера использует RETURN NULL , чтобы избежать вставки чего-либо в сам раздел по умолчанию.

Таким образом, раздел по умолчанию остается пустым. Более того, вам придется оплачивать накладные расходы только за триггер для строк, которые не входят ни в один из существующих разделов!

Комментарии:

1. Я переработал ответ. Прошу прощения за то, что не сразу понял, что речь шла о секционированной таблице.