# PostgreSQL extension periods 介紹
ANSI SQL 2011 裡引進了 periods and tables with SYSTEM VERSIONING 的觀念,後來在ANSI SQL 2016中更進一步.
目前各主流RDBMS,有程度不一的支持與進展.
PostgreSQL 方面目前有 periods 這個 extension,提供了資料型態與函數等相結合的支持.
### 網站
[github](https://github.com/xocolatl/periods)
## 安裝
```shell=
git clone https://github.com/xocolatl/periods
cd periods
make
make install
```
```sql=
create extension periods with schema public;
commit;
```
安裝後會建立 periods 這個 schema, 若之前未安裝 btree_gist extension, 可以用
```sql
create extension periods with schema public cascade;
```
這樣會一併安裝.
## Period 介紹
A period is a definition on a table which specifies a name and two columns. The period’s name cannot be the same as any column name of the table.
其實就是另外兩個日期時間欄位整合起來的欄位.在PG中有 daterange 這類的資料型態,我們先往下看.
常見的用處例如 例如民宿預約,PG原本有 btree_gist , 使用 exclude
類似這樣的語法
EXCLUDE USING gist (id WITH =, valid WITH &&)
實際也是使用這樣的方式,只是封裝為 period.符合ANSI SQL規定.
若使用ANSI SQL 標準語法應該是這樣
```sql=
create table room_booking (
guest text not null
, room_number int not null
, checkin date not null
, checkout date not null
, period for stay (checkin, checkout)
);
```
目前PG這樣是無法直接執行的! 因為 periods extension 尚未整併為語法,要用間接的方式.
```sql=
create table room_booking (
guest text not null
, room_number int not null
, checkin date not null
, checkout date not null
);
select periods.add_period('room_booking', 'stay', 'checkin', 'checkout');
--建立 Unique constraints
select periods.add_unique_key('room_booking', ARRAY['room_number'], 'stay');
add_unique_key
-------------------------------
room_booking_room_number_stay
commit;
```
### 測試
```sql=
insert into room_booking values
('Alice', 112, '2021-05-10', '2021-05-14');
commit;
insert into room_booking values
('Bob', 112, '2021-05-13', '2021-05-15');
ERROR: 23P01: conflicting key value violates exclusion constraint "room_booking_room_number_daterange_excl"
DETAIL: Key (room_number, daterange(checkin, checkout, '[)'::text))=(112, [2021-05-13,2021-05-15)) conflicts with existing key (room_number, daterange(checkin, checkout, '[)'::text))=(112, [2021-05-10,2021-05-14)).
CONSTRAINT NAME: room_booking_room_number_daterange_excl
```
可以看到這樣就不能overlap !
## SYSTEM-VERSIONED TABLES
這是主要要介紹的功能.直接來看實際的例子吧!
```sql=
create table sysver (
val text
, flap boolean
);
select periods.add_system_time_period('sysver', excluded_column_names => ARRAY['flap']);
-- flap 欄位會排除,不做紀錄.
select periods.add_system_versioning('sysver',
history_table_name => 'sysver_history',
view_name => 'sysver_view',
function_as_of_name => 'sysver_as_of',
function_between_name => 'sysver_between',
function_between_symmetric_name => 'sysver_between_symmetric',
function_from_to_name => 'sysver_from_to');
NOTICE: 00000: history table "sysver_history" created for "sysver", be sure to index it properly
LOCATION: exec_stmt_raise, pl_exec.c:3889
add_system_versioning
-----------------------
(1 row)
commit;
```
這樣就建立的 sysver 的 history table, sysver_history,
還有一些函數.來看實際的應用吧.
```sql=
insert into sysver (val, flap) values ('hello', false);
commit;
select *
from sysver;
val | flap | system_time_start | system_time_end
-------+------+-------------------------------+-----------------
hello | f | 2021-05-22 15:11:02.689252+08 | infinity
select *
from sysver_history;
val | flap | system_time_start | system_time_end
-----+------+-------------------+-----------------
(0 rows)
```
目前很單純的輸入一筆.
```sql=
update sysver
set val = 'world';
commit;
select *
from sysver;
val | flap | system_time_start | system_time_end
-------+------+-------------------------------+-----------------
world | f | 2021-05-22 15:15:55.621058+08 | infinity
select *
from sysver_history;
val | flap | system_time_start | system_time_end
-------+------+-------------------------------+-------------------------------
hello | f | 2021-05-22 15:11:02.689252+08 | 2021-05-22 15:15:55.621058+08
```
update 以後,觀察 sysver 與 sysver_history 的內容.
接著改變幾次flap,觀察sysver_history 會不會受到影響?
```sql=
update sysver set flap = not flap;
update sysver set flap = not flap;
update sysver set flap = not flap;
update sysver set flap = not flap;
update sysver set flap = not flap;
commit;
select *
from sysver;
val | flap | system_time_start | system_time_end
-------+------+-------------------------------+-----------------
world | t | 2021-05-22 15:15:55.621058+08 | infinity
select *
from sysver_history;
val | flap | system_time_start | system_time_end
-------+------+-------------------------------+-------------------------------
hello | f | 2021-05-22 15:11:02.689252+08 | 2021-05-22 15:15:55.621058+08
```
接著做 delete
```sql=
delete from sysver;
select val
from sysver;
val
-----
(0 rows)
select *
from sysver_history
order by system_time_start;
val | flap | system_time_start | system_time_end
-------+------+-------------------------------+-------------------------------
hello | f | 2021-05-22 15:11:02.689252+08 | 2021-05-22 15:15:55.621058+08
world | t | 2021-05-22 15:15:55.621058+08 | 2021-05-22 15:23:07.876518+08
(2 rows)
```
這樣目前 sysver 及 sysver_history 都可以觀察到.接著來做
temporal queries, 目前還未支持語法,只能先用 function的方式.
```sql=
select val
from sysver_as_of('2021-05-22 15:12:02+08')
order by system_time_start;
val
-------
hello
select val
from sysver_as_of('2021-05-22 15:15:56+08')
order by system_time_start;
val
-------
world
select val
from sysver_between('2021-05-22 15:12:02+08', '2021-05-22 15:15:56+08')
order by system_time_start;
val
-------
hello
world
(2 rows)
select val
from sysver_between('2021-05-22 15:15:56+08', '2021-05-22 15:12:02+08')
order by system_time_start;
val
-----
(0 rows)
select val
from sysver_from_to('2021-05-22 15:12:02+08', '2021-05-22 15:15:56+08')
order by system_time_start;
val
-------
hello
world
(2 rows)
```
## 結語
今天先介紹 periods 這個 extension, 比之前的 extension temporal_tables 有更高的整體性,也更貼近 ANSI SQL 的規定.
相信在不久的將來,大家可以很方便使用ANSI SQL 語法來做查詢.