如何在PostgreSQL中存储表历史

6yoyoihd  于 2023-11-18  发布在  PostgreSQL
关注(0)|答案(2)|浏览(153)

场景

我们需要在PostgreSQL中存储记录历史,以便当记录插入或更新主表(例如:pets)时,它会自动备份到历史表(pets_history)。
理想情况下,我们需要基于主表的模式生成历史表,而不需要任何人工干预。

INSERT INTO pets(name, species) VALUES ('Meowth', 'Cat')

个字符
Trigger应该自动将记录插入到**pets_history**中:

pets_history:
+----+--------+-----------+---------+
| id | ref_id | name      | species |
+----+--------+-----------+---------+
| 1  | 1      | Meowth    | Cat     |
+----+--------+-----------+---------+


当宠物更新时,将我的猫的名字从Meowth更改为Persian。例如:

UPDATE pets SET name = 'Persian' WHERE id = 1;
pets:
+---+------------+-------------+
|id | name       | species     |
+---+------------+-------------+
| 1 | Persian    | Cat         |
+---+------------+-------------+

的字符串
我想以以下内容结束我的演讲...

pets_history:
+----+--------+-----------+---------+
| id | ref_id | name      | species |
+----+--------+-----------+---------+
| 1  | 1      | Meowth    | Cat     |
| 2  | 1      | Persian   | Cat     |
+----+--------+-----------+---------+


稍后,当另一列/字段添加到pets表时,例如:color

pets:
+---+--------+---------+-------+
|id | name   | species | color |
+---+--------+---------+-------+
| 1 | Meowth | Cat     | cream |
+---+--------+---------+-------+


我们希望这会自动反映在pets_history表中:

pets_history:
+----+--------+---------+---------+-------+
| id | ref_id | name    | species | color |
+----+--------+---------+---------+-------+
| 1  | 1      | Meowth  | Cat     | null  |
| 2  | 1      | Persian | Cat     | null  |
| 3  | 1      | Persian | Cat     | cream |
+----+--------+---------+---------+-------+


如果有人知道在PostgreSQL或其他环境中如何实现这一点,请分享。我们看了这个问题/答案Implementing history of PostgreSQL table,它 * 部分 * 解决了这个挑战,但它不会自动创建_history表。

e4yzc0pl

e4yzc0pl1#

您可以使用to_jsonb将整行作为JSON对象保存在历史表中。在这种情况下,您不需要关心在历史表中添加新列,因为值的键将是列名。
宠物桌

CREATE TABLE public.pets
(
  id serial NOT NULL,
  name text,
  species text,
  PRIMARY KEY (id)
);

字符串
宠物历史表

CREATE TABLE public.h_pets
(
  id serial NOT NULL,
  target_row_id integer NOT NULL,
  executed_operation integer NOT NULL,
  operation_executed_at timestamp without time zone NOT NULL DEFAULT now(),
  data_after_executed_operation jsonb,
  PRIMARY KEY (id)
);


向历史表添加行的函数

CREATE OR REPLACE FUNCTION public.on_content_change()
  RETURNS trigger
  LANGUAGE 'plpgsql'
AS $BODY$
  DECLARE
    target_history_table TEXT;
  BEGIN
    target_history_table := TG_ARGV[0];

    IF TG_OP = 'INSERT'
    THEN
      EXECUTE
        format(
          'INSERT INTO %I (target_row_id, executed_operation, data_after_executed_operation) VALUES ($1.id, 0, to_jsonb($1))',
          target_history_table
        )
        USING NEW;
      RETURN NEW;
    ELSIF TG_OP = 'UPDATE'
    THEN
      EXECUTE
        format(
          'INSERT INTO %I (target_row_id, executed_operation, data_after_executed_operation) VALUES ($1.id, 1, to_jsonb($1))',
          target_history_table
        )
        USING NEW;
      RETURN NEW;
    ELSIF TG_OP = 'DELETE'
    THEN
      EXECUTE
        format(
          'INSERT INTO %I (target_row_id, executed_operation) VALUES ($1.id, 2)',
          target_history_table
        )
        USING OLD;
      RETURN OLD;
    END IF;
  END;
$BODY$;


和触发器的宠物表

CREATE TRIGGER pets_history_trigger
  BEFORE INSERT OR DELETE OR UPDATE
  ON public.pets
  FOR EACH ROW
  EXECUTE PROCEDURE public.on_content_change('h_pets');

n3h0vuf2

n3h0vuf22#

根据您选择的DBMS,您可以自动执行此操作。
Postgres有两个结构可以帮助您:(1)事件触发器--触发DB级事件,如创建和更改表;(2)触发器函数--可以创建触发器并将其应用于表。
在高级别上,你可以这样做:
1.创建一个触发器函数CreateHistoryTrigger,它实现了在基表上插入和更新时触发的逻辑,并插入到相应的历史表中。您可以通过为基表名称、列名和其他选项创建参数来泛化这一点。调用时,此函数将动态设计一个触发器,并将其应用于命名表。
1.创建一个事件触发器ManageHistoryTables来监视您感兴趣的更改,也就是说,假设您不希望每个表都有历史表,您可以通过模式、命名约定、要监视的特定表列表或任何适当的方式来过滤数据库事件。可能您希望至少监视CREATE TABLEALTER TABLEDROP TABLE
1.对于CREATE TABLEALTER TABLE,检查系统表以查看与基表对应的历史表是否存在。如果不存在,则通过从查询系统表发现的基表的列属性构建create或alter table命令并运行该命令来创建它。
1.对于ALTER TABLE,如果历史表已经存在,则将其当前列定义与基表进行比较,然后通过从查询系统表发现的基表的列属性构建alter table命令并运行该命令来添加/修改/或删除列。
1.对于DROP TABLE,删除history表(如果您想这么做的话)。
此时,您有了正确的历史表结构,但没有触发器填充该表结构。
1.在事件触发器中,对于CREATE TABLEALTER TABLEDROP TABLE中的任何一个,调用CreateHistoryTrigger触发器函数,传入您关心的表名和列名。该函数需要CREATE OR REPLACE表上的触发器,因为该触发器可能与同一表结构的先前版本不同。
像这样的元编程可能是具有挑战性的,但它也是一种有趣的、“永久的”/可重用的解决问题的方法。如果你能概括你在手动编写的触发器中使用的逻辑--允许表名和列是参数--这可能并不那么难,你可以在以后的项目中重用相同的事件触发器和触发器函数。

相关问题