问题描述
我必须解决有关触发器的练习:
考虑以下用于表示的关系数据库架构 项目信息:
人员(ID,姓氏,姓名,国籍)
项目(名称,经理, 从年初开始,NumPeopleInvolved,国际)
人员(项目,PersonID)
指定Oracle中维护以下各项所需的触发器 完整性约束:
a)参与项目的人数(属性 NumPeopleInvolved)必须与元组数一致 输入该项目的人员
b)如果项目是国际项目(“国际”属性 假设只有两个值),那么该项目必须至少包含两个 不同民族的人
我对b)部分有疑问。
我不知道如何处理给定项目没有人员参与的情况。如果我尝试插入第一个人,那么我就不会有两个不同国籍的人,因为我只有一个人。
该如何处理?
我应该使用语句级触发器吗?我没有触发器的经验,所以我仍然不太了解如何/无法使用一种触发器。
我尝试过这种方式,但显然不能正常工作:
CREATE TRIGGER InsertPersonnelInternational
AFTER INSERT ON Personnel
FOR EACH ROW
BEGIN
SELECT ProjectName
FROM Personnel INNER JOIN Project
WHERE PersonID = :new.ID Project = Name
SELECT International
FROM Personnel INNER JOIN Project
ON Project = Name
SELECT COUNT(*) AS NumPersonnel
FROM Personnel
WHERE Project = :new.Project
IF NumPersonnel >= 1 THEN
BEGIN
SELECT COUNT(*) AS NumNationalities
FROM Personnel INNER JOIN Person
ON Project = ProjectName
GROUP BY Nationality
IF International THEN
IF NumNationalities = 1 Then
BEGIN
raise_application_error(-1)
END
ELSE
IF NumNationalities <> 1 THEN
BEGIN
raise_application_error(-1)
END
END
END
END
解决方法
最好的方法是使用复合触发器。通过使用复合触发器,我们可以避免由于PERSONNEL上的行级触发器而导致的表变异问题。
我们跟踪数组中DML语句(插入,更新,删除)中每个受影响的行所引用的每个项目。在声明的最后,我们查询这些项目,以查看该项目是否为国际项目,以及是否要检查其指派人员的国籍。
它可能看起来像这样:
import React from 'react'
import { StyleSheet,SafeAreaView,Button,Platform } from 'react-native'
import * as FacebookAds from 'expo-ads-facebook'
export default function testScreen({ routes,navigation }) {
// Interstitial Id
const placementId =
Platform.OS === 'ios'
? 'xxxxxxxxxxxxxxxx_yyyyyyyyyyyyyyyy'
: 'aaaaaaaaaaaaaaaa_bbbbbbbbbbbbbbbb'
FacebookAds.AdSettings.addTestDevice(FacebookAds.AdSettings.currentDeviceHash)
let expirationDate: any
// Prevent ad from loading more often than once every minute
const displayAd = () => {
console.log(expirationDate)
if (!expirationDate || new Date() > expirationDate) {
expirationDate = new Date(new Date().getTime() + 1 * 60 * 1000)
FacebookAds.InterstitialAdManager.showAd(placementId)
.then((didClick) => {
console.log('Interstitial Ad Shown ' + didClick)
})
.catch((error) => {
console.log('Interstitial Ad ' + error)
})
} else {
console.log('Ad re-loaded too frequently')
}
}
return (
<SafeAreaView style={styles.container}>
<StatusBar style="auto" />
{/* TEST SCREEN */}
<Button title="Button" onPress={displayAd} />
</SafeAreaView>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,justifyContent: 'center',alignItems: 'center'
}
})
这里是a working demo on db<>fiddle。您会看到,尽管触发器允许一个国际项目只有一个指派的人员,但是当我们添加另一个具有相同国籍的人员时,它会引发错误。我们可以通过以特殊顺序插入行或通过插入一组行来解决此问题。应用此类业务规则存在问题。
您可以使用相同的方法(在同一触发器中)检查所分配人员的数量是否符合CREATE OR REPLACE TRIGGER international_project_trg
FOR insert or update or delete ON personnel
COMPOUND TRIGGER
-- Global declaration
type project_t is table of number index by personnel.project%type;
g_project project_t;
BEFORE EACH ROW IS
BEGIN
CASE
-- we don't care about the value here,we just what a set of distinct projects
WHEN INSERTING THEN
g_project(:new.project) := 1;
WHEN UPDATING THEN
g_project(:new.project) := 1;
WHEN DELETING THEN
g_project(:old.project) := 1;
END CASE;
END BEFORE EACH ROW;
AFTER STATEMENT IS
l_project personnel.project%type;
l_country_cnt pls_integer;
l_people_cnt pls_integer;
BEGIN
l_project := g_project.first();
while l_project is not null loop
select count(distinct ppl.nationality),count(*)
into l_country_cnt,l_people_cnt
from personnel per
join project prj on per.project = prj.name
join person ppl on per.personid = ppl.id
where per.project = l_project
and prj.international = 'Y';
if l_people_cnt <= 1 then
-- either not international project or only one assigned person
-- so we don't care
null;
elsif l_country_cnt <= 1 then
raise_application_error(-20999,l_project ||' must have multi-national team membership');
end if;
l_project := g_project.next(l_project);
end loop;
END AFTER STATEMENT;
END international_project_trg;
规则。
注意:复合触发器已到达Oracle 11gR1。
,我认为以下内容适用于对人员表的插入,删除和更新。它只是检查并更新每个项目的国际一致性,是否更改了人员表。
CREATE TRIGGER UpdateInternationalProject
AFTER INSERT OR UPDATE OR DELETE ON Personnel
BEGIN
SELECT name,international
FROM Project
AS ProjectInternational;
FOR projectInfo IN ProjectInternational
LOOP
SELECT COUNT(DISTINCT nationality)
AS numNationalities
FROM Personnel INNER JOIN Person
ON personId = id
WHERE project = projectInfo.name;
IF numNationalities = 1 THEN
IF projectInfo.international THEN
UPDATE Project
SET international = 0
WHERE name = projectInfo.name;
END IF;
ELIF numNationalities > 1 THEN
IF NOT projectInfo.international THEN
UPDATE Project
SET international = 1
WHERE name = projectInfo.name;
END IF;
END IF;
END LOOP;
END;
,
如果在表Personnel
上有行级触发器,则无法在触发器内的表Personnel
上运行任何SELECT-您将收到ORA-04091: table PERSONEL is mutating ...
错误。
我认为您的老师期待这样的事情:
CREATE TRIGGER ProjectConsistency
BEFORE INSERT OR UPDATE ON PROJECT
FOR EACH ROW
p_count INTEGER;
n_count INTEGER;
BEGIN
SELECT COUNT(*)
INTO p_count
FROM Personnel
WHERE PROJECT = :new.NAME;
IF :new.NumPeopleInvolved <> p_count THEN
RAISE_APPLICATION_ERROR(-20010,'The number of people involved in a project must be consistent with the number of tuples entered in Personnel for that project');
END IF;
IF :new.International = 'YES' THEN
SELECT COUNT(DISTINCT Nationality)
INTO n_count
FROM Personnel
WHERE PROJECT = :new.NAME;
IF n_count < 2 THEN
RAISE_APPLICATION_ERROR(-20010,'The project must involve at least two people of different nationalities')
END IF;
END IF;
END;
实际上,您不会使用触发器来实现这种要求,而是使用PL / SQL过程。
属性NumPeopleInvolved
无用,即多余。通常,您可以通过以下方式解决该问题:
UPDATE PROJECT proj
SET NumPeopleInvolved =
(SELECT COUNT(*)
FROM Personnel p
WHERE PROJECT = :new.NAME)
WHERE NAME = :new.NAME;
例如,这种更新可以通过触发器来完成。
实际上,您在表Personnel
和Person
上也需要类似的触发器,因为一个或多个人员可能会更改并且项目会变得不一致。我不知道练习是否应该考虑这一点。
想象一下,某人被释放,即从表Person中删除:
- 应用程序是否会引发错误-无法释放人员(如果该人员死于Corona会发生什么事情:-))?
- 项目会无效吗?
- 项目会自动更新吗?
然后,您应该从不发出类似raise_application_error(-1)
的错误-始终让用户知道出了什么问题!