如何在 PostgreSQL 中存储表历史记录

Rob*_*ion 6 postgresql triggers

设想

我们需要在 PostgreSQL 中存储记录历史,这样当一条记录插入或更新到主表(例如:)时pets,它会自动备份到历史表(pets_history)。

理想情况下,我们需要根据主表的模式生成历史表,无需任何人工干预。

INSERT INTO pets(name, species) VALUES ('Meowth', 'Cat')
Run Code Online (Sandbox Code Playgroud)
pets:
+---+------------+-------------+
|id | name       | species     |
+---+------------+-------------+
| 1 | Meowth     | Cat         |
+---+------------+-------------+
Run Code Online (Sandbox Code Playgroud)

ATrigger应该自动将记录插入到pets_history

pets_history:
+----+--------+-----------+---------+
| id | ref_id | name      | species |
+----+--------+-----------+---------+
| 1  | 1      | Meowth    | Cat     |
+----+--------+-----------+---------+
Run Code Online (Sandbox Code Playgroud)

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

pets:
+---+------------+-------------+
|id | name       | species     |
+---+------------+-------------+
| 1 | Meowth     | Cat         |
+---+------------+-------------+
Run Code Online (Sandbox Code Playgroud)
pets:
+---+------------+-------------+
|id | name       | species     |
+---+------------+-------------+
| 1 | Persian    | Cat         |
+---+------------+-------------+
Run Code Online (Sandbox Code Playgroud)

我想结束以下...

pets_history:
+----+--------+-----------+---------+
| id | ref_id | name      | species |
+----+--------+-----------+---------+
| 1  | 1      | Meowth    | Cat     |
| 2  | 1      | Persian   | Cat     |
+----+--------+-----------+---------+
Run Code Online (Sandbox Code Playgroud)

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

pets:
+---+--------+---------+-------+
|id | name   | species | color |
+---+--------+---------+-------+
| 1 | Meowth | Cat     | cream |
+---+--------+---------+-------+
Run Code Online (Sandbox Code Playgroud)

我们希望这会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 |
+----+--------+---------+---------+-------+
Run Code Online (Sandbox Code Playgroud)

如果有人知道在 PostgreSQL 中本地执行此操作的任何方法或以其他方式执行此操作,请分享。我们查看了这个问题/答案实现 PostgreSQL 表的历史,部分解决了挑战,但它不会自动创建_history表。

Yeh*_*uah 3

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

宠物桌

CREATE TABLE public.pets
(
  id serial NOT NULL,
  name text,
  species text,
  PRIMARY KEY (id)
);
Run Code Online (Sandbox Code Playgroud)

宠物历史表

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)
);
Run Code Online (Sandbox Code Playgroud)

向历史表添加行的功能

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$;
Run Code Online (Sandbox Code Playgroud)

和宠物表触发器

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');
Run Code Online (Sandbox Code Playgroud)

  • 模式将关注点放在首位:您需要知道插入时存在哪些列。Schemaless 将其推向了道路:您需要在阅读时弄清楚存在哪些列。两者都有优点和缺点,但如果有人需要从历史表中恢复数据,我不一定建议他们存储无模式 json。 (4认同)