如何自动将视图列映射到其原始列以插入源表?

时间:2015-07-16 01:28:12

标签: sql postgresql postgresql-9.2 sql-view

我目前正在寻找一种 通用 方式来处理(非常简单的)PostgreSQL视图上的insert语句,这些视图对于列名更改不会很脆弱或添加新列。目标是构建INSTEAD - 不需要维护的触发器,可以轻松应用于具有相似形式的新视图。

换句话说,这将是一个用于提供对某些视图的表格式访问的公式,这些视图允许以类似表格的方式访问它们。当然,这不是为了愚弄任何人去思考某些事情的目标,而是为了简化自己和其他将要添加记录到数据库的人的事情,其中​​许多人将会开始运行如果有任何触发器代码中断,我就可以了。

想要这样的事情的原因是要处理创建记录需要已存在的预先存在的记录的情况:

CREATE TABLE bar (
        bar_id SERIAL PRIMARY KEY,
        bar_a CHAR
);

CREATE TABLE foo (
        foo_id SERIAL PRIMARY KEY,
        foo_a BIGINT,
        bar_id BIGINT NOT NULL REFERENCES bar(bar_id)
);

CREATE OR REPLACE VIEW view1 AS
        SELECT 
                foo_id, 
                bar_id, 
                foo_a AS num, 
                bar_a AS let 
        FROM (foo RIGHT JOIN bar ON foo.bar_id = bar.bar_id);

能够直接插入这样的视图将是一个很好的方式来保持所有的石灰而不会掉落它们;数据库约束对于保持系统有序非常有用,但是当你运行会话时它们可能会妨碍你,并且只是在没有花费一整天的情况下尝试获取一些记录。拥有一个触发器句柄,在依赖的情况下允许两全其美。

具体而言,这意味着可以用来运行像

这样的代码
INSERT INTO bar (let) VALUES ('a');
INSERT INTO foo (num,foo_fk) VALUES (1,currval(bar_bar_id_seq)); 
-- currval only pays attention to the last insert which happens on your OWN connection, 
-- meaning it is safe to use it here

INSERT到视图。希望触发器对更改的列名称和修改视图不敏感,这样在视图中抛出格式良好的INSERT将导致成功,而触发器功能不需要频繁调整对于小的改变。

将视图列映射到表列(主要挑战)

显然,的信息必须隐藏在某种形式或其他形式中,但最难的部分(根据我的理解)是确定视图列和源表列之间的映射而不是人为干预。为了使INSERT - 触发器功能健壮,必须从某处挖掘出这些信息。

理想情况下,view_column_usage会有这样的结构:

view_catalog|view_schema|view_name|table_catalog|table_schema|table_name|source_col|view_col|
------------+-----------+---------+-------------+------------+----------+----------+--------+
 db1        | public    | view1   | db1         | public     | bar      | bar_a    | let    |
 db1        | public    | view1   | db1         | public     | bar      | bar_id   | ----   |
 db1        | public    | view1   | db1         | public     | foo      | foo_a    | num    |
 db1        | public    | view1   | db1         | public     | foo      | foo_fk   | bar_id |
 db1        | public    | view1   | db1         | public     | foo      | foo_id   | foo_id |

但是,我们只是得到这个:

view_catalog|view_schema|view_name|table_catalog|table_schema|table_name|column_name
------------+-----------+---------+------------+------------+-----------+-------------
db1         | public    | view1   | db1        | public     | foo       | foo_fk
db1         | public    | view1   | db1        | public     | foo       | foo_a
db1         | public    | view1   | db1        | public     | foo       | foo_id
db1         | public    | view1   | db1        | public     | bar       | bar_a
db1         | public    | view1   | db1        | public     | bar       | bar_id

这意味着我们没有开箱即用的映射,但没有在pg_views.definition上为适当的视图运行解析操作,如果没有框架并且可能违反{{3},这将是不可取的}。

变通方法

硬编码

前面给出的WITH (...) INSERT基本上是正确的,但是对于数据库视图使用感觉很麻烦,数据库视图只提供了来自其他表的一些字段的简单身份映射。 (如果视图在源字段上进行计算并且在添加到原始列之前必须将其转换回来,事情会变得非常激烈,但这不是这个想法。)

在视图的字段中使用可预测的命名格式

我们当然可以做像

这样的事情
CREATE OR REPLACE VIEW view1 AS
        SELECT 
                foo_id AS foo__foo_id, 
                bar_id AS bar__bar_id, 
                foo_a AS foo__foo_a, 
                bar_a AS bar__bar_a 
        FROM (foo RIGHT JOIN bar ON foo.bar_id = bar.bar_id);

然后让函数使用这种格式来确定放置所有内容的位置......

但是除了降低视图的易读性之外,这与将INSERT - 操作硬编码为触发器​​一样脆弱,因为如果设置了视图的列名,函数会窒息错了。我们可以用视图来查看以修复易读性问题,但这实际上会使脆弱部分变得更糟,这确实是主要问题。

只处理稍微脆弱的触发器并继续使用它

尽管我是处理这种级别的数据库结构的主要人物,但我很接近数据库。如此僵硬的INSTEAD - 触发器可能只是相当标准。如果可能的话,我仍然想要一个更好的解决方案,这样当我离开办公室并且其他开发人员仍在工作时,事情会得到体面的支持,而当我转移到另一个开发人员的时候位置。

备注

编辑:正如DRY正确指出的那样,如上所述,触发器功能会对性能产生影响,因为INSTEAD会在每一行上运行(除非我只是缓存映射并适当地更新它,这本身就很麻烦)。我应该澄清一下,这个触发器主要是为了开发目的,因为事情处于不稳定的状态,并且可以用更简单,更快速的部署替换。如果有人有办法做到这一点(无论多么慢),如果只是为了学习Postgre,那么放在口袋里会很有用。

特别是对于更深层次,现实世界的情况,尝试智能地计算函数内各种源表的依赖顺序可能是愚蠢的。由于这不应该改变太多,我不介意为触发器函数提供一些参数来告诉它哪些表应该首先获得新行。

随着事情变得越来越复杂,如果关系如何在视图中一起显示3或4,那么提供触发关于如何将关系合并在一起的信息可能也会很好。该函数不需要如此灵活,以至于容忍表名或主键列名更改(不调整其代码);我们不在这里建立天网。无论如何,鉴于上述更简单的测试用例,您不应该担心这一点。我会弄清楚那些丑陋的东西。

此外,假设外键(如something_fk之类的一致性级别始终转换为something.something_id是正常的,因为这种事情在整个数据库中应该是正常的。

如前所述,我们假设与INSERT触发器一起使用的视图不会对源列执行计算(或者为了保持直观,它必须找到这些操作的反转在插入值之前)。我们不要去那里。

潜在有用的资源

pg_attribute是我发现的系统表,其中可能包含一些有用的内容,包括有关表格列发生顺序的信息。 information_schema.columns也可以提供view_column_usage以及上面提到的内容。

版本

最后,我目前正在使用PostgreSQL 9.2,但如果它允许更清晰的解决方案,则愿意升级。

2 个答案:

答案 0 :(得分:2)

没有简单的解决方案,也不应该有解决方案。

您在此提出的建议意味着您将拥有一个多功能的触发器功能,可以动态地适应不断变化的视图定义以及基础表的更改。出于各种原因,这是一个坏主意,我将只给出两个:

  • 如果您的开发人员精通(并且特权!)足以修改表和视图定义,他们也应该知道如何为视图编写相应的INSTEAD OF触发器。知道DDL但不知道PL / pgSQL的开发人员应该被发送回PG school
  • 由于必须查找列名,源表,将视图列映射到表列,将值映射到列,处理开发人员提出的约束,因此触发函数与您描述的一样灵活。 ,处理列级或表权限等等。因此会很慢。由于INSTEAD OF触发函数始终执行FOR EACH ROW,因此它们应该精简且快速;你正在为自己设置一个重大的性能瓶颈。

您的表和视图定义多久更改一次?只需在您的商店制定业务规则,表格和视图不会被更改,除非并且直到许多人讨论并同意更改,包括更改触发器。测试任何建议的更改。记录下来。再测试一下。最后,在推出应用程序之前对其进行更多测试。

一个对PostgreSQL知识显然有限的开发人员可以对可能破坏应用程序的数据模型进行更改的商店并不是我信任的商店。

改进开发人员和开发过程,而不是PostgreSQL。

你能做什么

也就是说,精心设计表格和触发功能可以大大减少触发功能的数量。基本规则是保持触发器小而快,让它们只做明确定义的事情,比如将INSERT传播到视图的基础表。

INSTEAD OF触发器函数基本上只是将对视图操作的命令转换为对基础表进行操作的一个或多个命令。一个视图上的INSTEAD OF INSERT触发器功能与另一个视图上的触发器功能非常相似,因此您可以尝试使用模板的方法。模板可能如下所示:

CREATE FUNCTION trf_ioi_#view# () RETURNS trigger AS $$
BEGIN
  -- Put in INSERT statements on all the tables making up the view
  -- Example: Simple insert:
  -- INSERT INTO table (columns...)
  -- VALUES (NEW...);

  -- Get serial PK from 1 table to insert in other tables
  -- INSERT INTO table_PK (columns...)
  -- VALUES (NEW...)
  -- RETURNING pk INTO tid; (DECLARE tid before BEGIN)

  -- INSERT INTO table_FK (FK_column, other columns...)
  -- VALUES (tid, NEW...);

  RETURN NEW; -- to make trigger proceed; NULL to make trigger fail
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER tr_ioi_#view#
INSTEAD OF INSERT ON #view#
FOR EACH ROW EXECUTE PROCEDURE trf_ioi_#view#;

#view#替换为视图的实际名称,并且您拥有开发人员可以轻松完成的样板代码。创建这样的触发器功能确实不是很困难,这只是开发人员必须学习的新技巧。通常,顺便说一下,它们将使用现有的触发器函数,因此它们只需更新函数体中的语句以反映视图或基础表中的更改。给他们两天玩PL / pgSQL并触发功能,他们应该顺利进行。

答案 1 :(得分:0)

(正在使用Postgres 12.4版)

可以通过以下查询为自己查看潜在的空性:

select vcu.column_name, c.is_nullable, c.data_type
from information_schema.view_column_usage vcu 
join information_schema."columns" c 
    on c.column_name = vcu.column_name 
    and c.table_name  = vcu.table_name 
    and c.table_schema  = vcu.table_schema 
    and c.table_catalog = vcu.table_catalog 
where view_name = 'your_view_here'

尽管OP要求9.2,但时间在前进,来这里的其他人可能会喜欢使用它。