Пример использования Qbim со связанными объектами

Установка расширения

create extension qbim cascade;

Создание тестовых объектов

В данном примере рассматривается реализация с двумя объектами, связанными между собой.

Для описания связи будет использоваться составной первичный ключ <id, sd, ed> в одной таблице, и «тройка» <r_position, sd, ed> во второй. Записи (версии) будут считаться связанными, если первое поле в указанных тройках совпадает, а интервалы, создаваемые оставшимися двумя значениями, пересекаются.

Механизмы описания связей (foreing key) в темпоральной парадигме пока не позволяют сделать это на уровне задания ограничений таблиц.

Примечание
Но такое задание связи, вероятно, будет реализовано в следующих версиях QHB.

Моделируется предметная область — кадровый учет. Создаются два объекта: «персона» и «должность». Для демонстрации возможностей Qbim атрибутивный состав объектов сокращен.

create table person (
	id bigserial not null,
	sd timestamp not null default current_timestamp,
	ed timestamp not null default 'infinity'::timestamp,
	last_name varchar(50) not null,
	first_name varchar(50) not null,
	patronym varchar(50),
	r_position bigint not null,
	constraint person_id_sd_ed_excl exclude using gist (id with =, tsrange(sd, ed) with &&) where ((sd <> ed))
);
comment on table person is 'Персона'; 
comment on column person.id is 'Идентификатор';
comment on column person.sd is 'Время начала действия версии';
comment on column person.ed is 'Время окончания действия версии';
comment on column person.last_name is 'Фамилия';
comment on column person.first_name is 'Имя';
comment on column person.patronym is 'Отчество';
comment on column person.r_position is 'Ссылка на должность (OBJ#2)';

create table position (
	id bigserial not null,
	sd timestamp not null default current_timestamp,
	ed timestamp not null default 'infinity'::timestamp,
	position_name varchar(100) not null,
	constraint position_id_sd_ed_excl exclude using gist (id with =, tsrange(sd, ed) with &&) where ((sd <> ed))
);
comment on table position is 'Должность'; 
comment on column position.id is 'Идентификатор';
comment on column position.sd is 'Время начала действия версии';
comment on column position.ed is 'Время окончания действия версии';
comment on column position.position_name is 'Название доллжности';

Описание в таблицах описания объектов

select qbim_describe_object(i_table_name => 'person'::text, i_obj_id => 1::bigint);
select qbim_describe_object(i_table_name => 'position'::text, i_obj_id => 2::bigint);

select setval('obj_id_seq',2);

Создание реализаций объектов

Примечание
Здесь и далее при вызове интерфейсных методов указывается случайный идентификатор пользователя '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid, который используется при регистрации действия.

Должности. Создаем три должности с 1 января 2021 года:

select qbim_create(2::bigint, null::text, null::bigint,
  '2021-01-01 00:00:00.0'::timestamp, null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'position_name','Инженер');

select qbim_create(2::bigint, null::text, null::bigint,  
  '2021-01-01 00:00:00.0'::timestamp, null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'position_name','Старший инженер');

select qbim_create(2::bigint, null::text, null::bigint,  
  '2021-01-01 00:00:00.0'::timestamp, null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'position_name','Менеджер');

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

select qbim_create(1::bigint, null::text, null::bigint,
  '2021-01-11 00:00:00.0'::timestamp, null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'last_name','Начальников',
  'first_name','Игорь',
  'patronym','Петрович',
  'r_position',3);
select qbim_create(1::bigint, null::text, null::bigint,
  '2021-01-12 00:00:00.0'::timestamp, null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'last_name','Половинкин',
  'first_name','Семен',
  'patronym','Андреевич',
  'r_position',2);
select qbim_create(1::bigint, null::text, null::bigint,
  '2021-01-15 00:00:00.0'::timestamp, null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'last_name','Иванов',
  'first_name','Петр',
  'patronym','Васильевич',
  'r_position',1);
select qbim_create(1::bigint, null::text, null::bigint,
  '2021-01-16 00:00:00.0'::timestamp, null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'last_name','Малкин',
  'first_name','Иван',
  'patronym','Олегович',
  'r_position',2);
select qbim_create(1::bigint, null::text, null::bigint,
  '2021-01-17 00:00:00.0'::timestamp, null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'last_name','Селиванов',
  'first_name','Илья',
  'patronym','Максимович',
  'r_position',1);
select qbim_create(1::bigint, null::text, null::bigint,
  '2021-01-18 00:00:00.0'::timestamp, null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'last_name','Васин',
  'first_name','Сергей',
  'patronym','Петрович',
  'r_position',1);

Выведем обобщенную информацию по подразделению. В качестве интервалов выводим минимальные/максимальные границы интервалов. Условие tsrange(p.sd,p.ed) && tsrange(d.sd,d.ed) означает пересечение интервалов [p.sd,p.ed) и [d.sd,d.ed) соответственно.


Запрос обобщенной информации

select
	p.id,
	greatest(p.sd,d.sd) as sd,
	least(p.ed,d.ed) as ed,
	p.last_name||' '||p.first_name||' '||p.patronym as "ФИО",
	d.position_name as "Должность"
from person p join position d
	on p.r_position = d.id and tsrange(p.sd,p.ed) && tsrange(d.sd,d.ed)
order by 1,2;

Результат:

idsdedФИОДолжность
12021-01-11 00:00:00infinityНачальников Игорь ПетровичМенеджер
22021-01-12 00:00:00infinityПоловинкин Семен АндреевичСтарший инженер
32021-01-15 00:00:00infinityИванов Петр ВасильевичИнженер
42021-01-16 00:00:00infinityМалкин Иван ОлеговичСтарший инженер
52021-01-17 00:00:00infinityСеливанов Илья МаксимовичИнженер
62021-01-18 00:00:00infinityВасин Сергей ПетровичИнженер

Запрос информации «на дату»

Условие '2021-02-15 00:00:00.0'::timestamp <@ tsrange(t.sd,t.ed) означает, что указанный момент времени должен содержаться внутри интервала.

select * from (
	select
		p.id,
		greatest(p.sd,d.sd) as sd,
		least(p.ed,d.ed) as ed,
		p.last_name||' '||p.first_name||' '||p.patronym as "ФИО",
		d.position_name as "Должность"
	from person p join position d
		on p.r_position = d.id and tsrange(p.sd,p.ed) && tsrange(d.sd,d.ed)
) t
where '2021-02-15 00:00:00.0'::timestamp <@ tsrange(t.sd,t.ed)
order by 1,2;

Результат выглядит одинаково:

idsdedФИОДолжность
12021-01-11 00:00:00infinityНачальников Игорь ПетровичМенеджер
22021-01-12 00:00:00infinityПоловинкин Семен АндреевичСтарший инженер
32021-01-15 00:00:00infinityИванов Петр ВасильевичИнженер
42021-01-16 00:00:00infinityМалкин Иван ОлеговичСтарший инженер
52021-01-17 00:00:00infinityСеливанов Илья МаксимовичИнженер
62021-01-18 00:00:00infinityВасин Сергей ПетровичИнженер

Изменения

С 1 марта сотрудник с id=5 был переведен на другую должность:

select qbim_change(1::bigint,null::text,5::bigint,
  '2021-03-01 00:00:00.0'::timestamp,null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'r_position',2::bigint
);

Выведем моментальные снимки на даты 28 февраля и 1 марта:

select * from (
	select
		p.id,
		greatest(p.sd,d.sd) as sd,
		least(p.ed,d.ed) as ed,
		p.last_name||' '||p.first_name||' '||p.patronym as "ФИО",
		d.position_name as "Должность"
	from person p join position d
		on p.r_position = d.id and tsrange(p.sd,p.ed) && tsrange(d.sd,d.ed)
) t
where '2021-02-28 00:00:00.0'::timestamp <@ tsrange(t.sd,t.ed)
order by 1,2;

select * from (
	select
		p.id,
		greatest(p.sd,d.sd) as sd,
		least(p.ed,d.ed) as ed,
		p.last_name||' '||p.first_name||' '||p.patronym as "ФИО",
		d.position_name as "Должность"
	from person p join position d
		on p.r_position = d.id and tsrange(p.sd,p.ed) && tsrange(d.sd,d.ed)
) t
where '2021-03-01 00:00:00.0'::timestamp <@ tsrange(t.sd,t.ed)
order by 1,2;

Результаты различаются. Сотрудник в первом случае имеет одну должность, а во втором — другую.

Результат на 28 февраля:

idsdedФИОДолжность
12021-01-11 00:00:00infinityНачальников Игорь ПетровичМенеджер
22021-01-12 00:00:00infinityПоловинкин Семен АндреевичСтарший инженер
32021-01-15 00:00:00infinityИванов Петр ВасильевичИнженер
42021-01-16 00:00:00infinityМалкин Иван ОлеговичСтарший инженер
52021-01-17 00:00:002021-03-01 00:00:00Селиванов Илья МаксимовичИнженер
62021-01-18 00:00:00infinityВасин Сергей ПетровичИнженер

Результат на 1 марта:

idsdedФИОДолжность
12021-01-11 00:00:00infinityНачальников Игорь ПетровичМенеджер
22021-01-12 00:00:00infinityПоловинкин Семен АндреевичСтарший инженер
32021-01-15 00:00:00infinityИванов Петр ВасильевичИнженер
42021-01-16 00:00:00infinityМалкин Иван ОлеговичСтарший инженер
52021-03-01 00:00:00infinityСеливанов Илья МаксимовичСтарший инженер
62021-01-18 00:00:00infinityВасин Сергей ПетровичИнженер

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

idsdedФИОДолжность
12021-01-11 00:00:00infinityНачальников Игорь ПетровичМенеджер
22021-01-12 00:00:00infinityПоловинкин Семен АндреевичСтарший инженер
32021-01-15 00:00:00infinityИванов Петр ВасильевичИнженер
42021-01-16 00:00:00infinityМалкин Иван ОлеговичСтарший инженер
52021-01-17 00:00:002021-03-01 00:00:00Селиванов Илья МаксимовичИнженер
52021-03-01 00:00:00infinityСеливанов Илья МаксимовичСтарший инженер
62021-01-18 00:00:00infinityВасин Сергей ПетровичИнженер

С 15 марта произошло изменение — название должности «Инженер» изменили на «Младший инженер»:

select qbim_change(2::bigint,null::text,1::bigint,
  '2021-03-15 00:00:00.0'::timestamp,null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'position_name','Младший инженер');

Посмотрим, как изменился вывод обобщенной информации:

idsdedФИОДолжность
12021-01-11 00:00:00infinityНачальников Игорь ПетровичМенеджер
22021-01-12 00:00:00infinityПоловинкин Семен АндреевичСтарший инженер
32021-01-15 00:00:002021-03-15 00:00:00Иванов Петр ВасильевичИнженер
32021-03-15 00:00:00infinityИванов Петр ВасильевичМладший инженер
42021-01-16 00:00:00infinityМалкин Иван ОлеговичСтарший инженер
52021-01-17 00:00:002021-03-01 00:00:00Селиванов Илья МаксимовичИнженер
52021-03-01 00:00:00infinityСеливанов Илья МаксимовичСтарший инженер
62021-01-18 00:00:002021-03-15 00:00:00Васин Сергей ПетровичИнженер
62021-03-15 00:00:00infinityВасин Сергей ПетровичМладший инженер

Видно, что у всех сотрудников, имеющих должность «Инженер», появились новые версии с новым названием должности.


Вариант запроса информации «на дату» для конкретного сотрудника

select
	p.id,
	greatest(p.sd,d.sd) as sd,
	least(p.ed,d.ed) as ed,
	p.last_name||' '||p.first_name||' '||p.patronym as "ФИО",
	d.position_name as "Должность"
from person p join position d
	on p.r_position = d.id     
where p.id=5 and '2021-02-28 00:00:00.0'::timestamp <@ tsrange(p.sd,p.ed)
    and '2021-02-28 00:00:00.0'::timestamp <@ tsrange(d.sd,d.ed)
order by 1,2;
idsdedФИОДолжность
52021-01-17 00:00:002021-03-01 00:00:00Селиванов Илья МаксимовичИнженер

Вариант запроса информации для конкретного сотрудника за весь период

select
	p.id,
	greatest(p.sd,d.sd) as sd,
	least(p.ed,d.ed) as ed,
	p.last_name||' '||p.first_name||' '||p.patronym as "ФИО",
	d.position_name as "Должность"
from person p join position d
	on p.r_position = d.id and tsrange(p.sd,p.ed) && tsrange(d.sd,d.ed)     
where p.id=5
order by 1,2;
idsdedФИОДолжность
52021-01-17 00:00:002021-03-01 00:00:00Селиванов Илья МаксимовичИнженер
52021-03-01 00:00:00infinityСеливанов Илья МаксимовичСтарший инженер

Отражаются версии «до» и «после» перевода на другую должность.


Еще один вариант запроса информации для конкретного сотрудника за весь период

Если уже после смены названия должности перевести сотрудника с id=6 на другую должность:

select qbim_change(1::bigint,null::text,6::bigint,
  '2021-03-20 00:00:00.0'::timestamp,null::timestamp,
  '463404e8-ee5d-4597-b9a9-e3b69e29d18a'::uuid,
  'r_position',2::bigint);

и запросить все версии по этому сотруднику с помощью запроса Вариант запроса информации для конкретного сотрудника за весь период, но с id=6, то в результате отобразятся версии, соответствующие периодам «до» и «после» смены названия должности, а также периоду «после» перевода на другую должность:

idsdedФИОДолжность
62021-01-18 00:00:002021-03-15 00:00:00Васин Сергей ПетровичИнженер
62021-03-15 00:00:002021-03-20 00:00:00Васин Сергей ПетровичМладший инженер
62021-03-20 00:00:00infinityВасин Сергей ПетровичСтарший инженер

Регистрация действий

Поскольку в примере при описании объектов указывалось, что необходимо протоколировать информацию по действиям с объектами (поле obj.is_logging), все указанные в примере создания и изменения объектов отражаются в служебной таблице action.

select
    r_object, lnk_dt, fct_dt, action, pid, userid
from action;
r_objectlnk_dtfct_dtactionpiduserid
2:12021-01-01 00:00:002021-04-30 19:43:24.58678612521463404e8-ee5d-4597-b9a9-e3b69e29d18a
2:22021-01-01 00:00:002021-04-30 19:43:27.35219312521463404e8-ee5d-4597-b9a9-e3b69e29d18a
2:32021-01-01 00:00:002021-04-30 19:44:10.52547712521463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:12021-01-11 00:00:002021-04-30 19:55:28.10955712521463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:22021-01-12 00:00:002021-04-30 19:55:30.36352512521463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:32021-01-15 00:00:002021-04-30 19:55:32.67087612521463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:42021-01-16 00:00:002021-04-30 19:55:34.28393812521463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:52021-01-17 00:00:002021-04-30 19:55:39.77095812521463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:62021-01-18 00:00:002021-04-30 19:55:42.705812521463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:52021-01-17 00:00:002021-04-30 20:43:03.59747932521463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:52021-03-01 00:00:002021-04-30 20:43:03.59767322521463404e8-ee5d-4597-b9a9-e3b69e29d18a
2:12021-01-01 00:00:002021-04-30 20:56:12.48884432521463404e8-ee5d-4597-b9a9-e3b69e29d18a
2:12021-03-15 00:00:002021-04-30 20:56:12.48959222521463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:62021-01-18 00:00:002021-04-30 21:38:37.96677232521463404e8-ee5d-4597-b9a9-e3b69e29d18a
1:62021-03-20 00:00:002021-04-30 21:38:37.96696222521463404e8-ee5d-4597-b9a9-e3b69e29d18a

Значения полей описаны в разделе Описание версионируемых таблиц.

Примечание
Поля fct_dt и pid могут отличаться; в данной таблице они приведены как пример.