在 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 中触发 - 增加用外键引用的列的主要内容,如果未能解决你的问题,请参考以下文章