Temporal Validity

W ostatnim moim projekcie nad którym pracowałem część danych powinna zawierać czasowy znacznik ważności. Mówiąc krótko mamy dane, które posiadają jakąś datę ważności początkową i końcową. W związku z tym chciałem bliżej przyjrzeć się wprowadzonemu w Oracle Database 12c Release 1 (12.1) Temporal Validity.

Zgodnie z dokumentacją Oracle pkt. 1.9.4 Temporal Validity Support pozwala powiązać jeden lub więcej ważnych wymiarów czasowych z tabelą i sprawić, że dane będą widoczne w zależności od ich ważności czasowej, określonej przez datę początkową i końcową lub znaczniki okresu czasu, dla którego dany rekord jest uważany za ważny. Pozwala to na uproszczenie zapytań o efektywne zakresy dat.

Tworzymy tabelę

W celu przetestowania funkcjonalności utworzymy przykładową tabelę emp_example. Na podstawie wstawianych do niej rekordów dokonamy analizy okresu ważności poszczególnych rekordów. Tabela będzie zawierała podstawowe dane o pracowniku oraz znaczniki czasowe ważności rekordu.

create table emp_example (
      id         number generated always as identity increment by 1 start with 1 minvalue 1 nomaxvalue 
    , empno      number
    , first_name varchar2(64)
    , last_name  varchar2(64)
    , start_date timestamp
    , end_date   timestamp
    , constraint my_emp_pk primary key (  id )
    , period for emp_valid_time ( start_date, end_date )
    );

Table EMP_EXAMPLE created.

Po utworzeniu tabeli, automatycznie powstał constraint EMP_VALID_TIMED04C5F z warunkami: (START_DATE < END_DATE) and (EMP_VALID_TIME > 0)

Wstawiamy jeden rekord, który w zupełności wystarczy nam do testów.

insert into emp_example values (default, '101', 'Jaś', 'Fasola', date'2025-04-01', date'2025-04-30');
select * from emp_example;

Test funkcjonalności

Pomyślmy o datach w odniesieniu do osi czasu. Jest to najbardziej efektywne w przypadku pytań o nakładanie się zakresów dat i można je łatwo zwizualizować za pomocą diagramu takiego jak ten poniżej. Mamy oś czasu oraz dane w tabeli, które określają jakąś data początkową i data końcową. Może to być np. data trwania umowy, data ważności uprawnień, wszystko to zależy od założeń biznesowych.

Załóżmy, że Jaś Fasola ma uprawnienia do jakieś systemu co w naszej tabeli reprezentuje rekord z datą początkową 1 kwietnia 2025 i datą końcową 30 kwietnia 2025 roku. Chcemy teraz sprawdzić okres ważności w określonym punkcie czasu.

Poniżej przedstawiam pełen zestaw próbkowanych dat:

Aby wyświetlić dane pracownika, który jest aktywny w określonym punkcie czasu, używamy następującego zapytania: select * from emp_example as of period for emp_valid_time sysdate;

Sprawdźmy jakie uzyskamy rezultaty w poszczególnych punktach czasowych.

--------------------------------------------------------------------------------------------------------
--  Przypadek 1, punkt czasowy przekroczył zakres dat z tabeli
--    +------------+
--                    +
select * from emp_example as of period for emp_valid_time date'2025-05-01';

-- no rows selected

--------------------------------------------------------------------------------------------------------
--  Przypadek 2, punkt czasowy jest równy end_date
--    +------------+
--                 +
select * from emp_example as of period for emp_valid_time date'2025-04-30';

-- no rows selected
-- wygląda na to że zbiór wartości spełniający warunek jest z prawej strony niedomknięty
-- graniczna data 2025-04-30, nie spełnia warunku

--------------------------------------------------------------------------------------------------------
--  Przypadek 3, punkt czasowy zawiera się w przedziale dat
--    +------------+
--               +
select * from emp_example as of period for emp_valid_time date'2025-04-20';

 ID      EMPNO FIRST_NAME  LAST_NAME  START_DATE                    END_DATE                     
--- ---------- ----------- ---------- ----------------------------- -----------------------------
  1        101 Jaś         Fasola     2025-04-01 00:00:00,000000000 2025-04-30 00:00:00,000000000

--------------------------------------------------------------------------------------------------------
--  Przypadek 4, punkt czasowy jest równy dacie początkowej
--    +------------+
--    +
select * from emp_example as of period for emp_valid_time date'2025-04-01';

 ID      EMPNO FIRST_NAME  LAST_NAME  START_DATE                    END_DATE                     
--- ---------- ----------- ---------- ----------------------------- -----------------------------
  1        101 Jaś         Fasola     2025-04-01 00:00:00,000000000 2025-04-30 00:00:00,000000000

--------------------------------------------------------------------------------------------------------
--  Przypadek  5, punkt czasowy jest przed datą początkową
--    +------------+
--  +
select * from emp_example as of period for emp_valid_time date'2025-03-31';

-- no rows selected

Po analizie dochodzimy do wniosku, że odpowiednikiem dla naszego zapytania

select * from emp_example as of period for emp_valid_time sysdate;

jest

select * from emp_example where sysdate >= start_date and sysdate < end_date;

Zapytanie zwraca rekordy jeżeli punkt czasowy zawiera się w przedziale [2025-04-01 , 2025-04-30), inaczej mówiąc lewostronnie domkniętym i prawostronnie otwartym. Wniosek z tego jest taki, że pracownik posiada uprawnienia od północy 1 kwietnia i traci je dokładnie o północy 30 kwietnia. Jeżeli pracownik miałby mieć uprawnienia do końca dnia 30 kwietnia, to end_date powinno wskazywać na północ w dniu 1 maja 2025.

Prześledziliśmy jeden punkt czasu, ale zweryfikujemy jeszcze inną wersje zapytania. Sprawdźmy wersję z nakładaniem się zakresów czasowych. W tym przypadku używamy zapytania:

select * from emp_example versions period for emp_valid_time between DATA1 and DATA2.

W przypadku zakresu danych w Oracle mamy nie udokumentowaną funkcję overlaps, którą również przy tej okazji przetestujemy. Potraktujmy to bardziej jako ciekawostkę i nie używajmy tej funkcji w projektach na środowisku produkcyjnym.

--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- PRZEDZIAŁY
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--
--  1 Rozdzielne zakresy
--    +------------+
--                    +------------+

select * from emp_example 
versions period for emp_valid_time between date'2025-05-01' and date'2025-05-31';
-- zwrot:
-- no rows selected

select * from emp_example
where (start_date, end_date) overlaps (to_timestamp('2025-05-01'), to_timestamp('2025-05-31'));
-- zwrot:
-- no rows selected

--------------------------------------------------------------------------------------------------------
--  2 Zakresy mają dokładnie jeden punkt wspólny (end_date)
--    +------------+
--                 +-------------+
select * from emp_example 
versions period for emp_valid_time between date'2025-04-30' and date'2025-05-31';
-- zwrot:
-- no rows selected

select * from emp_example
where (start_date, end_date) overlaps (to_timestamp('2025-04-30'), to_timestamp('2025-05-31'));
-- zwrot:
-- no rows selected

--------------------------------------------------------------------------------------------------------
--  3 wspólny zakres jak poniżej
    +------------+
            +------------+
select * from emp_example 
versions period for emp_valid_time between date'2025-04-15' and date'2025-05-31';
-- zwrot:
--ID    EMPNO    FIRST_NAME    LAST_NAME    START_DATE                END_DATE
--1        101        Jaś            Fasola        2025-04-01 00:00:00,0    2025-04-30 00:00:00,0

select * from emp_example
where (start_date, end_date) overlaps (to_timestamp('2025-04-15'), to_timestamp('2025-05-31'));
-- zwrot:
--ID    EMPNO    FIRST_NAME    LAST_NAME    START_DATE                END_DATE
--1        101        Jaś            Fasola        2025-04-01 00:00:00,0    2025-04-30 00:00:00,0

--------------------------------------------------------------------------------------------------------
--  4 wspólny zakres jak poniżej
    +------------+
          +------+

select * from emp_example 
versions period for emp_valid_time between date'2025-04-15' and date'2025-04-30';
-- zwrot:
--ID    EMPNO    FIRST_NAME    LAST_NAME    START_DATE                END_DATE
--1        101        Jaś            Fasola        2025-04-01 00:00:00,0    2025-04-30 00:00:00,0

select * from emp_example
where (start_date, end_date) overlaps (to_timestamp('2025-04-15'), to_timestamp('2025-04-30'));
-- zwrot:
--ID    EMPNO    FIRST_NAME    LAST_NAME    START_DATE                END_DATE
--1        101        Jaś            Fasola        2025-04-01 00:00:00,0    2025-04-30 00:00:00,0

---------------------------------------------------------------------------------------------------------
--  5 drugi zawiera się w jeden jak poniżej
--    +------------+
--       +------+
select * from emp_example 
versions period for emp_valid_time between date'2025-04-15' and date'2025-04-20';
-- zwrot:
--ID    EMPNO    FIRST_NAME    LAST_NAME    START_DATE                END_DATE
--1        101        Jaś            Fasola        2025-04-01 00:00:00,0    2025-04-30 00:00:00,0

select * from emp_example
where (start_date, end_date) overlaps (to_timestamp('2025-04-15'), to_timestamp('2025-04-20'));
-- zwrot:
--ID    EMPNO    FIRST_NAME    LAST_NAME    START_DATE                END_DATE
--1        101        Jaś            Fasola        2025-04-01 00:00:00,0    2025-05-01 00:00:00,0

----------------------------------------------------------------------------------------------------------
-- 6 jeden i dwa są sobie równe
--    +------------+
--    +------------+
select * from emp_example 
versions period for emp_valid_time between date'2025-04-01' and date'2025-04-30';
-- zwrot:
--ID    EMPNO    FIRST_NAME    LAST_NAME    START_DATE                END_DATE
--1        101        Jaś            Fasola        2025-04-01 00:00:00,0    2025-04-30 00:00:00,0

select * from emp_example
where (start_date, end_date) overlaps (to_timestamp('2025-04-01'), to_timestamp('2025-04-30'));
-- zwrot:
--ID    EMPNO    FIRST_NAME    LAST_NAME    START_DATE                END_DATE
--1        101        Jaś            Fasola        2025-04-01 00:00:00,0    2025-04-30 00:00:00,0

----------------------------------------------------------------------------------------------------------
-- 7 jeden zawiera się w dwa jak poniżej
--       +------+
--    +------------+
select * from emp_example 
versions period for emp_valid_time between date'2025-03-01' and date'2025-06-30';
-- zwrot:
--ID    EMPNO    FIRST_NAME    LAST_NAME    START_DATE                END_DATE
--1        101        Jaś            Fasola        2025-04-01 00:00:00,0    2025-05-01 00:00:00,0

select * from emp_example
where (start_date, end_date) overlaps (to_timestamp('2025-03-01'), to_timestamp('2025-06-30'));
-- zwrot:
--ID    EMPNO    FIRST_NAME    LAST_NAME    START_DATE                END_DATE
--1        101        Jaś            Fasola        2025-04-01 00:00:00,0    2025-04-30 00:00:00,0

----------------------------------------------------------------------------------------------------------
-- 8 jeden zawiera się w dwa jak poniżej
--          +------+
--    +------------+
select * from emp_example 
versions period for emp_valid_time between date'2025-03-01' and date'2025-04-30';
-- zwrot:
--ID    EMPNO    FIRST_NAME    LAST_NAME    START_DATE                END_DATE
--1        101        Jaś            Fasola        2025-04-01 00:00:00,0    2025-04-30 00:00:00,0

select * from emp_example
where (start_date, end_date) overlaps (to_timestamp('2025-03-01'), to_timestamp('2025-04-30'));
-- zwrot:
--ID    EMPNO    FIRST_NAME    LAST_NAME    START_DATE                END_DATE
--1        101        Jaś            Fasola        2025-04-01 00:00:00,0    2025-04-30 00:00:00,0

----------------------------------------------------------------------------------------------------------
-- 9 jeden i dwa mają wspólny zakres jak poniżej
--            +------------+
--    +------------+
select * from emp_example 
versions period for emp_valid_time between date'2025-03-01' and date'2025-04-15';
-- zwrot:
--ID    EMPNO    FIRST_NAME    LAST_NAME    START_DATE                END_DATE
--1        101        Jaś            Fasola        2025-04-01 00:00:00,0    2025-04-30 00:00:00,0

select * from emp_example
where (start_date, end_date) overlaps (to_timestamp('2025-03-01'), to_timestamp('2025-04-15'));
-- zwrot:
--ID    EMPNO    FIRST_NAME    LAST_NAME    START_DATE                END_DATE
--1        101        Jaś            Fasola        2025-04-01 00:00:00,0    2025-04-30 00:00:00,0

----------------------------------------------------------------------------------------------------------
-- 10 jeden i dwa stykają w pukcie start_date
--                 +------------+
--    +------------+
select * from emp_example 
versions period for emp_valid_time between date'2025-03-01' and date'2025-04-01'
-- zwrot:
--ID    EMPNO    FIRST_NAME    LAST_NAME    START_DATE                END_DATE
--1        101        Jaś            Fasola        2025-04-01 00:00:00,0    2025-04-30 00:00:00,0
;
select * from emp_example
where (start_date, end_date) overlaps (to_timestamp('2025-03-01'), to_timestamp('2025-04-01'));
-- zwrot:
-- no rows selected
-- różnica pomiędzy "versions period for emp_valid_time" a "overlaps"
-- s2 > 
----------------------------------------------------------------------------------------------------------
-- 11 jeden i dwa nie mają punktów wspólnych jak poniżej
                    +------------+
    +------------+
select * from emp_example 
versions period for emp_valid_time between date'2025-03-01' and date'2025-03-31';
-- zwrot:
-- no rows selected

select * from emp_example
where (start_date, end_date) overlaps (to_timestamp('2025-03-01'), to_timestamp('2025-03-31'));
-- zwrot:
-- no rows selected
---------------------------------------------------------------------------------------------------------

Nie będę omawiał szczegółowo poszczególnych przypadków, ale spójrzmy tylko na wybrane, które bez wcześniejszej analizy nie muszą być tak oczywiste.

W punkcie 2 otrzymaliśmy wynik “no rows selected” i w zasadzie należało się tego spodziewać mając na uwadze wcześniejsze rozważania z jednym punktem czasowym. Po prostu end_date jest datą która “nie wchodzi” do pierwszego zakresu czasowego.

Natomiast w punkcie 10 w przypadku Temporal Validity wynik jest również oczywisty, ponieważ start_date zawiera się w zakresie czasowym nr 1, natomiast wynik funkcji overlap jest zupełnie inny, gdyż nie zwraca żadnego rekordu. Z powyższego widać, że pomiędzy tymi funkcjami są jednak różnice.

Po dokładnym przeanalizowaniu wyników możemy napisać odpowiedniki kod w SQL zastępujący testowane funkcje.

---------------------------------------------------------------------
-- Temporal Validity
select * from emp_example 
versions period for emp_valid_time between START_2 and END_2;
-- odpowiednikiem jest
select * from emp_example
where start_date <= END_2 and end_date > START_2;
----------------------------------------------------------------------
-- overlaps 
select * from emp_example
where (start_date, end_date) overlaps (START_2, END_2);
-- odpowiednikiem jest 
select * from emp_example
where start_date < END_2 and end_date > START_2;

Załóżmy, że nasz pracownik powinien mieć uprawnienia przez cały kwiecień. Poprawmy uprawnienia zgodnie działaniem Temporal Validity i ustawmy w tabeli datę końca uprawnień czyli północ w dniu 1 maja 2025. Odnówmy również uprawnienia pracownika od pierwszego maja na czas nieokreślony (END_DATE = NULL) . Po update i insert tabeli powinniśmy uzyskać zapisy jak poniżej.

Gdy teraz sprawdzimy okres ważności dla daty 2 maja 2025 roku, to zauważymy prostotę zapytania Temporal Validity, w którym NULL jest traktowany jako brak ograniczenia czasowego.

-- sprawdzamy kto ma uprawnienia w dniu 2 maja
select * from emp_example as of period for emp_valid_time date'2025-05-02';

ID EMPNO FIRST_NAME     LAST_NAME  START_DATE                    END_DATE 
-- ----- -------------- ---------- ----------------------------- ---------
 3   101 Jaś            Fasola     2025-05-01 00:00:00,000000000

Moim zdaniem warto skorzystać z tego rozwiązania choćby ze względu na prostotę budowania zapytań SQL. Mam nadzieję, że przybliżyłem nieco obsługę Temporal Validity i w jaki sposób należy zaprojektować schemat tabeli aby obliczenia przebiegały prawidłowo.

0
Subscribe to my newsletter

Read articles from Dariusz Grzywacz directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Dariusz Grzywacz
Dariusz Grzywacz