一、概念
歷史拉鍊表,就是記錄一個事務從開始一直到當前狀態的所有變化的信息,拉鍊表可以避免按每一天存儲所有記錄造成的海量存儲問題,同時也是處理緩慢變化數據的一種常見方式。
假設企業擁有1000萬的會員信息,每天有20萬的會員資料變更,我們需要記錄所有會議的歷史變化記錄,並至少保留兩年,該怎麼辦?
儲存兩年就是 2 x 365 x 1000萬 = 7300000000(70億),如果儲存更長時間,則無法估算需要的存儲。而用拉鍊表存儲,每日只向表中新增和變化的數據量,每日不過20萬條,
儲存2年也只需要 2 x 365 * 200000 = 146000000 (1.46以)存儲空間。
二、案例設計
--創建用戶信息的原始表
drop table t_userinfo_src;
create table t_userinfo_src(
user_id int,
user_name character varying,
user_no integer,
phone_no character varying,
create_date date,
update_date date
) distribute by hash(user_id);
--創建目標拉鍊表
drop table t_userinfo_zipper;
create table t_userinfo_zipper(
user_id int,
user_name character varying,
user_no integer,
phone_no character varying,
effective_date date,
invalid_date date
) distribute by hash(user_id);
2019年11月12日 新增了兩個用戶,
則這兩條記錄的生效時間爲當天,由於到 2019年11月12日 爲止,這兩條記錄還沒有被修改過,所以失效時間爲無窮大,
這裏設置爲數據庫中的最大值(3000-12-31),數據如下:
insert into t_userinfo_src(user_id,user_name,user_no,phone_no,create_date,update_date)
values(1001,'se7en.shi','110','13000000001','2019-11-12','2019-11-12'),
(1002,'eleven','120','13000000002','2019-11-12','2019-11-12'),
(1003,'rose','120','13000000003','2019-11-12','2019-11-12');
postgres=> select * from t_userinfo_src;
user_id | user_name | user_no | phone_no | create_date | update_date
---------+-----------+---------+-------------+---------------------+---------------------
1002 | eleven | 120 | 13000000002 | 2019-11-12 00:00:00 | 2019-11-12 00:00:00
1001 | se7en.shi | 110 | 13000000001 | 2019-11-12 00:00:00 | 2019-11-12 00:00:00
1003 | rose | 120 | 13000000003 | 2019-11-12 00:00:00 | 2019-11-12 00:00:00
(3 rows)
--執行函數,傳入今天的時間,處理昨天的數據
select * from fn_userinfo_zipper('2019-11-13');
--查看拉鍊表的數據
postgres=> select * from t_userinfo_zipper;
user_id | user_name | user_no | phone_no | effective_date | invalid_date
---------+-----------+---------+-------------+---------------------+---------------------
1003 | rose | 120 | 13000000003 | 2019-11-12 00:00:00 | 2999-12-31 00:00:00
1001 | se7en.shi | 110 | 13000000001 | 2019-11-12 00:00:00 | 2999-12-31 00:00:00
1002 | eleven | 120 | 13000000002 | 2019-11-12 00:00:00 | 2999-12-31 00:00:00
(3 rows)
第二天(2019-11-13),
用戶 1001 被刪除,
用戶 1002 的電話號碼被修改成 13000000004 。
爲了保留歷史狀態,用戶 1001 的失效時間被修改成 2019-11-12,用戶 1002 則變成兩條記錄,
新增1004用戶數據。
--原始表的操作爲
delete from t_userinfo_src where user_id=1001;
update t_userinfo_src set phone_no='13000000004',update_date='2019-11-13' where user_id=1002;
insert into t_userinfo_src(user_id,user_name,user_no,phone_no,create_date,update_date)
values(1004,'jack','110','13000000005','2019-11-13','2019-11-13');
--查看原始表數據
postgres=> select * from t_userinfo_src;
user_id | user_name | user_no | phone_no | create_date | update_date
---------+-----------+---------+-------------+---------------------+---------------------
1003 | rose | 120 | 13000000003 | 2019-11-12 00:00:00 | 2019-11-12 00:00:00
1004 | jack | 110 | 13000000005 | 2019-11-13 00:00:00 | 2019-11-13 00:00:00
1002 | eleven | 120 | 13000000004 | 2019-11-12 00:00:00 | 2019-11-13 00:00:00
postgres=> select * from t_userinfo_zipper;
user_id | user_name | user_no | phone_no | effective_date | invalid_date
---------+-----------+---------+-------------+---------------------+---------------------
1003 | rose | 120 | 13000000003 | 2019-11-12 00:00:00 | 2999-12-31 00:00:00 --拉鍊表中,14號執行後,應該被新增
1001 | se7en.shi | 110 | 13000000001 | 2019-11-12 00:00:00 | 2999-12-31 00:00:00 --拉鍊表中,14號執行後,應該被刪除
1002 | eleven | 120 | 13000000002 | 2019-11-12 00:00:00 | 2999-12-31 00:00:00 --拉鍊表中,14號執行後,應該被標記爲無效,無效時間是2019-11-13 00:00:00
--執行函數
select * from fn_userinfo_zipper('2019-11-14');
--執行函數後查看數據
postgres=> select * from t_userinfo_src;
user_id | user_name | user_no | phone_no | create_date | update_date
---------+-----------+---------+-------------+---------------------+---------------------
1003 | rose | 120 | 13000000003 | 2019-11-12 00:00:00 | 2019-11-12 00:00:00
1004 | jack | 110 | 13000000005 | 2019-11-13 00:00:00 | 2019-11-13 00:00:00
1002 | eleven | 120 | 13000000004 | 2019-11-12 00:00:00 | 2019-11-13 00:00:00
(3 rows)
postgres=> select * from t_userinfo_zipper;
user_id | user_name | user_no | phone_no | effective_date | invalid_date
---------+-----------+---------+-------------+---------------------+---------------------
1003 | rose | 120 | 13000000003 | 2019-11-12 00:00:00 | 2999-12-31 00:00:00
1001 | se7en.shi | 110 | 13000000001 | 2019-11-12 00:00:00 | 2019-11-13 00:00:00 --被標記爲刪除,invalid_date爲2019-11-13 00:00:00
1004 | jack | 110 | 13000000005 | 2019-11-13 00:00:00 | 2999-12-31 00:00:00 --新增數據
1002 | eleven | 120 | 13000000002 | 2019-11-12 00:00:00 | 2019-11-13 00:00:00 --被標記爲無效
1002 | eleven | 120 | 13000000004 | 2019-11-13 00:00:00 | 2999-12-31 00:00:00 --更新後的數據
(5 rows)
--拉鍊表的使用
1,如果要查詢最新的數據,那麼只要查詢失效時間爲 2999-12-31 的數據即可
postgres=> select * from t_userinfo_zipper where invalid_date='2999-12-31';
user_id | user_name | user_no | phone_no | effective_date | invalid_date
---------+-----------+---------+-------------+---------------------+---------------------
1004 | jack | 110 | 13000000005 | 2019-11-13 00:00:00 | 2999-12-31 00:00:00
1003 | rose | 120 | 13000000003 | 2019-11-12 00:00:00 | 2999-12-31 00:00:00
1002 | eleven | 120 | 13000000004 | 2019-11-13 00:00:00 | 2999-12-31 00:00:00
1,如果要查詢 2019年11月12號 的歷史數據,則篩選生效時間 <= 2019-11-13 並且失效時間 > 2019-11-13 的數據即可;
postgres=> select * from t_userinfo_zipper where invalid_date<='2019-11-13' and invalid_date >='2019-11-13';
user_id | user_name | user_no | phone_no | effective_date | invalid_date
---------+-----------+---------+-------------+---------------------+---------------------
1002 | eleven | 120 | 13000000002 | 2019-11-12 00:00:00 | 2019-11-13 00:00:00
1001 | se7en.shi | 110 | 13000000001 | 2019-11-12 00:00:00 | 2019-11-13 00:00:00
(2 rows)
--實現函數如下
create or replace function fn_userinfo_zipper(IN cur_date text)
returns void
as $$
/*
本功能是將原數據表中 新增數據、修改、刪除記錄到拉鍊表中
invalid_date 設定爲 2999-12-31
本函數傳入值爲時間,具體爲今天執行昨天的數據,參數爲 (to_date(cur_date,'yyyy-mm-dd') - 1)
總體邏輯如下
--1.目標表中沒有此主鍵的,確定爲新增 - 新增
--2,捕獲原表被刪除的數據,並更新拉鍊表被刪除數據的失效時間
--3 捕獲被修改的內容,將其置爲無效
--3.1 閉鏈:目標表中有此主鍵的記錄,狀態值不同,更新結束日期爲當天
--3.2 開鏈:目標表中新增一條修改的數據,更新結束日期爲無窮大
@author: se7en.shi
@date: 2019-11-13
*/
declare
begin
--1.目標表中沒有此主鍵的,確定爲新增 - 新增
insert into t_userinfo_zipper(user_id,user_name,user_no,phone_no,effective_date,invalid_date)
select a.user_id,a.user_name,a.user_no,a.phone_no,a.create_date,to_date('2999-12-31','yyyy-mm-dd') as invalid_date
from t_userinfo_src a
where a.create_date=(to_date(cur_date,'yyyy-mm-dd') - 1)
and not exists(
select 1 from t_userinfo_zipper b
where a.user_id=b.user_id);
raise notice 'finish new increasing ...';
--2,捕獲原表被刪除的數據,並更新拉鍊表被刪除數據的失效時間
update t_userinfo_zipper a set invalid_date=(to_date(cur_date,'yyyy-mm-dd')-1)
where not EXISTS(
select 1 from t_userinfo_src b
where a.user_id=b.user_id
);
raise notice 'finish delete data capture ...';
--3 捕獲被修改的內容,將其置爲無效
--3.1 閉鏈:目標表中有此主鍵的記錄,狀態值不同,更新結束日期爲當天
update t_userinfo_zipper a set invalid_date=(to_date(cur_date,'yyyy-mm-dd')-1)
where a.invalid_date=to_date('2999-12-31','yyyy-mm-dd')
and exists(
select 1 from t_userinfo_src b
where a.user_id=b.user_id and b.create_date < (to_date(cur_date,'yyyy-mm-dd')-1)
and (b.user_name<>a.user_name or b.user_no<>a.user_no or b.phone_no<>a.phone_no)
);
raise notice 'finish modifyed data capture lable invalid...';
--3.2 開鏈:目標表中新增一條修改的數據,更新結束日期爲無窮大
insert into t_userinfo_zipper(user_id,user_name,user_no,phone_no,effective_date,invalid_date)
select a.user_id,a.user_name,a.user_no,a.phone_no,(to_date(cur_date,'yyyy-mm-dd') - 1) as effective_date,to_date('2999-12-31','yyyy-mm-dd') as invalid_date
from t_userinfo_src a
where a.create_date<=(to_date(cur_date,'yyyy-mm-dd') - 1)
and exists(
select 1 from (
select user_id,effective_date,max(invalid_date) as invalid_date
from t_userinfo_zipper
group by user_id,effective_date ) b
where a.user_id=b.user_id
and a.create_date=b.effective_date
and b.invalid_date <= (to_date(cur_date,'yyyy-mm-dd') - 1)
);
raise notice 'finish modifyed data capture new insert';
end;
$$ language plpgsql;
各種數據庫遷移到PostgreSQL及PG維保、緊急救援及商業服務,請掃碼聯繫。
----------------------------------------------------------
個人微信
創作不易,如果對你的工作有幫助,打賞小費,以資鼓勵吧