You are on page 1of 78

IS210 – Chương 2

Procedural Language/Structured
Query Language (PL/SQL)
Ưu điểm của PL/SQL
• Tích hợp chặt chẽ với SQL.
• Hiệu suất cao.
• Bảo mật chặt chẽ.
Phân quyền người được tác động lên khối lệnh

2
Các tính năng chính của PL/SQL
• Khối lệnh PL/SQL
• Biến và hằng số trong PL/SQL
• Cấu trúc điều khiển trong PL/SQL
• Quản lý lỗi trong PL/SQL
• Chương trình con PL/SQL (Subprogram)
• PL/SQL Packages

3
PL/SQL Block
DECLARE -- Declarative part (optional)
Declarations of local types, variables, & subprograms

BEGIN --Executable part (required)

Statements (which can use items declared in declarative part)


EXCEPTION -- Exception-handling part (optional)

Exception handlers for exceptions raised in executable part


END;

4
Các tính năng chính của PL/SQL
• Khối lệnh PL/SQL
• Biến và hằng số trong PL/SQL
• Cấu trúc điều khiển trong PL/SQL
• Quản lý lỗi trong PL/SQL
• Chương trình con PL/SQL (Subprogram)
• PL/SQL Packages

5
Khai báo biến trong PL/SQL
• Biến trong PL/SQL có thể là bất kỳ kiểu dữ liệu nào được sử dụng trong SQL
(chẳng hạn char, date, number…) hoặc kiểu dữ liệu chỉ có ở PL/SQL (chẳng hạn
boolean, pls_integer).
DECLARE
part_number NUMBER(6); -- SQL data type
part_name VARCHAR2(20); -- SQL data type
in_stock BOOLEAN; -- PL/SQL-only data type
part_price NUMBER(6,2); -- SQL data type
part_description VARCHAR2(50); -- SQL data type
BEGIN
NULL;
END;

6
Gán giá trị cho biến
• Để gán giá trị cho biến ta có thể sử dụng các cách sau:
• Sử dụng toán tử gán :=
• Bằng cách selecting (hoặc fetching) dữ liệu.
• Bằng cách truyền cho nó như là một đối số OUT hoặc IN OUT trong chương
trình con (subprogram), rồi sau đó gán giá trị trong subprogram.
• Khai báo một hằng số giống như khai báo biến ngoại trừ việc phải
thêm từ khóa CONSTANT và ngay lập tức gán giá trị cho hằng số này.
• credit_limit CONSTANT NUMBER := 5000.00;

7
Các tính năng chính của PL/SQL
• Khối lệnh PL/SQL
• Biến và hằng số trong PL/SQL
• Cấu trúc điều khiển trong PL/SQL
• Quản lý lỗi trong PL/SQL
• Chương trình con PL/SQL (Subprogram)
• PL/SQL Packages

8
Cấu trúc điều khiển trong PL/SQL
• Quản lý điều kiện (Conditional Control)
• Quản lý lặp (Iterative Control)
• Quản lý tuần tự (Sequential Control)

9
Quản lý điều kiện (Conditional Control)
DECLARE
• sales NUMBER(8,2) := 20000;
bonus NUMBER(6,2);
emp_id NUMBER(6) := 120;
BEGIN
IF sales > 50000 THEN
bonus := 1500;
ELSIF sales > 35000 THEN
bonus := 500;
ELSE
bonus := 100;
END IF;
UPDATE employees SET salary = salary + bonus
WHERE employee_id = emp_id;
END;
10
Cấu trúc điều khiển trong PL/SQL
• Quản lý điều kiện (Conditional Control)
• Quản lý lặp (Iterative Control)
• Quản lý tuần tự (Sequential Control)

11
Quản lý lặp
Sử dụng FOR-LOOP Statement
• Trong một FOR loop, biến đếm có thể dùng để đọc nhưng không thể thay đổi, ví
dụ:
BEGIN
FOR i IN 1..3 LOOP
IF i < 3 THEN
DBMS_OUTPUT.PUT_LINE (TO_CHAR(i));
ELSE
i := 2; --ERROR Không thể thay đổi giá trị của i
END IF;
END LOOP;
END;

12
Cấu trúc điều khiển trong PL/SQL
• Quản lý điều kiện (Conditional Control)
• Quản lý lặp (Iterative Control)
• Quản lý tuần tự (Sequential Control)

13
Quản lý tuần tự
Sử dụng GOTO Statement
DECLARE
p VARCHAR2(30);
n PLS_INTEGER := 37;
BEGIN
FOR j in 2..ROUND(SQRT(n)) LOOP
IF n MOD j = 0 THEN
p := ' is not a prime number';
GOTO print_now;
END IF;
END LOOP;
p := ' is a prime number';
<<print_now>>
DBMS_OUTPUT.PUT_LINE(TO_CHAR(n) || p);
END;

14
Các tính năng chính của PL/SQL
• Khối lệnh PL/SQL
• Biến và hằng số trong PL/SQL
• Cấu trúc điều khiển trong PL/SQL
• Quản lý lỗi trong PL/SQL
• Chương trình con PL/SQL (Subprogram)
• PL/SQL Packages

15
Quản lý lỗi trong PL/SQL
Exception được định nghĩa sẵn

16
Quản lý lỗi trong PL/SQL
• DECLARE
stock_price NUMBER := 9.73;
net_earnings NUMBER := 0;
pe_ratio NUMBER;
BEGIN
-- Calculation might cause division-by-zero error.
pe_ratio := stock_price / net_earnings;
DBMS_OUTPUT.PUT_LINE('Price/earnings ratio = ' || pe_ratio);
EXCEPTION -- exception handlers begin Only one of the WHEN blocks is executed.
WHEN ZERO_DIVIDE THEN -- handles 'division by zero' error
DBMS_OUTPUT.PUT_LINE('Company must have had zero earnings.');
pe_ratio := NULL;
WHEN OTHERS THEN -- handles all other errors
DBMS_OUTPUT.PUT_LINE('Some other kind of error occurred.');
pe_ratio := NULL;
END; -- exception handlers and block end here
17
Quản lý lỗi trong PL/SQL
• Ta có thể tránh exception bằng cách kiểm tra mẫu số trước

DECLARE
stock_price NUMBER := 9.73;
net_earnings NUMBER := 0;
pe_ratio NUMBER;
BEGIN
IF(net_earnings = 0) THEN
pe_ratio:=NULL;
ELSE net_earnings
pe_ratio:= stock_price / net_earnings;
END IF;
END;

18
Quản lý lỗi trong PL/SQL

DECLARE EMPLOYEE
id char(5) := 10009;
Name varchar2(50); empId empName address
add varchar2(10); 10001 John US
BEGIN
10002 Mary US
SELECT empName, address INTO name, add
FROM EMPLOYEE 10003 Micheal UK
WHERE empId=id 10004 Henry GER
Trong khối lệnh select phải gán biến, select without into -> lỗi do select không có mục đích

EXCEPTION
-- Catches all 'no data found' errors
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE ('No Data found for employee ' || id);
END;

19
Quản lý lỗi trong PL/SQL
• --Tự khai báo exception
DECLARE
out_of_stock EXCEPTION;
number_on_hand NUMBER := 0;
BEGIN
IF number_on_hand < 1 THEN
RAISE out_of_stock; -- raise an exception that you defined
………
……….
END IF;
EXCEPTION
-- handle the error
WHEN out_of_stock THEN
DBMS_OUTPUT.PUT_LINE('Encountered out-of-stock error.');
END;

20
Các tính năng chính của PL/SQL
• Khối lệnh PL/SQL
• Biến và hằng số trong PL/SQL
• Cấu trúc điều khiển trong PL/SQL
• Quản lý lỗi trong PL/SQL
• Chương trình con PL/SQL (Subprogram)
• PL/SQL Packages

21
Tổng quan về chương trình con
• Một chương trình con (PL/SQL subprogram) chính là một khối lệnh
PL/SQL được đặt tên và được gọi với một tập các đối số.
• Một chương trình con có thể là một thủ tục (procedure) hoặc là một
hàm (function).
• Thông thường, procedure được sử dụng để thực hiện một tác vụ nào
đó còn function được sử dụng để tính toán và trả về kết quả.
• Chương trình con có thể được tạo ở mức schema, trong một package,
hay trong một khối lệnh PL/SQL.

22
Chương trình con
trong một khối lệnh PL/SQL
DECLARE
in_string VARCHAR2(100) := 'Test string';

PROCEDURE double ( original VARCHAR2) AS


BEGIN
DBMS_OUTPUT.PUT_LINE (original || original);
END;
BEGIN
double (in_string);
END;

23
Chương trình con ở mức schema
(Standalone subprogram)
CREATE OR REPLACE PROCEDURE remove_emp (employee_id NUMBER) AS
tot_emps NUMBER;
BEGIN
DELETE FROM employees
WHERE employees.employee_id = employee_id;
--tot_emps := tot_emps - 1;
END;
CREATE OR REPLACE FUNCTION get_bal(acc_no IN NUMBER)
RETURN NUMBER IS
acc_bal NUMBER(11,2); --declare acc_bal
BEGIN
SELECT order_total INTO acc_bal
FROM orders
WHERE customer_id = acc_no;
DBMS_OUTPUT.PUT_LINE (TO_CHAR(acc_no));
RETURN(acc_bal);
24
END;
Các phần trong một chương trình con
PROCEDURE double (original IN VARCHAR2, new_string OUT VARCHAR2)
IS
-- Declarative part of procedure (optional) goes here

BEGIN
-- Executable part of procedure begins
new_string := original || ' + ' || original;
-- Executable part of procedure ends

-- Exception-handling part of procedure (optional) begins


EXCEPTION
WHEN VALUE_ERROR THEN
DBMS_OUTPUT.PUT_LINE('Output buffer not long enough.');
END;

25
Các phần trong một chương trình con
• Một thủ tục (procedure) và một hàm (function) có cùng cấu trúc, ngoại
trừ:
• Phần đầu của function phải chứa mệnh đề RETURN (return clause) xác định
kiểu dữ liệu trả về. Còn procedure không chứa mệnh đề RETURN này.
• Một function phải chứa ít nhất một câu lệnh RETURN (return statement) trong
phần thực thi. Trong procedure, câu lệnh RETURN không bắt buộc.

26
Chương trình con
Ví dụ
PROCEDURE raise_salary ( emp_id NUMBER, amount NUMBER)
IS
BEGIN
IF emp_id IS NULL THEN
RETURN; return statement
END IF;
UPDATE employees SET salary = salary + amount WHERE employee_id = emp_id;
END raise_salary;

FUNCTION compute_bonus (emp_id NUMBER, bonus NUMBER)


RETURN NUMBER
IS
emp_sal NUMBER; return clause
BEGIN
SELECT salary INTO emp_sal FROM employees WHERE employee_id = emp_id;
RETURN emp_sal + bonus; return statement
END compute_bonus; 27
Xác định các loại đối số trong chương trình
con
• Có 3 phương thức truyền đối số là IN (mặc định), OUT, và IN OUT.
• Tránh việc sử dụng OUT và IN OUT với function.

28
Đối số trong chương trình con
sử dụng IN
• Đối số IN cho phép truyền giá trị tới chương trình con.
• Trong một chương trình con, đối số IN hoạt động giống như một hằng
số. Nó không được gán giá trị.
• Đối số IN có thể được khởi tạo một giá trị mặc định.

29
Đối số trong chương trình con
sử dụng IN
PROCEDURE debit_account (acct_id INTEGER, amount IN REAL) IS
service_charge REAL;
BEGIN
service_charge := amount + 100;
amount := amount + 5; error
...
END debit_account;

BEGIN
debit_account (100034, 20);
END;

30
Đối số trong chương trình con
sử dụng IN-truyền đối số mặc định
PROCEDURE Get_emp_names (Dept_num IN NUMBER DEFAULT 20) IS ...

Gọi chương trình con với đối số mặc định:


Get_emp_names (); -- truyền đối số mặc định là 20
Get_emp_names(47); -- truyền đối số là 47

31
Đối số trong chương trình con
sử dụng OUT
• Đối số OUT trả về giá trị tới môi trường gọi nó.
• Trong chương trình con, đối số OUT hoạt động như là một biến
(variable).
• Có thể thay đổi giá trị của nó và sử dụng giá trị này sau khi gán.

32
Đối số trong chương trình con
sử dụng OUT
CREATE PROCEDURE square (val IN NUMBER, resultval OUT NUMBER)
IS
BEGIN
DBMS_OUTPUT.PUT_LINE (resultval );
resultval := val*val;
END;

SET SERVEROUTPUT ON
DECLARE
v_kq NUMBER := 200;
BEGIN
square (5, v_kq); --gọi chương trình con, đối số là OUT trả kết quả về
-- môi trường gọi nó thông qua biến v_kq
DBMS_OUTPUT.PUT_LINE (v_kq);
END;
33
Đối số trong chương trình con
sử dụng IN OUT
• Đối số IN OUT truyền giá trị khởi tạo tới chương trình con và trả về
một giá trị mới cho môi trường gọi nó.
• Đối số IN OUT phải là một biến, không được là hằng số hay một biểu
thức.

34
Đối số trong chương trình con
sử dụng IN OUT
CREATE PROCEDURE square (val IN OUT NUMBER) IS
BEGIN
val := val*val;
END;

SET SERVEROUTPUT ON
DECLARE
v_init NUMBER;
BEGIN
v_init := 5;
square (v_init);
DBMS_OUTPUT.PUT_LINE (v_init);
END;
35
Các tính năng chính của PL/SQL
• Khối lệnh PL/SQL
• Biến và hằng số trong PL/SQL
• Cấu trúc điều khiển trong PL/SQL
• Quản lý lỗi trong PL/SQL
• Chương trình con PL/SQL (Subprogram)
• PL/SQL Packages

36
PL/SQL Package là gì?
• Là một tập hợp các PL/SQL object được nhóm lại tạo thành một gói.
• Chúng có thể chứa:
• Procedures, functions
• Types, cursors, variables, constants
• Exceptions

37
Cấu trúc của Package
• Có 2 phần:
• Phần mô tả (specification):
• Khai báo các public item (procedure, function, variable...).
• Các chương trình khác có thể truy xuất đến chúng từ ngoài package.
• Phần thân (body)
• Hiện thực các public item. Đồng thời nó cũng có thể chứa các private item.
• Các đơn vị private này chỉ có thể truy xuất trong tầm vực của package.

38
Package specification và body
CREATE PACKAGE emp_actions AS
/* Declare externally visible types, cursor, exception. */
TYPE EmpRecTyp IS RECORD (emp_id INT, salary REAL);
invalid_salary EXCEPTION;
/* Declare externally callable subprograms. */
PROCEDURE raise_salary (emp_id INT, grade INT);
END emp_actions;

CREATE PACKAGE BODY emp_actions AS


number_hired INT; -- visible only in this package
PROCEDURE raise_salary (emp_id INT, grade INT) IS
………
END raise_salary;
BEGIN -- initialization part starts here
……….
END emp_actions; 39
Ví dụ (package specification)

CREATE OR REPLACE PACKAGE empMgmt
IS
PROCEDURE sal_raise (amount NUMBER, depNo NUMBER);
---------------------------------------------
FUNCTION salDep_sum (depNo IN NUMBER)
RETURN NUMBER;
END empMgmt;

40
Ví dụ (Package Body)
CREATE OR REPLACE PACKAGE BODY empMgmt
IS
• PROCEDURE sal_raise (amount NUMBER, depNo NUMBER)
IS
BEGIN
update staff set salary=salary+amount where deptId =depNo;
END sal_raise;
---------------------------------------------
FUNCTION salDep_sum (depNo IN NUMBER)
RETURN NUMBER
IS
sal number;
BEGIN
select sum(salary) into sal from staff where deptId= depNo;
RETURN sal ;
END salDep_sum;
END empMgmt; 41
Gọi các phần tử trong package
• package elements (variables, procedures or functions) được truy cập
theo cú pháp sau:
package_name.element_name;
• Ví dụ: gọi hàm sal_raise trong package empMgmt

DECLARE
amt NUMBER := 500;
depId NUMBER :=101;
BEGIN
empMgmt.sal_raise (amt, depId);
END;
42
Xóa Package
• Để xóa toàn bộ package bao gồm specification và body sử dụng cú
pháp:
• DROP PACKAGE package_name
• Chỉ xóa package body sử dụng cú pháp: F

• DROP PACKAGE BODY package_name;

43
Ví dụ
• Tạo một package quản lý nhân viên bao gồm các thao tác: thêm nhân
viên, xóa nhân viên, tăng lương cho nhân viên
CREATE OR REPLACE PACKAGE Employee_management AS -- package specification

FUNCTION Hire_emp
(Name VARCHAR2, Job VARCHAR2, Mgr NUMBER,
Hiredate DATE, Sal NUMBER, Comm NUMBER, Deptno NUMBER)
RETURN NUMBER;

PROCEDURE fire_emp (emp_id IN NUMBER);

PROCEDURE Sal_raise (Emp_id IN NUMBER, Sal_incr IN NUMBER);


END Employee_management;

44
CREATE PACKAGE BODY Employee_management AS

FUNCTION Hire_emp (Name VARCHAR2, Job VARCHAR2, Mgr NUMBER, Hiredate DATE,
Sal NUMBER, Comm NUMBER, Deptno NUMBER) RETURN NUMBER IS
New_empno NUMBER(10);
BEGIN
SELECT Emp_sequence.NEXTVAL INTO New_empno FROM dual;
INSERT INTO Emp_tab VALUES (New_empno, Name, Job, Mgr, Hiredate, Sal, Comm, Deptno);
RETURN (New_empno);
END Hire_emp;

PROCEDURE fire_emp (emp_id IN NUMBER) AS


BEGIN
DELETE FROM Emp_tab WHERE Empno = Emp_id;
IF SQL%NOTFOUND THEN
Raise_application_error(-20011, 'Invalid Employee Number: ' || TO_CHAR(Emp_id));
END IF;
END fire_emp;

PROCEDURE Sal_raise (Emp_id IN NUMBER, Sal_incr IN NUMBER) AS


BEGIN
UPDATE Emp_tab SET Sal = Sal + Sal_incr WHERE Empno = Emp_id;
IF SQL%NOTFOUND THEN
Raise_application_error(-20011, 'Invalid Employee Number: ' || TO_CHAR(Emp_id));
END IF;
END Sal_raise; 45
END Employee_management;
Trigger

46
Tổng quan về trigger
• Trigger là một đơn vị chương trình lưu trữ trong database và thực thi
(fire) để đáp ứng một sự kiện nào đó.
• Sự kiện này được kết hợp với một table, view, schema, hoặc database,
và là một trong những sự kiện sau:
• Một câu lệnh DML (DELETE, INSERT, hoặc UPDATE).
• Một câu lệnh DDL (CREATE, ALTER, DROP)
• Một tác vụ trên database (SERVERERROR, LOGON, LOGOFF, STARTUP,
hoặc SHUTDOWN).

47
Sự khác biệt giữa trigger và constraint (ràng
buộc)
• Cả trigger và contraint đều ràng buộc dữ liệu đầu vào, nhưng chúng có
những điểm khác biệt đáng kể:
• Trigger chỉ áp dụng cho dữ liệu mới.
• Constraint có thể áp dụng cho dữ liệu mới hoặc cả dữ liệu cũ và dữ liệu mới
• Trigger có thể tuân theo những quy tắc phức tạp mà constraint không thể.

48
Simple DML trigger
• Simple DML trigger kích hoạt duy nhất ở một trong những thời điểm
sau:
• Trước khi câu lệnh thực thi
(BEFORE statement trigger hay statement-level BEFORE trigger)
• Sau khi câu lệnh thực thi
(AFTER statement trigger hay statement-level AFTER trigger)
• Trước mỗi dòng (row) mà câu lệnh tác động
(BEFORE each row trigger hay row-level BEFORE trigger).
• Sau mỗi dòng (row) mà câu lệnh tác động
(AFTER each row trigger hay row-level AFTER trigger).

49
Compound DML trigger
• Compound DML trigger có thể kích hoạt tại một thời điểm, một vài
thời điểm hoặc tất cả các thời điểm.
• Compound trigger giúp ích cách tiếp cận trong việc chia sẻ dữ liệu ở
các thời điểm khác nhau (timing point).

50
DML Trigger
• DML trigger được tạo trên table hoặc view và nó bắt các sự kiện
INSERT, DELETE, UPDATE.
• DML trigger có thể là trigger đơn (simple) hoặc trigger phức hợp
(compound).

51
Thứ tự kích hoạt trigger (DML trigger)

• Thứ tự kích hoạt trigger:


• Trước khi câu lệnh DML thực thi
• Đối với mỗi dòng (row):
• a. trước mỗi row mà câu lệnh tác động.
• b. thực thi Insert, Update, Delete
• c. sau mỗi row mà câu lệnh tác động
• Sau khi câu lệnh DML thực thi.

52
Thứ tự kích hoạt trigger (ví dụ)
Giả sử ta có 4 SIMPLE DML trigger được tạo trên table NHANVIEN (với phần thực thi là null)
BEFORE statement trigger
AFTER statement trigger
BEFORE each row trigger
AFTER each row trigger
Thực thi câu lệnh:
UPDATE nhanvien SET luong = luong + 10 WHERE diachi = ‘Q11’;

NHANVIEN MANV HOTEN DIACHI LUONG BEFORE statement trigger


BEFORE each row
thực thi Update
1 NGUYEN VAN A Q11 20 30 AFTER each row trigger
BEFORE each row
2 NGUYEN VAN B Q11 50 60 thực thi Update
AFTER each row trigger
3 NGUYEN VAN C Q10 30
BEFORE each row
4 NGUYEN VAN D Q11 100 110 thực thi Update
AFTER each row trigger
53
AFTER statement trigger
Tạo trigger
CREATE [OR RELACE] TRIGGER triggername
{BEFORE | AFTER}
{DELETE, INSERT, UPDATE [OF columnname….]}
ON tablename
[REFERENCING {OLD AS old, NEW AS new}]
[FOR EACH ROW [WHEN condition]]
DECLARE
Variable declaration;
Constant declaration;
BEGIN
PL/SQL subprogram body;
[EXCEPTION
exception PL/SQL block;
END;

54
Tạo trigger
Ví dụ

CREATE TRIGGER Print_trigger_type _biu_s CREATE TRIGGER Print_trigger_type _aiu
BEFORE INSERT OR UPDATE ON emp AFTER INSERT OR UPDATE ON emp
BEGIN FOR EACH ROW
dbms_output.put_line('call before statement'); BEGIN
END; dbms_output.put_line('call after each row');
END;

CREATE TRIGGER Print_trigger_type _biu


BEFORE INSERT OR UPDATE ON emp CREATE TRIGGER Print_trigger_type _aiu_s
FOR EACH ROW AFTER INSERT OR UPDATE ON emp
BEGIN BEGIN
dbms_output.put_line('call before each row'); dbms_output.put_line('call after statement');
END; END;

55
Vị từ điều kiện trong trigger
Vị từ điều kiện TRUE nếu và chỉ nếu:
INSERTING Một câu lệnh INSERT kích hoạt trigger

UPDATING Một câu lệnh UPDATE kích hoạt trigger

UPDATING ('column') Một câu lệnh UPDATE tác động trên một
column cụ thể nào đó kích hoạt trigger.

DELETING Một câu lệnh DELETE kích hoạt trigger

56
Vị từ điều kiện trong trigger
Ví dụ
CREATE OR REPLACE TRIGGER t
BEFORE
INSERT OR
UPDATE OF salary, department_id OR
DELETE
ON employees
BEGIN
CASE
WHEN INSERTING THEN
DBMS_OUTPUT.PUT_LINE('Inserting');
WHEN UPDATING('salary') THEN
DBMS_OUTPUT.PUT_LINE('Updating salary');
WHEN UPDATING('department_id') THEN
DBMS_OUTPUT.PUT_LINE('Updating department ID');
WHEN DELETING THEN
DBMS_OUTPUT.PUT_LINE('Deleting');
END CASE;
END;
57
Old và New

• Khi row-trigger kích hoạt, có 2 dữ liệu ảo được tạo, gọi là new và old.
• new table_name%ROWTYPE;
• old table_name%ROWTYPE;
• Old và new có kiểu dữ liệu ROWTYPE từ table bị tác động. Sử
dụng dấu chấm (.) để tham chiếu đến column từ old và new.
Triggering Statement OLD.field Value NEW.field Value

INSERT NULL Post-insert value

UPDATE Pre-update Post-update value


DELETE Pre-delete value NULL
Có thể gán lại giá trị cho NEW đối với BEFORE EACH ROW TRIGGER
Không thể gán giá trị cho NEW đối với AFTER EACH ROW TRIGGER 58
Không thể gán lại giá trị cho OLD
Old và New (ví dụ)
Giả sử ta có một trigger BEFORE each row được tạo trên table NHANVIEN
(với phần thực thi là null)

Thực thi câu lệnh:


UPDATE nhanvien SET luong = luong + 10 WHERE diachi = ‘Q11’;

NHANVIEN
MANV HOTEN DIACHI LUONG
:old.luong = 20
BEFORE each row
:new.luong = 30
1 NGUYEN VAN A Q11 20
30 thực thi Update set luong = :NEW.luong

2 NGUYEN VAN B Q8 50

3 NGUYEN VAN C Q10 30


:old.luong = 100
BEFORE each row
:new.luong = 110
4 NGUYEN VAN D Q11 100
110 thực thi Update set luong = :NEW.luong
59
Ví dụ
• Viết trigger mỗi khi insert, update, delete thì in ra thông tin lương của nhân viên bao gồm lương cũ,
lương mới và hiệu số giữa lương cũ và lương mới
CREATE OR REPLACE TRIGGER Print_salary_changes
BEFORE INSERT OR UPDATE OR DELETE ON emp
FOR EACH ROW
DECLARE
sal_diff NUMBER;
BEGIN
sal_diff := NVL( :NEW.luong,0) - NVL(:OLD.luong,0);
dbms_output.put('Old salary: ' || :OLD. luong );
dbms_output.put(' New salary: ' || :NEW. luong );
dbms_output.put_line(' Difference ' || sal_diff);
END;

60
Thực thi câu lệnh:
UPDATE nhanvien SET luong= luong + 10 WHERE diachi = ‘Q11’;

NHANVIEN
MANV HOTEN DIACHI LUONG
:old.luong = 20
BEFORE each row
• :new.luong = 30
sal_diff := :NEW.luong - :OLD.luong;
dbms_output.put('Old salary: ' || OLD. luong );
1 A Q11 20 dbms_output.put(' New salary: ' || :NEW. luong );
dbms_output.put_line(' Difference ' || sal_diff);

30 thực thi Update set luong = :NEW.luong

2 B Q8 50

:old.luong = 75
BEFORE each row
:new.luong = 85
sal_diff := :NEW.luong - :OLD.luong;
dbms_output.put('Old salary: ' || OLD. luong );
3 C Q11 75 dbms_output.put(' New salary: ' || :NEW. luong );
dbms_output.put_line(' Difference ' || sal_diff);

85 thực thi Update set luong = :NEW.luong 61


Ví dụ
• Viết trigger mỗi khi insert, update, delete thì in ra thông tin lương của nhân viên bao gồm lương cũ,
lương mới và hiệu số giữa lương cũ và lương mới
CREATE OR REPLACE TRIGGER Print_salary_changes
AFTER INSERT OR UPDATE ON Nhanvien
FOR EACH ROW
DECLARE
sal_diff NUMBER;
BEGIN
sal_diff := :NEW.luong - :OLD.luong;
dbms_output.put('Old salary: ' || :OLD. luong );
dbms_output.put(' New salary: ' || :NEW. luong );
dbms_output.put_line(' Difference ' || sal_diff);
END;

62
Thực thi câu lệnh:
UPDATE nhanvien SET luong = luong + 10 WHERE diachi = ‘Q11’;

MANV HOTEN DIACHI LUONG NHANVIEN

30 thực thi Update set luong := 30



:old.luong = 20
1 A Q11 20
:new.luong = 30
sal_diff := :NEW.luong - :OLD.luong;
AFTER each row dbms_output.put('Old salary: ' || OLD. luong );
dbms_output.put(' New salary: ' || :NEW. luong );
dbms_output.put_line(' Difference ' || sal_diff);

2 B Q8 50

85 thực thi Update set luong = 85


:old.luong = 75
3 C Q11 75 :new.luong = 85
sal_diff := :NEW.luong - :OLD.luong;
AFTER each row dbms_output.put('Old salary: ' || OLD. luong );
dbms_output.put(' New salary: ' || :NEW. luong ); 63
dbms_output.put_line(' Difference ' || sal_diff);
Old và New (ví dụ)
• Viết trigger đảm bảo rằng khi thêm mới một hóa đơn (hoadon) thì trị giá (trigia)
của hóa đơn đó luôn bằng 0.
INSERT INTO hoadon VALUES (‘HD03’, ‘16/06/2000’, 700)

hoadon Sohd Nghd Trigia


HD01 15/06/2000 30000
HD02 15/06/2000 200
HD03 16/06/2000 0

CREATE OR REPLACE TRIGGER insert_hoadon


BEFORE INSERT ON hoadon
FOR EACH ROW
BEGIN
:NEW.trigia := 0;
END; 64
Ví dụ
• Viết trigger mỗi khi thêm hoặc sửa dữ liệu thì HOTEN của nhân viên phải được chuyển
thành chữ in hoa.
NHANVIEN MANV HOTEN DIACHI LUONG
1 NGUYEN VAN A Q11 20
2 NGUYEN VAN B Q8 50
3 NGUYEN VAN C Q10 30
4 NGUYEN VAN D Q11 100

CREATE TRIGGER hoten_upper


BEFORE INSERT OR UPDATE ON nhanvien
FOR EACH ROW
BEGIN
:NEW.hoten := UPPER (:NEW.hoten);
END; 65
Ví dụ
• Viết trigger đảm bảo mỗi khi tăng lương của nhân viên nếu lương mới thấp hơn lương cũ thì lương vẫn được giữ nguyên
bằng lương cũ.
UPDATE nhanvien SET luong =(luong+40)/2
NHANVIEN MANV HOTEN DIACHI LUONG
1 NGUYEN VAN A Q11 20 30
2 NGUYEN VAN B Q8 50 50
3 NGUYEN VAN C Q10 30 35

CREATE OR REPLACE TRIGGER nhanvien_tangluong


BEFORE UPDATE OF luong ON nhanvien
FOR EACH ROW
BEGIN
IF (:NEW.luong < :OLD.luong) THEN
:NEW.luong := :OLD.luong;
END IF;
END;
66
Ví dụ
• Mỗi khi có một user nào đó tác động vào bảng nhanvien thì thông tin về username, ngày
cập nhật được lưu trữ trong table emp_log.

CREATE TABLE Emp_log (


Username VARCHAR2(50),
Log_date DATE
);

CREATE OR REPLACE TRIGGER Log_emp_update


AFTER INSERT OR UPDATE OR DELETE ON nhanvien
BEGIN
INSERT INTO Emp_log VALUES (USER, SYSDATE);
END;

67
Aborting Triggers with Error
Ví dụ
• Viết trigger đảm bảo mỗi khi tăng lương của nhân viên thì lương mới không được
thấp hơn lương cũ.Nếu tồn tại một trường hợp nào đó mà lương mới thấp hơn
lương cũ thì báo lỗi.
CREATE OR REPLACE TRIGGER nhanvien_tangluong
BEFORE UPDATE OF luong ON nhanvien
FOR EACH ROW
BEGIN
IF (:NEW.luong < :OLD.luong) THEN
RAISE_APPLICATION_ERROR(-20000, 'luong moi khong duoc thap
hon luong cu');
END IF;
END;

68
Aborting Triggers with Error
Ví dụ
• Viết trigger đảm bảo tuổi vào làm của nhân viên không được nhỏ hơn
18
CREATE OR REPLACE TRIGGER PersonCheckAge
AFTER INSERT OR UPDATE OF Ngaysinh, ngayvl ON NHANVIEN
FOR EACH ROW
BEGIN
IF (extract(year from :NEW.ngayvl) - extract(year from :NEW.ngaysinh)<18)
THEN
RAISE_APPLICATION_ERROR(-20000, ‘Tuoi khong nho hon 18');
END IF;
END;

69
Ví dụ: trị giá hóa đơn bằng tổng tien (tiền)
của các CTHD thuộc hóa đơn đó
SOHD NGHD TRIGIA
HD01 25/12/2008 30000
HD02 12/05/2009 23000
HD03 12/05/2009 0

SOHD MASP TIEN


HD01 BB01 15000
HD01 BB02 10000
HD01 BB03 5000
HD02 BB01 20000
HD02 BB04 3000

70
Ví dụ: trị giá hóa đơn bằng tổng tien (tiền)
của các CTHD thuộc hóa đơn đó
CREATE TRIGGER cthd_ins_ai
AFTER INSERT ON CTHD
FOR EACH ROW
BEGIN
UPDATE hoadon SET trigia = NVL(trigia, 0)+ :NEW.tien
WHERE sohd = :NEW.sohd;
END;

CREATE TRIGGER cthd_del_ad


AFTER DELETE ON CTHD
FOR EACH ROW
BEGIN
UPDATE hoadon SET trigia = NVL(trigia, 0) - :OLD.tien
WHERE sohd = :OLD.sohd;
END; 71
Ví dụ: trị giá hóa đơn bằng tổng tien (tiền)
của các CTHD thuộc hóa đơn đó
CREATE TRIGGER cthd_upd_au
AFTER UPDATE OF sohd, tien ON CTHD
FOR EACH ROW
BEGIN
UPDATE hoadon SET trigia = NVL(trigia, 0) + :NEW.tien
WHERE sohd = :NEW.sohd;
UPDATE hoadon SET trigia = NVL(trigia, 0) - :OLD.tien
WHERE sohd = :OLD.sohd;
END;

72
Ví dụ: trị giá hóa đơn bằng tổng tien (tiền)
của các CTHD thuộc hóa đơn đó (cách khác)
CREATE TRIGGER cthd_aiud_fer
AFTER INSERT OR DELETE OR UPDATE OF sohd, tien ON cthd
FOR EACH ROW
BEGIN
IF ( INSERTING OR UPDATING ) THEN
UPDATE hoadon SET trigia= NVL (trigia,0)+:NEW.tien
WHERE sohd = :NEW.sohd;
END IF;

IF ( UPDATING OR DELETING) THEN


UPDATE hoadon
SET trigia = NVL(trigia,0)-:OLD.tien
WHERE sohd = :OLD.sohd;
END IF;
END;

73
Ví dụ: trị giá hóa đơn bằng tổng sl*gia của
các CTHD thuộc hóa đơn đó
SOHD NGHD TRIGIA
HD01 25/12/2008 30000
HD02 12/05/2009 23000
HD03 12/05/2009 0

SOHD MASP SL GIA


HD01 BB01 15 1000
HD01 BB02 10 1000
HD01 BB03 1 5000
HD02 BB01 20 1000
HD02 BB04 2 1500

74
Ví dụ: trị giá hóa đơn bằng tổng sl*gia của
các CTHD thuộc hóa đơn đó
CREATE TRIGGER cthd_ins_ai
AFTER INSERT ON CTHD
FOR EACH ROW
BEGIN
UPDATE hoadon SET trigia = trigia + :NEW.sl* :NEW.gia
WHERE sohd = :NEW.sohd;
END;

CREATE TRIGGER cthd_del_ad


AFTER DELETE ON CTHD
FOR EACH ROW
BEGIN
UPDATE hoadon SET trigia = trigia - :OLD.sl* :OLD.gia
WHERE sohd = :OLD.sohd;
END;

75
Ví dụ: trị giá hóa đơn bằng tổng sl*gia của
các CTHD thuộc hóa đơn đó
CREATE TRIGGER cthd_upd_au
AFTER UPDATE ON CTHD
FOR EACH ROW
BEGIN
UPDATE hoadon SET trigia = trigia + :NEW.sl* :NEW.gia
WHERE sohd = :NEW.sohd;
UPDATE hoadon SET trigia = trigia - :OLD.sl* :OLD.gia
WHERE sohd = :OLD.sohd;
END;

76
Table mutating
• Error xảy ra khi một row-level trigger truy cập đến cùng table (mà
trigger được định nghĩa trên đó) trong lúc thực thi. Table lúc này gọi là
mutating.
OPERATION TYPE MUTATING?
insert before/statement-level No
insert after/statement-level No
update before/statement-level No
update after/statement-level No
delete before/statement-level No
delete after/statement-level No
insert before/row-level Single row NO,Multi-row yes
insert after/row-level Yes
update before/row-level Yes
update after/row-level Yes
delete before/row-level Yes
delete after/row-level Yes
77
Table mutating
employee id firstname salary deptid
1 helen 100 fin
2 john 50 fin

CREATE TRIGGER employee_raisesal


BEFORE INSERT OR UPDATE ON employee
FOR EACH ROW
WHEN (NEW.id != 1)
DECLARE
sal1 NUMBER;
BEGIN
SELECT salary INTO sal1 FROM employee;
-- more processing...
END;

update employee set salary=50 where id=2; MUTATING ERROR

78

You might also like