下面是一个使用您提供的列名构建的通用示例,我认为这些列名或多或少都是您想要的。不要把它看作是一个立即可用的解决方案,而是一个如何做出类似这样的工作的例子,你必须修改一下你自己的实际用例。
大致的想法是创建一个基本的原始表,它保存所有的数据,并建立一个用于普通访问的视图。无论多么复杂,您仍然可以使用原始表格执行任何您需要对数据执行的操作,但该视图为常规使用提供更严格的访问权限。规则在视图上就位,以执行这些限制并执行所需的特殊操作。尽管对于当前的应用程序来说听起来并不重要,但需要注意的是,这些限制可以通过PostgreSQL的角色和特权以及SQL GRANT
命令来实施。
我们从制作原始表格开始。由于is_current
列很可能会被用作参考,因此我们会为其添加索引。我们将利用PostgreSQL的SERIAL
类型来管理我们的原始表row_id
。该视图甚至不需要参考底层row_id
。我们会将is_current
默认为True
值,因为我们预计大部分时间我们都会添加当前记录,而不是过去的记录。
CREATE TABLE raw_employee (
row_id SERIAL PRIMARY KEY,
employee_id INTEGER,
other_id INTEGER,
other_dimensions VARCHAR,
effective_date DATE,
expiration_date DATE,
is_current BOOLEAN DEFAULT TRUE
);
CREATE INDEX employee_is_current_index ON raw_employee (is_current);
现在我们定义我们的视图。对于大多数人来说,这将是访问员工数据的正常方式。在内部,它是针对我们已经定义的底层raw_employee
表按需运行的特殊SELECT
。如果我们有理由,我们可以进一步优化这个视图来隐藏更多的数据(它已经隐藏了前面提到的低级row_id
),或者显示通过计算或与其他表的关系产生的额外数据。
CREATE OR REPLACE VIEW employee AS
SELECT employee_id, other_id,
other_dimensions, effective_date, expiration_date,
is_current
FROM raw_employee;
现在我们的规则。我们构造这些内容,以便每当有人试图对我们的观点进行操作时,内部会根据我们定义的限制对我们的原始表执行操作。第一个INSERT
;它大多只是通过没有改变的数据,但它必须考虑到隐藏row_id
:
CREATE OR REPLACE RULE employee_insert AS ON INSERT TO employee DO INSTEAD
INSERT INTO raw_employee VALUES (
NEXTVAL('raw_employee_row_id_seq'),
NEW.employee_id, NEW.other_id,
NEW.other_dimensions,
NEW.effective_date, NEW.expiration_date,
NEW.is_current
);
的NEXTVAL
部分使我们对PostgreSQL的精益生产row_id
处理。接下来是我们最复杂的一个:UPDATE
。根据您描述的意图,它必须与employee_id
,other_id
对匹配并执行两个操作:将旧记录更新为不再最新,并插入具有更新日期的新记录。你没有指定你想如何管理新的到期日期,所以我猜测了一下。改变它很容易。
CREATE OR REPLACE RULE employee_update AS ON UPDATE TO employee DO INSTEAD (
UPDATE raw_employee SET is_current = FALSE
WHERE raw_employee.employee_id = OLD.employee_id AND
raw_employee.other_id = OLD.other_id;
INSERT INTO raw_employee VALUES (
NEXTVAL('raw_employee_row_id_seq'),
COALESCE(NEW.employee_id, OLD.employee_id),
COALESCE(NEW.other_id, OLD.other_id),
COALESCE(NEW.other_dimensions, OLD.other_dimensions),
COALESCE(NEW.effective_date, OLD.expiration_date - '1 day'::INTERVAL),
COALESCE(NEW.expiration_date, OLD.expiration_date + '1 year'::INTERVAL),
TRUE
);
);
采用COALESCE
能使我们更新有明确的更新列,但保留旧的值是那些不。最后,我们需要为DELETE
制定一个规则。既然你说过你想确保你可以追踪员工的历史,那么最好的办法也是最简单的:我们只是禁用它。
CREATE OR REPLACE RULE employee_delete_protect AS
ON DELETE TO employee DO INSTEAD NOTHING;
现在我们应该能够通过我们的观点进行INSERT
操作将数据插入到我们的原始表。这里有两名样本员工;第一个还剩几个星期,但第二个即将到期。请注意,在此级别,我们不需要关心row_id
。这是低级原始表的内部实现细节。
INSERT INTO employee VALUES (
1, 1,
'test', CURRENT_DATE - INTERVAL '1 week', CURRENT_DATE + INTERVAL '3 weeks',
TRUE
);
INSERT INTO employee VALUES (
2, 2,
'another test', CURRENT_DATE - INTERVAL '1 month', CURRENT_DATE,
TRUE
);
最后一个例子在我们完成的所有构建完成后看起来很简单。它对视图执行UPDATE
操作,并在内部对现有员工#2进行更新,并为员工#2添加新条目。
UPDATE employee SET expiration_date = CURRENT_DATE + INTERVAL '1 year'
WHERE employee_id = 2 AND other_id = 2;
我再次强调,这并不意味着不经过修改就直接使用。尽管你应该有足够的信息来为你的特定情况做些工作。
我很困惑你的意思是“other_id”和“other_dimensions”。你能澄清一下你的意图吗? – Feneric
这可能是字面上任何可能会改变的东西。例如,经理或地点。在我的具体情况中,它包含未纳入我们员工管理系统的其他系统的ID。我们需要将这些ID(来自各种自定义工具,电话系统,Salesforce等)追溯到官方员工ID,但其中一些会被回收或更改。 – trench
最佳做法是使用存储过程以原子方式执行操作。我必须想一点,但用一个例子来说明它是一个答案。 – Feneric