如何解决有关Oracle触发器的练习

问题描述

我必须解决有关触发器的练习:

考虑以下用于表示的关系数据库架构 项目信息:

人员(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;

例如,这种更新可以通过触发器来完成。

实际上,您在表PersonnelPerson上也需要类似的触发器,因为一个或多个人员可能会更改并且项目会变得不一致。我不知道练习是否应该考虑这一点。

想象一下,某人被释放,即从表Person中删除:

  • 应用程序是否会引发错误-无法释放人员(如果该人员死于Corona会发生什么事情:-))?
  • 项目会无效吗?
  • 项目会自动更新吗?

然后,您应该从不发出类似raise_application_error(-1)的错误-始终让用户知道出了什么问题!

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...