在 Oracle 中触发 - 增加用外键引用的列

Posted

技术标签:

【中文标题】在 Oracle 中触发 - 增加用外键引用的列【英文标题】:Trigger in Oracle - increment column referenced with foreign key 【发布时间】:2021-12-04 20:17:31 【问题描述】:

我需要在 Oracle SQL 中创建一个触发器。我有 2 个具有这些属性的实体:

军队

army_name VARCHAR(50) 主键 number_of_soliders 整数

士兵

personal_number 整数主键 solider_name VARCHAR(50) NOT NULL army_name VARCHAR(50) REFERENCES Army(army_name)

现在我需要为 number_of_soliders 创建一个触发器。默认值为 0,每次插入士兵时,我都需要将此值增加 1,对于特定的军队。因此,如果插入一个士兵并引用“美国陆军”,他们的士兵数量会自动增加一。

非常感谢

【问题讨论】:

【参考方案1】:

这是一个特别糟糕的主意,尽管显然很受欢迎。如果您尝试存储可以在运行时计算的内容,那么存储的值不正确只是时间问题。相信我。您根本不应该存储“number_of_soldiers”。你总是可以计算出来的

SQL> show user
USER is "SCOTT"
SQL> -- create the tables
SQL>  CREATE TABLE "SCOTT"."ARMY"
  2     (    "ARMY_NAME" VARCHAR2(20 BYTE) NOT NULL ENABLE,
  3           CONSTRAINT "ARMY_PK" PRIMARY KEY ("ARMY_NAME")
  4    USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255
  5    TABLESPACE "USERS"  ENABLE
  6     ) SEGMENT CREATION DEFERRED
  7    PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
  8   NOCOMPRESS LOGGING
  9    TABLESPACE "USERS" ;

Table created.

SQL> --
SQL>  CREATE TABLE "SCOTT"."SOLDIER"
  2     (    "COLUMN1" NUMBER(*,0) NOT NULL ENABLE,
  3          "SOLDIER_NAME" VARCHAR2(20 BYTE) NOT NULL ENABLE,
  4          "ARMY_NAME" VARCHAR2(20 BYTE) NOT NULL ENABLE,
  5           CONSTRAINT "SOLDIER_PK" PRIMARY KEY ("COLUMN1")
  6    USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255
  7    TABLESPACE "USERS"  ENABLE,
  8           CONSTRAINT "SOLDIER_FK1" FOREIGN KEY ("ARMY_NAME")
  9            REFERENCES "SCOTT"."ARMY" ("ARMY_NAME") ENABLE
 10     ) SEGMENT CREATION DEFERRED
 11    PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255
 12   NOCOMPRESS LOGGING
 13    TABLESPACE "USERS" ;

Table created.

SQL> -- load tables
SQL> -- load tables
SQL> insert into army values ('US ARMY');

1 row created.

SQL> insert into army values ('Canadian Army');

1 row created.

SQL> insert into soldier values (1,'Jody','US ARMY');

1 row created.

SQL> insert into soldier values (2,'Fred','US ARMY');

1 row created.

SQL> insert into soldier values (3,'Bob','US ARMY');

1 row created.

SQL> insert into soldier values (4,'Pierre','Canadian Army');

1 row created.

SQL> insert into soldier values (5,'Rocky','Canadian Army');

1 row created.
SQL> -- Do the query
SQL> select army_name,
  2         count(*)
  3  from soldier
  4  group by army_name
  5  order by army_name;

ARMY_NAME              COUNT(*)
-------------------- ----------
Canadian Army                 2
US ARMY                       3

2 rows selected.

SQL> -- clean up
SQL> drop table  soldier purge;

Table dropped.

SQL> drop table army purge;

Table dropped.

您还有其他几个设计问题,但这只是解决眼前的问题。

【讨论】:

像这样存储冗余数据几乎总是一个坏主意,因为很难完全保证它的正确性。最佳选择:制作视图或物化视图。如果确实需要存储它,最好制作包或程序来管理表中的数据(例如,可以在更新士兵之前始终锁定军队),而不是通过触发器来做到这一点。 15 年来,我管理了一个数据模型,其中包含数百个带有“智能”触发器的表,相信我,它让你大吃一惊。 你说得有道理,但这是学校项目的一部分,我们应该在数据库中而不是视图中有触发器(不要问我为什么) 通常在 education 项目中,您会构建一个 normalized model,而您仅在实践中使用 denormalized 模型,在这种情况下,您认为非常需要它。 @Vaclav 询问你的导师他/她是否可以评论【参考方案2】:

触发器:

SQL> create or replace trigger trg_ai_sol
  2    after insert or delete on soldier
  3    for each row
  4  begin
  5    if inserting then
  6       update army a set
  7         a.number_of_soldiers = a.number_of_soldiers + 1
  8         where a.army_name = :new.army_name;
  9    elsif deleting then
 10       update army a set
 11         a.number_of_soldiers = a.number_of_soldiers - 1
 12         where a.army_name = :old.army_name;
 13    end if;
 14  end;
 15  /

Trigger created.

测试:

SQL> select * From soldier;

no rows selected

SQL> select * from army;

ARMY_NAME    NUMBER_OF_SOLDIERS
------------ ------------------
US Army                       0
Another Army                  0

SQL> insert into soldier values (1, 'Little', 'US Army');

1 row created.

SQL> insert into soldier
  2    select 2, 'Foot', 'Another Army' from dual union all
  3    select 3, 'Oracle', 'US Army' from dual;

2 rows created.

SQL> select * From soldier;

PERSONAL_NUMBER SOLDIER_NAME         ARMY_NAME
--------------- -------------------- --------------------
              1 Little               US Army
              2 Foot                 Another Army
              3 Oracle               US Army

SQL> select * from army;

ARMY_NAME    NUMBER_OF_SOLDIERS
------------ ------------------
US Army                       2
Another Army                  1

SQL> delete from soldier where personal_number in (2, 3);

2 rows deleted.

SQL> select * From soldier;

PERSONAL_NUMBER SOLDIER_NAME         ARMY_NAME
--------------- -------------------- --------------------
              1 Little               US Army

SQL> select * from army;

ARMY_NAME    NUMBER_OF_SOLDIERS
------------ ------------------
US Army                       1
Another Army                  0

SQL>

【讨论】:

非常感谢!按预期工作:) @Vaclav 这会在士兵换军时给出不一致的值。【参考方案3】:

您可以在 Oracle 中创建序列,例如

CREATE SEQUENCE soldier_seq
    MINVALUE 1
    START WITH 1
    INCREMENT BY 1
    CACHE 10;

在表中创建新行时,引用 key0 字段作为序列的增量

insert into soldier
    (personnel_number, soldier_name, army_name)
    values (soldier_seq.nextval, "first and last Name", "USMC")

【讨论】:

序列永远不能保证没有间隙。当“士兵”从“军队”中移除时会发生什么? 问题是如何根据士兵表中的插入(可能还有删除)在陆军级别维护number_of_soldiers 差距不是问题,因为增量是要求。此外,您永远不会删除这样的记录,而是将其标记为已删除(非活动)。计算军队中的士兵人数将包括 where 子句 = active。

以上是关于在 Oracle 中触发 - 增加用外键引用的列的主要内容,如果未能解决你的问题,请参考以下文章

Spring用外键保存复合主键

[转帖]数据库,傻逼才用外键约束!

[转]oracle触发器与:new,:old的使用

用外键删除其他数据

在mysql中创建两个用外键链接的表,插入、删除和更新

Oracle外键需要建索引吗?