1 All Powder Board and Ski Oracle 9i Workbook Chapter 7: Integrity and Transactions Jerry Post...

Preview:

Citation preview

1

All Powder Board and Ski

Oracle 9i WorkbookChapter 7: Integrity and TransactionsJerry PostCopyright © 2003

2

Compute Sales Tax

Sales Tax

From Figure 6.29

3

Create Oracle Package and Function

The slash is required to separate the commands

Package definition

Package body

Function definition

4

Test the Function in SQL

Dual is a tiny system table used for testing because it has one column and one row

Package name

Function name

Correct result: 7 percent of 500

5

Add Event Code to the Sales Form

Choose the PRE-TEXT-ITEM event

Call the new function

6

Debugging

Double click to set breakpoint

Debug/Debug ModuleDebug/Step Into

See form and variable values with Debug/Debug Windows

7

Inventory Database Triggers

Sale(SaleID, CustomerID, EmployeeID, SaleDate, …)

SaleItem(SaleID, SKU, QuantitySold, SalePrice)

Inventory(SKU, QuantityOnHand, …)

If a new item is sold, subtract QuantitySold from QuantityOnHandComplications: changes to the data

A SaleItem is revoked, the SaleItem row deletedThe QuantitySold is changedThe SKU is changed

Sample values:SaleID=3000 SKU=500000 or 500010CustomerID=582 EmployeeID=5

8

Database Event Triggers

DELETE

BEFORE INSERT AFTER

UPDATE

CREATE OR REPLACE TRIGGER NewSaleQOHAFTER INSERT ON SaleItemFOR EACH ROW

BEGINUPDATE INVENTORY SET QuantityOnHand = QuantityOnHand - :NEW.QuantitySold WHERE SKU = :NEW.SKU;

END;

New/inserted value

9

Setup Example

INSERT INTO Sale (SaleID, CustomerID, EmployeeID)VALUES (3000, 582, 5);

SELECT SKU, QuantityOnHandFROM InventoryWHERE SKU=500000;

INSERT INTO SaleItem (SaleID, SKU, QuantitySold, SalePrice)VALUES (3000, 500000, 1, 100);

Check the QuantityOnHand before and after the INSERT

10

Potential Problem: Delete Row

DELETE FROM SaleItemWHERE SaleID=3000 And SKU=500000;

Check the QuantityOnHand before and after the DELETEThe value does not change!

CREATE OR REPLACE TRIGGER DelSaleQOHAFTER DELETE ON SaleItemFOR EACH ROW

BEGINUPDATE INVENTORY SET QuantityOnHand = QuantityOnHand + :OLD.QuantitySold WHERE SKU = :OLD.SKU;

END;

Restore the deleted quantity

11

Problems

What if the clerk entered the wrong value and should have entered 1 instead of 2 units?

Test it, and the code subtracts 1 from the QOH, leaving 7.

You need to add the original 2 units back.

QuantityOnHand = QuantityOnHand – QuantitySold + OldQuantity

12

Problem: Change the Quantity

CREATE or REPLACE TRIGGER ChangeSaleQOHAFTER UPDATE ON SaleItemFOR EACH ROW

BEGINUPDATE Inventory SET QuantityOnHand = QuantityOnHand

+ :OLD.QuantitySold - :NEW.QuantitySoldWHERE SKU = :OLD.SKU;

END;

UPDATE SaleITemSET QuantitySold = 2WHERE SaleID=3000 And SKU=500000;

Test it

Test it again

Add back the old quantity (1) and subtract the new value (2)

13

Problem: Change the SKU

UPDATE SaleITemSET QuantitySold = 3, SKU = 500010WHERE SaleID=3000 And SKU=500000;

Test it by changing both QuantitySold and SKU

SELECT SKU, QuantityOnHandFROM InventoryWHERE SKU=500000 Or SKU=500010;

SELECT SKU, QuantityOnHandFROM InventoryWHERE SKU=500000 Or SKU=500010;

14

Trigger to Handle SKU Changes

CREATE or REPLACE TRIGGER ChangeSaleQOHAFTER UPDATE ON SaleItemFOR EACH ROW

BEGINIF (:OLD.SKU = :NEW.SKU) THEN

UPDATE Inventory SET QuantityOnHand = QuantityOnHand

+ :OLD.QuantitySold - :NEW.QuantitySoldWHERE SKU = :OLD.SKU;

ELSEUPDATE InventorySET QuantityOnHand = QuantityOnHand + :OLD.QuantitySoldWHERE SKU = :OLD.SKU;UPDATE InventorySET QuantityOnHand = QuantityOnHand - :NEW.QuantitySoldWHERE SKU = :NEW.SKU;

END IF;END; Test it again

15

Transactions for Discounts

New table

16

Rental Form

Button to open discount form

17

Rental Discount Form

RentID and Amount are determined by the Rental form

Date default value is set to $$DATETIME$$ This is an unbound form built from

design view with no Data Block source

18

Rental Form Code: Discount Button

:global.RentID := :Rental.RentID;:global.Amount := :Rental.SubCharges;Call_Form('D:\Students\AllPowder\GiveRentDiscount');

Save the RentID and total repair charges into global variables that can be retrieved by the discount form when it starts.

Rental Form, Button to open Discount form

Trigger event: WHEN-BUTTON-PRESS

19

Discount Form Triggers

:RentalID := :global.RentID;:Amount := :global.Amount;

Form: WHEN-NEW-FORM-INSTANCE

UPDATE RentItem SET RepairCharges=0 WHERE RentID = :RentalID;

INSERT INTO RentalDiscount(RentID, DiscountDate, DiscountAmount, Reason)VALUES (:RentalID, :TransDate, :Amount, :Reason);Commit;:txtMessage := 'Changes recorded.';

Button: WHEN-BUTTON-PRESSED

20

Transaction Code

BEGINUPDATE RentItem SET RepairCharges=0 WHERE RentID = :RentalID;

INSERT INTO RentalDiscount(RentID, DiscountDate, DiscountAmount, Reason)

VALUES (:RentalID, :TransDate, :Amount, :Reason);Commit;:txtMessage := 'Changes recorded.';

EXCEPTIONWHEN OTHERS THENRollback;

END;

WHEN-BUTTON-PRESSED

If something goes wrong, cancel the first update

21

Query for Cursor: Weekly Sales

CREATE VIEW WeeklySales ASSELECT TO_CHAR(SaleDate, 'ww') AS SalesWeek,

Sum(SalePrice*QuantitySold) AS ValueFROM Sale INNER JOIN SaleItem ON Sale.SaleID=SaleItem.SaleIDWHERE SaleDate Is Not NullGROUP BY TO_CHAR(SaleDate, 'ww');

22

Set up Package to Compute Average

CREATE OR REPLACE PACKAGE SalesAnalysis ASFUNCTION AvgPercentWeeklyChange return REAL;

END SalesAnalysis;/CREATE or REPLACE PACKAGE BODY SalesAnalysis AS

FUNCTION AvgPercentWeeklyChange return REAL ISCURSOR c1 IS

SELECT SalesWeek, Value FROM WeeklySales;Avg1 REAL;N Integer;PriorValue WeeklySales.Value%TYPE;

Define the SELECT statement for the cursor to trace throughCreate variable to hold the

value from the previous row with the same data type as the column in the table

23

Code to Compute Average Change

BEGINAvg1 := 0;N := 0;PriorValue := -1;FOR recSales in c1 LOOP

IF PriorValue > 0 THENAvg1 := Avg1 + (recSales.Value - PriorValue)/PriorValue;N := N + 1;

END IF;PriorValue := recSales.Value;

END LOOP;RETURN (Avg1/N);

END AvgPercentWeeklyChange;END SalesAnalysis;/

Skip the first week because there is no prior value

Compute the percent change and keep a running total

Save the current row value and move to the next row

24

A Sequence for the Sale Table

CREATE SEQUENCE seq_SaleINCREMENT BY 1START WITH 10000NOMAXVALUENOCYCLECACHE 10;

Start at a high number to avoid collisions with existing data

25

Trigger to Generate Key

CREATE OR REPLACE TRIGGER GenKeyForSaleBEFORE INSERT ON SaleFOR EACH ROW

BEGINSELECT seq_Sale.NEXTVAL INTO :NEW.SaleID FROM dual;

END;/

Automatically generate and use a new key value for SaleID whenever a row is added to the Sale table

Generate next value Use it as the new SaleID

26

Test the Key Generator

INSERT INTO Sale (CustomerID, EmployeeID) VALUES (582, 5);

SELECT seq_Sale.CURRVAL FROM dual;

SELECT * FROM Sale WHERE SaleID=10000;

Insert a row into Sale without specifying a SaleID

See what key value was generated

Retrieve the sales data to ensure the row was created

27

Keys: Create Sales and Items (barcode)

Customer ID card is scanned

Create new sale

Scan an item

Save sale item, update QOH and totals

Repeat until done (payment key)

Get SaleID

Save SaleID, SKU, Quantity

28

Generate Sale Form

IDs and SKU would be scanned, but to test code, set default values

29

Concurrency and Lock Test Form

30

PL/SQL to Change ZIP Code

BEGINUPDATE CustomerSET ZIP = :ZIPCodeWHERE CustomerID = :CustomerID;Commit;

END;

31

Customer List Form

32

Read Consistent Lock on the Form

Open both forms and use the testing form to change the ZIP code for CustomerID=1

Error message that value was changed

Return here and try to change the ZIP code

33

Stronger Lock on the Test Form

DECLAREconcurrency_hit EXCEPTION;PRAGMA EXCEPTION_INIT(concurrency_hit, -8177);

BEGINSET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

UPDATE CustomerSET ZIP = :ZIPCodeWHERE CustomerID = :CustomerID;Commit;

EXCEPTIONWHEN concurrency_hit THEN

message ('Data has been changed by another process.');WHEN OTHERS THEN

message ('Unknown error.');END;

Name the concurrency error

Set strongest isolation level

Catch error raised by this update interrupting another one

Notify user who can decide to try again or exit

34

Serializable Isolation Level

The change is not made and the error is trapped because the row is locked

Recommended