#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. Я переработал ответ. Прошу прощения за то, что не сразу понял, что речь шла о секционированной таблице.