Page 251
Unlike many rules you might encounter in business, this actually makes sense! Simple as it is, though, it isn't possible to enforce at the database level without the use of a trigger. Now enhance this rule to include the following conditions:
If an employee is switching from salary to an hourly rate, the new yearly income, based on a 2000-hour year, must be within plus-or-minus 20 percent of his salary. The reverse condition applies to employees switching to a salary from an hourly rate.
Listing 11.3 shows one possible implementation of these rules using a before-update trigger on the employee table.
INPUT/
OUTPUT
Listing 11.3. Trigger enforcing complex business rules.
1: CREATE OR REPLACE TRIGGER emp_pay_type_chg 2: BEFORE UPDATE ON employee 3: FOR EACH ROW WHEN (OLD.pay_type <> NEW.pay_type) 4: DECLARE 5: new_yearly_rate employee.pay_rate%TYPE; 6: old_yearly_rate employee.pay_rate%TYPE; 7: allowed_variance_amt employee.pay_rate%TYPE; 8: allowed_variance_pct NUMBER(2,2) := .20; 9: hours_in_year INTEGER := 2000; 10: BEGIN 11: --Check to see if the pay rate has also been changed. 12: IF :OLD.pay_rate = :NEW.pay_rate THEN 13: RAISE_APPLICATION_ERROR (-20000, Â'The pay type changed and the pay rate did not.'); 14: END IF; 15: 16: --Check to verify that the new expected yearly rate is within 17: --plus-or-minus 20% of the previous yearly rate. 18: IF :OLD.pay_type = `H' AND :NEW.pay_type = `S' THEN 19: --the new salary must be + or - 20% of the old hourly rate * 2000 hours. 20: old_yearly_rate := ROUND(:OLD.pay_rate * hours_in_year,2); 21: new_yearly_rate := :NEW.pay_rate; 22: allowed_variance_amt := allowed_variance_pct * old_yearly_rate; 23: IF ABS(new_yearly_rate - old_yearly_rate) > allowed_variance_amt THEN 24: RAISE_APPLICATION_ERROR (-20000,'New salary is not within ` 25: || TO_CHAR(allowed_variance_pct * 100) || `% of the old rate.'); 26: END IF; 27: ELSIF :OLD.pay_type = `S' AND :NEW.pay_type = `H' THEN 28: --the new hourly rate * 2000 hours must be + or - 20% of the old salary. 29: new_yearly_rate := ROUND(:NEW.pay_rate * hours_in_year,2); 30: old_yearly_rate := :OLD.pay_rate; 31: allowed_variance_amt := allowed_variance_pct  * (old_yearly_rate * hours_in_year); 32: IF ABS(new_yearly_rate - old_yearly_rate) > allowed_variance_amt THEN 33: RAISE_APPLICATION_ERROR (-20000, Â'New project yearly rate is not within ` 34: || TO_CHAR(allowed_variance_pct * 100) || `% of the old salary.'); 35: END IF;
continues
Page 252
Listing 11.3. continued
36: ELSE 37: --Something is very wrong if we ever get here. 38: RAISE_APPLICATION_ERROR(-20000,'Invalid combination of pay types: ` 39: || :OLD.pay_type || ` ` || :NEW.pay_type); 40: END IF; 41: END; 42: / 43: Trigger created.
ANALYSIS
The trigger shown in Listing 11.3 is implemented as a before trigger (line 2) so
that it can validate any changes to the employee record before they are actually saved
in the database. The WHEN condition in line 3 causes this trigger to fire only when the pay
type is changed. This trigger could be fired on every update, but that would decrease
efficiency. Because the trigger validates changes to the pay_type field, it makes sense to fire it only
when that particular field is changed.
NOTE |
Be sure to notice that you need to precede the correlation names in the trigger body with a colon; in other words, use :OLD and :NEW. However, this is not the case in the WHEN clause. |
The first, and easiest, test that this trigger performs is to verify that the pay_rate field has been changed (line 12). An error is raised (line 13) if the pay rate has not been changed. The next task is to check to be sure that the employee's new yearly income will be within 20 percent of its previous values. A value of 2000 hours (line 9) is used to compute the yearly wages for an hourly worker. Lines 18 through 26 check the new wage for an employee changing from an hourly rate to a yearly salary. If the new salary is not within 20 percent of what the employee had been making previously, then an error is raised (lines 24_25). The code in lines 27 through 35 makes a similar check for a salaried employee being switched to an hourly rate. Finally, the code in lines 37 through 39 is executed if the change is not one of these two cases, and an error is raised.
To test the trigger shown in Listing 11.3, try typing in the SQL INSERT and UPDATE statements shown in Listing 11.4.
INPUT/
OUTPUT
Listing 11.4. Testing the pay_type_chg trigger.
1: --Insert an employee record and try some updates that should fail. 2: --Then try an update that should succeed. 3: --Create an employee for testing. 4: INSERT INTO employee 5: (emp_id, emp_name, pay_rate, pay_type)
Page 253
6: VALUES (100,'Kazam el Shabar',100,'H'); 7: 1 row created. 8: --Try updating to an invalid pay type. 9: UPDATE employee 10: SET pay_type = `X', 11: pay_rate = 200000 12: WHERE emp_id = 100; 13: UPDATE employee 14: * 15: ERROR at line 1: 16: ORA-20000: Invalid combination of pay types: H X 17: ORA-06512: at "MY_READER.EMP_PAY_TYPE_CHG", line 34 18: ORA-04088: error during execution of trigger `MY_READER.EMP_PAY_TYPE_CHG' 19: --Try an update where we only update the pay type and not the rate. 20: UPDATE employee 21: SET pay_type = `S' 22: WHERE emp_id = 100; 23: UPDATE employee 24: * 25: ERROR at line 1: 26: ORA-20000: The pay type changed and the pay rate did not. 27: ORA-06512: at "MY_READER.EMP_PAY_TYPE_CHG", line 10 28: ORA-04088: error during execution of trigger `MY_READER.EMP_PAY_TYPE_CHG' 29: --Update both type and rate, but the new yearly rate is 30: --more than 20% greater or less than the previous rate. 31: UPDATE employee 32: SET pay_type = `S', 33: pay_rate = 300000 34: WHERE emp_id = 100; 35: UPDATE employee 36: * 37: ERROR at line 1: 38: ORA-20000: New salary is not within 20% of the old rate. 39: ORA-06512: at "MY_READER.EMP_PAY_TYPE_CHG", line 20 40: ORA-04088: error during execution of trigger `MY_READER.EMP_PAY_TYPE_CHG' 41: --Update both type and rate, this time the new value is within range. 42: UPDATE employee 43: SET pay_type = `S', 44: pay_rate = 240000 45: WHERE emp_id = 100; 46: 1 row updated.
ANALYSIS
The first statement, the INSERT statement, simply adds an employee record to
the table for testing purposes. The first UPDATE statement tries to set the pay type
code to `X'. Because it is the pay type field that is changing, the
pay_type_chg trigger fires and rejects the change because
`X' is an invalid code. The second UPDATE statement changes
the pay type to salaried, but does not change the pay rate. This update is also rejected, this
time because the rate has not also been changed. The business rule states that the new yearly
pay must be within 20 percent of the previous value when an employee's pay type changes,
and the next two examples deal with this. Using a value of 2000 hours in a year, and
multiplying that by the employee's current rate of $100/hour, you get a total of $200,000 per
year. Twenty percent of $200,000 is $40,000, so the valid range after changing the pay type
would