Bài tập & đáp án
Đề bài đọc tự do. Phần đáp án và giải thích chi tiết chỉ dành cho sinh viên đang học lớp thực hành IT004, cần mã truy cập từ giảng viên.
ĐỀ BÀI
Bài tập Trigger
Tuần 5 dùng TRIGGER để cài đặt các ràng buộc nghiệp vụ mà CHECK thông thường không xử lý được (so sánh giữa nhiều bảng, tính toán tổng hợp...), trên cả 2 lược đồ dùng chung Quản lý bán hàng và Quản lý giáo vụ. Đề bài chi tiết nằm trong file đính kèm bên dưới.
<MSSV>_<HoVaTen>_BTTH5.sql (MSSV là mã số sinh viên, HoVaTen là họ và tên).Cơ sở dữ liệu Quản lý bán hàng (câu 1-4)
- Ngày mua hàng (
NGHD) của một khách hàng thành viên sẽ lớn hơn hoặc bằng ngày khách hàng đó đăng ký thành viên (NGDK). - Ngày bán hàng (
NGHD) của một nhân viên phải lớn hơn hoặc bằng ngày nhân viên đó vào làm. - Trị giá của một hóa đơn là tổng thành tiền (số lượng * đơn giá) của các chi tiết thuộc hóa đơn đó.
- Doanh số của một khách hàng là tổng trị giá các hóa đơn mà khách hàng thành viên đó đã mua.
Cơ sở dữ liệu Quản lý giáo vụ (câu 1-12)
- Lớp trưởng của một lớp phải là học viên của lớp đó.
- Trưởng khoa phải là giáo viên thuộc khoa và có học vị "TS" hoặc "PTS".
- Học viên chỉ được thi một môn học nào đó khi lớp của học viên đã học xong môn học này.
- Mỗi học kỳ của một năm học, một lớp chỉ được học tối đa 3 môn.
- Sỉ số của một lớp bằng với số lượng học viên thuộc lớp đó.
- Trong quan hệ
DIEUKIENgiá trị của thuộc tínhMAMHvàMAMH_TRUOCtrong cùng một bộ không được giống nhau ("A","A") và cũng không tồn tại hai bộ ("A","B") và ("B","A"). - Các giáo viên có cùng học vị, học hàm, hệ số lương thì mức lương bằng nhau.
- Học viên chỉ được thi lại (lần thi >1) khi điểm của lần thi trước đó dưới 5.
- Ngày thi của lần thi sau phải lớn hơn ngày thi của lần thi trước (cùng học viên, cùng môn học).
- Học viên chỉ được thi những môn mà lớp của học viên đó đã học xong.
- Khi phân công giảng dạy một môn học, phải xét đến thứ tự trước sau giữa các môn học (sau khi học xong những môn học phải học trước mới được học những môn liền sau).
- Giáo viên chỉ được phân công dạy những môn thuộc khoa giáo viên đó phụ trách.
ĐÁP ÁN
Xem đáp án
Nội dung được bảo vệ
Nhập mã truy cập để xem đáp án Tuần 5.
Mã truy cập được cung cấp bởi giảng viên.
Đáp án đầy đủ cho Quản lý bán hàng (4 câu) và Quản lý giáo vụ (12 câu). Mỗi câu có "Bảng xét tầm ảnh hưởng" (bảng nào có thể vi phạm ràng buộc, cần Trigger nào), đáp án chính theo hướng IF EXISTS/NOT EXISTS (đúng với mọi số dòng bị tác động), và mục "Cách khác" dùng biến tạm (chỉ đúng khi thao tác ảnh hưởng đúng 1 dòng - Xem thêm lý thuyết mục 07). Gõ lại từng câu vào SSMS để nhớ lâu hơn nhé - Nút Copy ở đây chỉ mang tính hình thức thôi.
CÂU 1 (QLBH) · TRIGGER AFTER UPDATE / INSERT
Đề bài: Ngày mua hàng (NGHD) của một khách hàng thành viên sẽ lớn hơn hoặc bằng ngày khách hàng đó đăng ký thành viên (NGDK).
Bảng xét tầm ảnh hưởng
| Bảng | Thao tác cần bắt | Vì sao có thể vi phạm |
|---|---|---|
KHACHHANG | UPDATE | Sửa NGDK thành ngày muộn hơn hóa đơn đã có sẵn của khách đó. |
HOADON | INSERT, UPDATE | Thêm/sửa hóa đơn có NGHD sớm hơn NGDK của khách hàng. |
Phải viết đủ 2 Trigger trên 2 bảng khác nhau mới bảo vệ được ràng buộc từ mọi hướng thay đổi dữ liệu.
Trigger 1 - ON KHACHHANG AFTER UPDATE
CREATE TRIGGER TRG_KHACHHANG_NGDK
ON KHACHHANG
AFTER UPDATE
AS
BEGIN
IF EXISTS (
SELECT *
FROM inserted i
JOIN HOADON hd ON hd.MAKH = i.MAKH
WHERE i.NGDK > hd.NGHD
)
BEGIN
RAISERROR(N'Ngày đăng ký thành viên phải nhỏ hơn hoặc bằng ngày mua hàng.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;Cách khác (biến tạm - Chỉ đúng khi UPDATE đúng 1 khách hàng)
CREATE TRIGGER TRG_KHACHHANG_NGDK_BIENTAM
ON KHACHHANG
AFTER UPDATE
AS
BEGIN
DECLARE @MAKH CHAR(4), @NGDK SMALLDATETIME, @NGHD_SomNhat SMALLDATETIME;
SELECT @MAKH = MAKH, @NGDK = NGDK FROM inserted;
SELECT @NGHD_SomNhat = MIN(NGHD) FROM HOADON WHERE MAKH = @MAKH;
IF (@NGHD_SomNhat IS NOT NULL AND @NGDK > @NGHD_SomNhat)
BEGIN
RAISERROR(N'Ngày đăng ký thành viên phải nhỏ hơn hoặc bằng ngày mua hàng.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;SELECT @MAKH = MAKH, @NGDK = NGDK FROM inserted chỉ lấy đúng 1 dòng - Nếu câu UPDATE sửa NGDK của nhiều khách hàng cùng lúc (VD UPDATE KHACHHANG SET NGDK = ... không có WHERE), cách này chỉ kiểm tra được 1 khách hàng bất kỳ trong số đó, bỏ sót phần còn lại một cách im lặng.
Trigger 2 - ON HOADON AFTER INSERT, UPDATE
CREATE TRIGGER TRG_HOADON_NGHD_NGDK
ON HOADON
AFTER INSERT, UPDATE
AS
BEGIN
IF EXISTS (
SELECT *
FROM inserted i
JOIN KHACHHANG kh ON kh.MAKH = i.MAKH
WHERE i.NGHD < kh.NGDK
)
BEGIN
RAISERROR(N'Ngày lập hóa đơn phải lớn hơn hoặc bằng ngày khách hàng đăng ký thành viên.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;Cách khác (biến tạm - Chỉ đúng khi thêm/sửa đúng 1 hóa đơn)
CREATE TRIGGER TRG_HOADON_NGHD_NGDK_BIENTAM
ON HOADON
AFTER INSERT, UPDATE
AS
BEGIN
DECLARE @NGHD SMALLDATETIME, @NGDK SMALLDATETIME;
SELECT @NGHD = i.NGHD, @NGDK = kh.NGDK
FROM inserted i
JOIN KHACHHANG kh ON kh.MAKH = i.MAKH;
IF (@NGDK IS NOT NULL AND @NGHD < @NGDK)
BEGIN
RAISERROR(N'Ngày lập hóa đơn phải lớn hơn hoặc bằng ngày khách hàng đăng ký thành viên.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;Cùng giới hạn với Trigger 1 - Chỉ an toàn khi INSERT/UPDATE tác động đúng 1 hóa đơn. JOIN KHACHHANG (không phải LEFT JOIN) tự động bỏ qua hóa đơn không gắn khách hàng thành viên (MAKH IS NULL) - Đúng với ý đề chỉ ràng buộc "khách hàng thành viên".
Cả 2 Trigger đều dùng IF EXISTS đối chiếu toàn bộ inserted với bảng còn lại qua JOIN, thay vì gán ra 1 biến rồi so sánh - Nên đúng bất kể UPDATE/INSERT tác động 1 hay nhiều dòng.
Ràng buộc "NGHD ≥ NGDK" có thể bị phá vỡ từ 2 phía: Sửa NGDK (phía KHACHHANG) hoặc thêm/sửa hóa đơn (phía HOADON) - Bảng xét tầm ảnh hưởng ở trên chính là bước xác định điều này. Chỉ viết 1 Trigger trên 1 bảng sẽ để lọt ràng buộc từ hướng còn lại.
CÂU 2 (QLBH) · TRIGGER AFTER UPDATE / INSERT
Đề bài: Ngày bán hàng (NGHD) của một nhân viên phải lớn hơn hoặc bằng ngày nhân viên đó vào làm.
Bảng xét tầm ảnh hưởng
| Bảng | Thao tác cần bắt | Vì sao có thể vi phạm |
|---|---|---|
NHANVIEN | UPDATE | Sửa NGVL thành ngày muộn hơn hóa đơn nhân viên đó đã lập. |
HOADON | INSERT, UPDATE | Thêm/sửa hóa đơn có NGHD sớm hơn NGVL của nhân viên bán. |
Trigger 1 - ON NHANVIEN AFTER UPDATE
CREATE TRIGGER TRG_NHANVIEN_NGVL
ON NHANVIEN
AFTER UPDATE
AS
BEGIN
IF EXISTS (
SELECT *
FROM inserted i
JOIN HOADON hd ON hd.MANV = i.MANV
WHERE i.NGVL > hd.NGHD
)
BEGIN
RAISERROR(N'Ngày vào làm phải nhỏ hơn hoặc bằng ngày lập hóa đơn.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;Cách khác (biến tạm)
CREATE TRIGGER TRG_NHANVIEN_NGVL_BIENTAM
ON NHANVIEN
AFTER UPDATE
AS
BEGIN
DECLARE @MANV CHAR(4), @NGVL SMALLDATETIME, @NGHD_SomNhat SMALLDATETIME;
SELECT @MANV = MANV, @NGVL = NGVL FROM inserted;
SELECT @NGHD_SomNhat = MIN(NGHD) FROM HOADON WHERE MANV = @MANV;
IF (@NGHD_SomNhat IS NOT NULL AND @NGVL > @NGHD_SomNhat)
BEGIN
RAISERROR(N'Ngày vào làm phải nhỏ hơn hoặc bằng ngày lập hóa đơn.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;Chỉ đúng khi UPDATE sửa NGVL của đúng 1 nhân viên - Giống hệt giới hạn đã nêu ở Câu 1.
Trigger 2 - ON HOADON AFTER INSERT, UPDATE
CREATE TRIGGER TRG_HOADON_NGHD_NGVL
ON HOADON
AFTER INSERT, UPDATE
AS
BEGIN
IF EXISTS (
SELECT *
FROM inserted i
JOIN NHANVIEN nv ON nv.MANV = i.MANV
WHERE i.NGHD < nv.NGVL
)
BEGIN
RAISERROR(N'Ngày lập hóa đơn phải lớn hơn hoặc bằng ngày nhân viên vào làm.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;Cách khác (biến tạm)
CREATE TRIGGER TRG_HOADON_NGHD_NGVL_BIENTAM
ON HOADON
AFTER INSERT, UPDATE
AS
BEGIN
DECLARE @NGHD SMALLDATETIME, @NGVL SMALLDATETIME;
SELECT @NGHD = i.NGHD, @NGVL = nv.NGVL
FROM inserted i
JOIN NHANVIEN nv ON nv.MANV = i.MANV;
IF (@NGHD < @NGVL)
BEGIN
RAISERROR(N'Ngày lập hóa đơn phải lớn hơn hoặc bằng ngày nhân viên vào làm.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;Chỉ đúng khi thêm/sửa đúng 1 hóa đơn mỗi lần.
Cấu trúc giống hệt Câu 1, chỉ đổi đối tượng: NHANVIEN.NGVL thay cho KHACHHANG.NGDK.
Cùng lý do "2 hướng vi phạm" như Câu 1 - NHANVIEN mọi nhân viên đều có MANV (không NULL như HOADON.MAKH) nên không cần lọc thêm điều kiện IS NOT NULL khi JOIN. Trên thực tế có thể gộp Trigger 2 của Câu 1 và Câu 2 thành 1 Trigger duy nhất trên HOADON kiểm tra cả 2 điều kiện - Ở đây tách riêng theo từng câu cho rõ ràng khi chấm bài.
CÂU 3 (QLBH) · TRIGGER TỰ ĐỘNG CẬP NHẬT
Đề bài: Trị giá của một hóa đơn là tổng thành tiền (số lượng * đơn giá) của các chi tiết thuộc hóa đơn đó.
Bảng xét tầm ảnh hưởng
| Bảng | Thao tác cần bắt | Vì sao có thể ảnh hưởng |
|---|---|---|
CTHD | INSERT, UPDATE, DELETE | Thêm/sửa/xóa 1 dòng chi tiết đều làm thay đổi tổng thành tiền của hóa đơn chứa nó. |
Đây là Trigger tự động tính toán (không phải Trigger chặn lỗi) - CTHD không lưu sẵn đơn giá, phải lấy GIA từ SANPHAM.
CREATE TRIGGER TRG_CTHD_CAPNHAT_TRIGIA
ON CTHD
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
UPDATE hd
SET TRIGIA = ISNULL((
SELECT SUM(ct.SL * sp.GIA)
FROM CTHD ct
JOIN SANPHAM sp ON sp.MASP = ct.MASP
WHERE ct.SOHD = hd.SOHD
), 0)
FROM HOADON hd
WHERE hd.SOHD IN (
SELECT SOHD FROM inserted
UNION
SELECT SOHD FROM deleted
);
END;Cách khác (biến tạm - Chỉ đúng khi thay đổi đúng 1 dòng CTHD)
CREATE TRIGGER TRG_CTHD_CAPNHAT_TRIGIA_BIENTAM
ON CTHD
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
DECLARE @SOHD INT;
SELECT @SOHD = SOHD FROM inserted;
IF (@SOHD IS NULL) SELECT @SOHD = SOHD FROM deleted;
UPDATE HOADON
SET TRIGIA = ISNULL((
SELECT SUM(SL * GIA)
FROM CTHD ct JOIN SANPHAM sp ON sp.MASP = ct.MASP
WHERE ct.SOHD = @SOHD
), 0)
WHERE SOHD = @SOHD;
END;@SOHD lấy từ inserted trước (đúng cho INSERT/UPDATE); nếu rỗng (trường hợp DELETE, inserted không có dòng nào) mới lấy từ deleted. Cách này minh họa được cả 3 thao tác nhưng vẫn chỉ đúng khi mỗi lần INSERT/UPDATE/DELETE tác động đúng 1 dòng CTHD - Xóa nhiều dòng chi tiết cùng lúc (VD DELETE FROM CTHD WHERE SOHD = 1001 khi hóa đơn có nhiều sản phẩm) sẽ chỉ cập nhật đúng cho 1 hóa đơn dù thực tế có thể ảnh hưởng nhiều hóa đơn khác trong cùng batch.
UNION gộp SOHD từ cả inserted lẫn deleted thành 1 danh sách duy nhất các hóa đơn cần tính lại, phủ đủ cả 3 thao tác INSERT/UPDATE/DELETE trong cùng 1 Trigger; ISNULL(..., 0) đảm bảo hóa đơn không còn dòng chi tiết nào (xóa hết) vẫn nhận TRIGIA = 0 thay vì NULL.
INSERT chỉ có dữ liệu trong inserted, DELETE chỉ có trong deleted, còn UPDATE có ở cả hai - Dùng UNION trên cả 2 bảng ảo là cách duy nhất để 1 Trigger chung xử lý đúng cho tất cả trường hợp, đồng thời tính lại đúng cho mọi hóa đơn bị ảnh hưởng nếu batch thay đổi nhiều dòng CTHD của nhiều hóa đơn khác nhau cùng lúc.
CÂU 4 (QLBH) · TRIGGER TỰ ĐỘNG CẬP NHẬT
Đề bài: Doanh số của một khách hàng là tổng trị giá các hóa đơn mà khách hàng thành viên đó đã mua.
Bảng xét tầm ảnh hưởng
| Bảng | Thao tác cần bắt | Vì sao có thể ảnh hưởng |
|---|---|---|
HOADON | INSERT, UPDATE, DELETE | Thêm/sửa/xóa hóa đơn (hoặc TRIGIA của nó bị Trigger Câu 3 cập nhật) đều làm thay đổi tổng doanh số của khách hàng. |
CREATE TRIGGER TRG_HOADON_CAPNHAT_DOANHSO
ON HOADON
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
UPDATE kh
SET DOANHSO = ISNULL((
SELECT SUM(hd.TRIGIA)
FROM HOADON hd
WHERE hd.MAKH = kh.MAKH
), 0)
FROM KHACHHANG kh
WHERE kh.MAKH IN (
SELECT MAKH FROM inserted WHERE MAKH IS NOT NULL
UNION
SELECT MAKH FROM deleted WHERE MAKH IS NOT NULL
);
END;Cách khác (biến tạm - Chỉ đúng khi thay đổi đúng 1 hóa đơn)
CREATE TRIGGER TRG_HOADON_CAPNHAT_DOANHSO_BIENTAM
ON HOADON
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
DECLARE @MAKH CHAR(4);
SELECT @MAKH = MAKH FROM inserted;
IF (@MAKH IS NULL) SELECT @MAKH = MAKH FROM deleted;
IF (@MAKH IS NOT NULL)
BEGIN
UPDATE KHACHHANG
SET DOANHSO = ISNULL((SELECT SUM(TRIGIA) FROM HOADON WHERE MAKH = @MAKH), 0)
WHERE MAKH = @MAKH;
END
END;Cùng cấu trúc và cùng giới hạn với Câu 3 - Chỉ đúng khi mỗi lần thay đổi tác động đúng 1 hóa đơn.
Cấu trúc giống hệt Câu 3, chỉ đổi từ tính TRIGIA của hóa đơn sang tính DOANHSO của khách hàng; lọc thêm MAKH IS NOT NULL vì hóa đơn khách vãng lai không có MAKH.
SQL Server mặc định cho phép Trigger lồng nhau (nested trigger): Khi Trigger Câu 3 chạy UPDATE HOADON SET TRIGIA = ..., chính thao tác UPDATE đó sẽ tự động kích hoạt Trigger AFTER UPDATE này trên HOADON - Nghĩa là chỉ cần CTHD thay đổi, cả TRIGIA lẫn DOANHSO đều tự động đồng bộ theo dây chuyền mà không cần gọi thủ công.
CÂU 1 (QGV) · TRIGGER AFTER INSERT, UPDATE, DELETE
Đề bài: Lớp trưởng của một lớp phải là học viên của lớp đó.
Bảng xét tầm ảnh hưởng
| Bảng | Thao tác cần bắt | Vì sao có thể vi phạm |
|---|---|---|
LOP | INSERT, UPDATE | Gán TRGLOP là 1 học viên không thuộc lớp đó. |
HOCVIEN | DELETE, UPDATE | Xóa học viên đang là lớp trưởng, hoặc chuyển học viên đó sang lớp khác. |
Trigger 1 - ON LOP AFTER INSERT, UPDATE
CREATE TRIGGER TRG_LOP_KIEMTRA_LOPTRUONG
ON LOP
AFTER INSERT, UPDATE
AS
BEGIN
IF EXISTS (
SELECT * FROM inserted i
WHERE i.TRGLOP IS NOT NULL
AND NOT EXISTS (
SELECT * FROM HOCVIEN hv
WHERE hv.MAHV = i.TRGLOP AND hv.MALOP = i.MALOP
)
)
BEGIN
RAISERROR(N'Lớp trưởng của một lớp phải là học viên của chính lớp đó.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;i.TRGLOP IS NOT NULL cho phép lớp chưa có lớp trưởng (giá trị NULL) - Không nên chặn luôn cả trường hợp hợp lệ này.
Trigger 2 - ON HOCVIEN AFTER DELETE, UPDATE
CREATE TRIGGER TRG_HOCVIEN_BAOVE_LOPTRUONG
ON HOCVIEN
AFTER DELETE, UPDATE
AS
BEGIN
IF EXISTS (
SELECT *
FROM deleted d
JOIN LOP l ON l.TRGLOP = d.MAHV
WHERE NOT EXISTS (
SELECT * FROM inserted i
WHERE i.MAHV = d.MAHV AND i.MALOP = l.MALOP
)
)
BEGIN
RAISERROR(N'Học viên đang là lớp trưởng - không thể xóa hoặc chuyển sang lớp khác.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;Cách khác (biến tạm - Chỉ đúng khi thay đổi đúng 1 học viên, chỉ bắt DELETE)
CREATE TRIGGER TRG_HOCVIEN_BAOVE_LOPTRUONG_BIENTAM
ON HOCVIEN
AFTER DELETE
AS
BEGIN
DECLARE @MAHV CHAR(5), @MALOP CHAR(3);
SELECT @MAHV = MAHV, @MALOP = MALOP FROM deleted;
IF EXISTS (SELECT * FROM LOP WHERE TRGLOP = @MAHV AND MALOP = @MALOP)
BEGIN
RAISERROR(N'Học viên đang là lớp trưởng, không thể xóa.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;Chỉ xử lý được DELETE đúng 1 học viên - Không bắt được trường hợp UPDATE đổi MALOP của lớp trưởng sang lớp khác, và không đúng nếu xóa nhiều học viên cùng lúc.
Trigger 1 chặn gán sai ngay từ phía LOP; Trigger 2 chặn việc học viên đang là lớp trưởng bị xóa hoặc "rời lớp" (đổi MALOP) từ phía HOCVIEN, dùng NOT EXISTS để kiểm tra học viên đó (nếu còn tồn tại sau thao tác) có còn đúng lớp mà mình đang làm lớp trưởng không.
AFTER DELETE, UPDATE gộp chung trên Trigger 2 vì cùng 1 điều kiện NOT EXISTS xử lý đúng cho cả 2 tình huống: Khi DELETE, inserted rỗng nên NOT EXISTS luôn đúng (chặn); khi UPDATE, chỉ chặn nếu dòng mới không còn khớp đúng lớp đang làm lớp trưởng.
CÂU 2 (QGV) · TRIGGER AFTER INSERT, UPDATE
Đề bài: Trưởng khoa phải là giáo viên thuộc khoa và có học vị "TS" hoặc "PTS".
Bảng xét tầm ảnh hưởng
| Bảng | Thao tác cần bắt | Vì sao có thể vi phạm |
|---|---|---|
KHOA | INSERT, UPDATE | Gán TRGKHOA là giáo viên không thuộc khoa, hoặc học vị không phải TS/PTS. |
GIAOVIEN | UPDATE | Đổi MAKHOA hoặc hạ HOCVI của giáo viên đang là trưởng khoa. |
Trigger 1 - ON KHOA AFTER INSERT, UPDATE
CREATE TRIGGER TRG_KHOA_KIEMTRA_TRGKHOA
ON KHOA
AFTER INSERT, UPDATE
AS
BEGIN
IF EXISTS (
SELECT * FROM inserted i
WHERE i.TRGKHOA IS NOT NULL
AND NOT EXISTS (
SELECT * FROM GIAOVIEN gv
WHERE gv.MAGV = i.TRGKHOA
AND gv.MAKHOA = i.MAKHOA
AND gv.HOCVI IN (N'TS', N'PTS')
)
)
BEGIN
RAISERROR(N'Trưởng khoa phải là giáo viên thuộc khoa và có học vị TS hoặc PTS.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;Cách khác (biến tạm - Chỉ đúng khi INSERT/UPDATE đúng 1 khoa)
CREATE TRIGGER TRG_KHOA_KIEMTRA_TRGKHOA_BIENTAM
ON KHOA
AFTER INSERT, UPDATE
AS
BEGIN
DECLARE @TRGKHOA CHAR(4), @MAKHOA CHAR(4);
SELECT @TRGKHOA = TRGKHOA, @MAKHOA = MAKHOA FROM inserted;
IF (@TRGKHOA IS NOT NULL AND NOT EXISTS (
SELECT * FROM GIAOVIEN
WHERE MAGV = @TRGKHOA AND MAKHOA = @MAKHOA AND HOCVI IN (N'TS', N'PTS')
))
BEGIN
RAISERROR(N'Trưởng khoa phải là giáo viên thuộc khoa và có học vị TS hoặc PTS.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;Trigger 2 - ON GIAOVIEN AFTER UPDATE
CREATE TRIGGER TRG_GIAOVIEN_BAOVE_TRGKHOA
ON GIAOVIEN
AFTER UPDATE
AS
BEGIN
IF EXISTS (
SELECT *
FROM inserted i
JOIN KHOA k ON k.TRGKHOA = i.MAGV
WHERE i.MAKHOA <> k.MAKHOA OR i.HOCVI NOT IN (N'TS', N'PTS')
)
BEGIN
RAISERROR(N'Giáo viên đang là trưởng khoa - không thể đổi khoa hoặc hạ học vị xuống dưới TS/PTS.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;Trigger 1 kiểm tra ngay khi gán/đổi trưởng khoa; Trigger 2 bảo vệ ràng buộc theo chiều ngược lại - Chặn việc chỉnh sửa thông tin giáo viên khiến họ không còn đủ điều kiện làm trưởng khoa của khoa mình đang phụ trách.
Đây là ví dụ rõ nhất cho việc lập bảng xét tầm ảnh hưởng trước khi viết Trigger - Nếu chỉ nhìn vào đề bài theo hướng "kiểm tra khi gán trưởng khoa" (Trigger 1), rất dễ bỏ sót tình huống dữ liệu bị phá vỡ ràng buộc từ phía GIAOVIEN (Trigger 2), vì đây là bảng không được nhắc trực tiếp trong câu hỏi.
CÂU 3 (QGV) · TRIGGER AFTER INSERT, UPDATE
Đề bài: Học viên chỉ được thi một môn học nào đó khi lớp của học viên đã học xong môn học này.
Bảng xét tầm ảnh hưởng
| Bảng | Thao tác cần bắt | Vì sao có thể vi phạm |
|---|---|---|
KETQUATHI | INSERT, UPDATE | Thêm/sửa 1 kết quả thi ứng với môn mà lớp học viên chưa học xong. |
"Học xong" được hiểu là GIANGDAY đã có dòng (lớp, môn) với DENNGAY không muộn hơn ngày thi (NGTHI).
CREATE TRIGGER TRG_KETQUATHI_KIEMTRA_DAHOC
ON KETQUATHI
AFTER INSERT, UPDATE
AS
BEGIN
IF EXISTS (
SELECT *
FROM inserted i
JOIN HOCVIEN hv ON hv.MAHV = i.MAHV
WHERE NOT EXISTS (
SELECT * FROM GIANGDAY gd
WHERE gd.MALOP = hv.MALOP AND gd.MAMH = i.MAMH AND gd.DENNGAY <= i.NGTHI
)
)
BEGIN
RAISERROR(N'Học viên chỉ được thi môn học mà lớp mình đã học xong.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;Cách khác (biến tạm - Chỉ đúng khi thêm/sửa đúng 1 kết quả thi)
CREATE TRIGGER TRG_KETQUATHI_KIEMTRA_DAHOC_BIENTAM
ON KETQUATHI
AFTER INSERT, UPDATE
AS
BEGIN
DECLARE @MAHV CHAR(5), @MAMH CHAR(10), @NGTHI SMALLDATETIME, @MALOP CHAR(3);
SELECT @MAHV = MAHV, @MAMH = MAMH, @NGTHI = NGTHI FROM inserted;
SELECT @MALOP = MALOP FROM HOCVIEN WHERE MAHV = @MAHV;
IF NOT EXISTS (
SELECT * FROM GIANGDAY
WHERE MALOP = @MALOP AND MAMH = @MAMH AND DENNGAY <= @NGTHI
)
BEGIN
RAISERROR(N'Học viên chỉ được thi môn học mà lớp mình đã học xong.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;NOT EXISTS lồng bên trong tìm dòng GIANGDAY chứng minh lớp đã học xong môn đó (DENNGAY <= NGTHI); nếu không tìm thấy dòng nào như vậy cho 1 học viên đang thi, đó là vi phạm.
"Học xong" không thể chỉ kiểm tra "có phân công giảng dạy hay không" - Phải kiểm tra thêm điều kiện thời gian DENNGAY <= NGTHI, nếu không thì học viên vẫn có thể "thi" một môn đang được giảng dạy dở dang.
CÂU 4 (QGV) · TRIGGER AFTER INSERT, UPDATE
Đề bài: Mỗi học kỳ của một năm học, một lớp chỉ được học tối đa 3 môn.
Bảng xét tầm ảnh hưởng
| Bảng | Thao tác cần bắt | Vì sao có thể vi phạm |
|---|---|---|
GIANGDAY | INSERT, UPDATE | Thêm/sửa phân công khiến 1 lớp có hơn 3 môn trong cùng học kỳ/năm. |
CREATE TRIGGER TRG_GIANGDAY_TOIDA_3MON
ON GIANGDAY
AFTER INSERT, UPDATE
AS
BEGIN
IF EXISTS (
SELECT MALOP, HOCKY, NAM
FROM GIANGDAY
WHERE MALOP IN (SELECT MALOP FROM inserted)
GROUP BY MALOP, HOCKY, NAM
HAVING COUNT(*) > 3
)
BEGIN
RAISERROR(N'Mỗi học kỳ của một năm học, một lớp chỉ được học tối đa 3 môn.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;Cách khác (biến tạm - Chỉ đúng khi thêm/sửa đúng 1 phân công)
CREATE TRIGGER TRG_GIANGDAY_TOIDA_3MON_BIENTAM
ON GIANGDAY
AFTER INSERT, UPDATE
AS
BEGIN
DECLARE @MALOP CHAR(3), @HOCKY TINYINT, @NAM SMALLINT, @SoMon INT;
SELECT @MALOP = MALOP, @HOCKY = HOCKY, @NAM = NAM FROM inserted;
SELECT @SoMon = COUNT(*) FROM GIANGDAY WHERE MALOP = @MALOP AND HOCKY = @HOCKY AND NAM = @NAM;
IF (@SoMon > 3)
BEGIN
RAISERROR(N'Mỗi học kỳ của một năm học, một lớp chỉ được học tối đa 3 môn.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;GROUP BY MALOP, HOCKY, NAM kèm HAVING COUNT(*) > 3 tìm những nhóm (lớp, học kỳ, năm) đã có hơn 3 môn, giới hạn trước bằng WHERE MALOP IN (SELECT MALOP FROM inserted) để không phải quét toàn bộ GIANGDAY.
Không cần lọc thêm theo HOCKY/NAM trong mệnh đề WHERE ngoài vì GROUP BY đã tự tách đúng theo từng (học kỳ, năm) - Chỉ cần thu hẹp đúng những lớp vừa được phân công thêm.
CÂU 5 (QGV) · TRIGGER TỰ ĐỘNG CẬP NHẬT
Đề bài: Sỉ số của một lớp bằng với số lượng học viên thuộc lớp đó.
Bảng xét tầm ảnh hưởng
| Bảng | Thao tác cần bắt | Vì sao có thể ảnh hưởng |
|---|---|---|
HOCVIEN | INSERT, UPDATE, DELETE | Thêm/xóa học viên, hoặc đổi MALOP của học viên, đều làm thay đổi sỉ số của (các) lớp liên quan. |
CREATE TRIGGER TRG_HOCVIEN_CAPNHAT_SISO
ON HOCVIEN
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
UPDATE l
SET SISO = (
SELECT COUNT(*) FROM HOCVIEN hv WHERE hv.MALOP = l.MALOP
)
FROM LOP l
WHERE l.MALOP IN (
SELECT MALOP FROM inserted
UNION
SELECT MALOP FROM deleted
);
END;Cách khác (biến tạm - Chỉ đúng khi thay đổi đúng 1 học viên)
CREATE TRIGGER TRG_HOCVIEN_CAPNHAT_SISO_BIENTAM
ON HOCVIEN
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
DECLARE @MALOP CHAR(3);
SELECT @MALOP = MALOP FROM inserted;
IF (@MALOP IS NULL) SELECT @MALOP = MALOP FROM deleted;
UPDATE LOP
SET SISO = (SELECT COUNT(*) FROM HOCVIEN WHERE MALOP = @MALOP)
WHERE MALOP = @MALOP;
END;Bỏ sót lớp cũ khi UPDATE đổi MALOP của 1 học viên - Cách biến tạm ở đây chỉ cập nhật đúng cho lớp mới (lấy từ inserted), không cập nhật lại sỉ số cho lớp cũ (phải lấy từ deleted) mà học viên đó vừa rời đi. Đây là lý do bản IF EXISTS/UNION ở trên luôn là lựa chọn an toàn hơn.
Tính lại SISO bằng COUNT(*) trực tiếp trên HOCVIEN cho từng lớp bị ảnh hưởng, xác định bằng UNION giữa inserted và deleted - Giống kỹ thuật đã dùng ở Câu 3, Câu 4 (QLBH).
Khi UPDATE đổi MALOP của 1 học viên, cả lớp cũ lẫn lớp mới đều phải cập nhật lại sỉ số - UNION giữa inserted (lớp mới) và deleted (lớp cũ) đảm bảo không bỏ sót lớp nào trong 2 lớp đó.
CÂU 6 (QGV) · TRIGGER AFTER INSERT, UPDATE
Đề bài: Trong quan hệ DIEUKIEN giá trị của thuộc tính MAMH và MAMH_TRUOC trong cùng một bộ không được giống nhau ("A","A") và cũng không tồn tại hai bộ ("A","B") và ("B","A").
Bảng xét tầm ảnh hưởng
| Bảng | Thao tác cần bắt | Vì sao có thể vi phạm |
|---|---|---|
DIEUKIEN | INSERT, UPDATE | Thêm/sửa 1 dòng tạo thành cặp (A,A), hoặc tạo cặp đối xứng ngược với 1 dòng đã có. |
CREATE TRIGGER TRG_DIEUKIEN_KIEMTRA
ON DIEUKIEN
AFTER INSERT, UPDATE
AS
BEGIN
IF EXISTS (SELECT * FROM inserted WHERE MAMH = MAMH_TRUOC)
BEGIN
RAISERROR(N'Một môn học không thể là điều kiện tiên quyết của chính nó.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
IF EXISTS (
SELECT * FROM inserted i
JOIN DIEUKIEN dk ON dk.MAMH = i.MAMH_TRUOC AND dk.MAMH_TRUOC = i.MAMH
)
BEGIN
RAISERROR(N'Không được tồn tại đồng thời (A,B) và (B,A) trong DIEUKIEN.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;Cách khác (biến tạm - Chỉ đúng khi thêm/sửa đúng 1 dòng)
CREATE TRIGGER TRG_DIEUKIEN_KIEMTRA_BIENTAM
ON DIEUKIEN
AFTER INSERT, UPDATE
AS
BEGIN
DECLARE @MAMH CHAR(10), @MAMH_TRUOC CHAR(10);
SELECT @MAMH = MAMH, @MAMH_TRUOC = MAMH_TRUOC FROM inserted;
IF (@MAMH = @MAMH_TRUOC)
BEGIN
RAISERROR(N'Một môn học không thể là điều kiện tiên quyết của chính nó.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
IF EXISTS (SELECT * FROM DIEUKIEN WHERE MAMH = @MAMH_TRUOC AND MAMH_TRUOC = @MAMH)
BEGIN
RAISERROR(N'Không được tồn tại đồng thời (A,B) và (B,A) trong DIEUKIEN.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;IF đầu chặn cặp (A,A) ngay trong inserted; IF sau tự JOIN DIEUKIEN với chính nó (self-join qua bảng ảo inserted) để tìm dòng đối xứng (B,A) đã tồn tại ứng với dòng mới thêm (A,B).
Đây là 2 điều kiện độc lập nên tách thành 2 khối IF riêng, mỗi khối có thông báo lỗi rõ ràng khác nhau - Nếu gộp chung 1 IF ... OR ... thì thông báo lỗi sẽ mơ hồ, không biết chính xác vi phạm nào đã xảy ra.
CÂU 7 (QGV) · TRIGGER AFTER INSERT, UPDATE
Đề bài: Các giáo viên có cùng học vị, học hàm, hệ số lương thì mức lương bằng nhau.
Bảng xét tầm ảnh hưởng
| Bảng | Thao tác cần bắt | Vì sao có thể vi phạm |
|---|---|---|
GIAOVIEN | INSERT, UPDATE | Thêm/sửa 1 giáo viên với mức lương khác giáo viên khác cùng (học vị, học hàm, hệ số). |
CREATE TRIGGER TRG_GIAOVIEN_KIEMTRA_MUCLUONG
ON GIAOVIEN
AFTER INSERT, UPDATE
AS
BEGIN
IF EXISTS (
SELECT *
FROM inserted i
JOIN GIAOVIEN gv
ON gv.HOCVI = i.HOCVI
AND ISNULL(gv.HOCHAM, N'') = ISNULL(i.HOCHAM, N'')
AND gv.HESO = i.HESO
AND gv.MAGV <> i.MAGV
WHERE gv.MUCLUONG <> i.MUCLUONG
)
BEGIN
RAISERROR(N'Giáo viên có cùng học vị, học hàm, hệ số lương thì mức lương phải bằng nhau.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;Cách khác (biến tạm - Chỉ đúng khi thêm/sửa đúng 1 giáo viên)
CREATE TRIGGER TRG_GIAOVIEN_KIEMTRA_MUCLUONG_BIENTAM
ON GIAOVIEN
AFTER INSERT, UPDATE
AS
BEGIN
DECLARE @MAGV CHAR(4), @HOCVI VARCHAR(10), @HOCHAM VARCHAR(10),
@HESO NUMERIC(4,2), @MUCLUONG MONEY;
SELECT @MAGV = MAGV, @HOCVI = HOCVI, @HOCHAM = HOCHAM,
@HESO = HESO, @MUCLUONG = MUCLUONG
FROM inserted;
IF EXISTS (
SELECT * FROM GIAOVIEN
WHERE HOCVI = @HOCVI
AND ISNULL(HOCHAM, N'') = ISNULL(@HOCHAM, N'')
AND HESO = @HESO
AND MAGV <> @MAGV
AND MUCLUONG <> @MUCLUONG
)
BEGIN
RAISERROR(N'Giáo viên có cùng học vị, học hàm, hệ số lương thì mức lương phải bằng nhau.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;JOIN GIAOVIEN với chính bảng đó (qua inserted) để so khớp 3 tiêu chí HOCVI, HOCHAM, HESO; nếu tìm được giáo viên khác (MAGV <> i.MAGV) cùng cả 3 tiêu chí nhưng khác MUCLUONG, đó là vi phạm.
HOCHAM có thể NULL (giáo viên chưa có học hàm) - Trong SQL, NULL = NULL không cho kết quả TRUE mà là không xác định, nên nếu so sánh trực tiếp gv.HOCHAM = i.HOCHAM, 2 giáo viên cùng chưa có học hàm sẽ bị hiểu nhầm là "khác nhau" và không được so khớp. Bọc ISNULL(..., N'') quy đổi NULL thành chuỗi rỗng trước khi so sánh, giúp 2 giá trị NULL được xem là bằng nhau.
CÂU 8 (QGV) · TRIGGER AFTER INSERT
Đề bài: Học viên chỉ được thi lại (lần thi >1) khi điểm của lần thi trước đó dưới 5.
Bảng xét tầm ảnh hưởng
| Bảng | Thao tác cần bắt | Vì sao có thể vi phạm |
|---|---|---|
KETQUATHI | INSERT | Thêm 1 lượt thi lại (LANTHI > 1) trong khi lần thi liền trước đã đạt (DIEM >= 5). |
CREATE TRIGGER TRG_KETQUATHI_KIEMTRA_THILAI
ON KETQUATHI
AFTER INSERT
AS
BEGIN
IF EXISTS (
SELECT *
FROM inserted i
WHERE i.LANTHI > 1
AND NOT EXISTS (
SELECT * FROM KETQUATHI kq
WHERE kq.MAHV = i.MAHV AND kq.MAMH = i.MAMH
AND kq.LANTHI = i.LANTHI - 1 AND kq.DIEM < 5
)
)
BEGIN
RAISERROR(N'Học viên chỉ được thi lại khi điểm lần thi trước đó dưới 5.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;Cách khác (biến tạm - Chỉ đúng khi thêm đúng 1 kết quả thi)
CREATE TRIGGER TRG_KETQUATHI_KIEMTRA_THILAI_BIENTAM
ON KETQUATHI
AFTER INSERT
AS
BEGIN
DECLARE @MAHV CHAR(5), @MAMH CHAR(10), @LANTHI TINYINT, @DiemLanTruoc NUMERIC(4,2);
SELECT @MAHV = MAHV, @MAMH = MAMH, @LANTHI = LANTHI FROM inserted;
IF (@LANTHI > 1)
BEGIN
SELECT @DiemLanTruoc = DIEM FROM KETQUATHI
WHERE MAHV = @MAHV AND MAMH = @MAMH AND LANTHI = @LANTHI - 1;
IF (@DiemLanTruoc IS NULL OR @DiemLanTruoc >= 5)
BEGIN
RAISERROR(N'Học viên chỉ được thi lại khi điểm lần thi trước đó dưới 5.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END
END;Chỉ kiểm tra khi LANTHI > 1 (lần thi đầu tiên không cần điều kiện gì); NOT EXISTS tìm dòng kết quả lần thi liền trước (LANTHI - 1) có DIEM < 5 - Nếu không tìm thấy (nghĩa là lần trước đã đạt, hoặc không hề có lần trước), đó là vi phạm.
NOT EXISTS tự động xử lý luôn cả trường hợp lần thi trước không tồn tại (VD chèn thẳng LANTHI = 3 mà bỏ qua LANTHI = 2) - Cách biến tạm phải tự thêm điều kiện @DiemLanTruoc IS NULL mới xử lý đúng tình huống này, cho thấy NOT EXISTS vừa gọn vừa ít sót hơn.
CÂU 9 (QGV) · TRIGGER AFTER INSERT, UPDATE
Đề bài: Ngày thi của lần thi sau phải lớn hơn ngày thi của lần thi trước (cùng học viên, cùng môn học).
Bảng xét tầm ảnh hưởng
| Bảng | Thao tác cần bắt | Vì sao có thể vi phạm |
|---|---|---|
KETQUATHI | INSERT, UPDATE | Thêm/sửa NGTHI khiến thứ tự ngày thi không còn tăng dần theo LANTHI. |
CREATE TRIGGER TRG_KETQUATHI_KIEMTRA_NGTHI
ON KETQUATHI
AFTER INSERT, UPDATE
AS
BEGIN
IF EXISTS (
SELECT *
FROM inserted i
JOIN KETQUATHI kq
ON kq.MAHV = i.MAHV AND kq.MAMH = i.MAMH AND kq.LANTHI <> i.LANTHI
WHERE (kq.LANTHI < i.LANTHI AND kq.NGTHI >= i.NGTHI)
OR (kq.LANTHI > i.LANTHI AND kq.NGTHI <= i.NGTHI)
)
BEGIN
RAISERROR(N'Ngày thi của lần thi sau phải lớn hơn ngày thi của lần thi trước.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;Cách khác (biến tạm - Chỉ đúng khi thêm/sửa đúng 1 kết quả thi)
CREATE TRIGGER TRG_KETQUATHI_KIEMTRA_NGTHI_BIENTAM
ON KETQUATHI
AFTER INSERT, UPDATE
AS
BEGIN
DECLARE @MAHV CHAR(5), @MAMH CHAR(10), @LANTHI TINYINT, @NGTHI SMALLDATETIME;
SELECT @MAHV = MAHV, @MAMH = MAMH, @LANTHI = LANTHI, @NGTHI = NGTHI FROM inserted;
IF EXISTS (
SELECT * FROM KETQUATHI
WHERE MAHV = @MAHV AND MAMH = @MAMH AND LANTHI <> @LANTHI
AND (
(LANTHI < @LANTHI AND NGTHI >= @NGTHI)
OR (LANTHI > @LANTHI AND NGTHI <= @NGTHI)
)
)
BEGIN
RAISERROR(N'Ngày thi của lần thi sau phải lớn hơn ngày thi của lần thi trước.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;JOIN mỗi dòng vừa thêm/sửa với các lần thi khác của cùng học viên, cùng môn (kq.LANTHI <> i.LANTHI); 2 điều kiện trong WHERE xét cả 2 chiều - Lần trước có ngày thi không nhỏ hơn lần đang xét, hoặc lần sau có ngày thi không lớn hơn.
Phải xét cả 2 chiều (lần thi trước lẫn lần thi sau so với dòng đang thêm/sửa) vì UPDATE có thể sửa ngày thi của bất kỳ lần thi nào, không nhất thiết là lần cuối cùng - Nếu chỉ so với "lần thi liền trước" như Câu 8, sẽ bỏ sót trường hợp sửa 1 lần thi ở giữa làm sai thứ tự với lần thi phía sau nó.
CÂU 10 (QGV) · CÙNG RÀNG BUỘC VỚI CÂU 3
Đề bài: Học viên chỉ được thi những môn mà lớp của học viên đó đã học xong.
TRG_KETQUATHI_KIEMTRA_DAHOC đã tạo ở Câu 3 xử lý đầy đủ ràng buộc này, không cần tạo thêm Trigger mới - Tạo trùng sẽ khiến 2 Trigger cùng chạy trên cùng 1 sự kiện, không sai nhưng thừa và khó bảo trì.Không có đáp án SQL mới - Xem lại Trigger TRG_KETQUATHI_KIEMTRA_DAHOC ở Câu 3.
Trước khi viết Trigger, nên đối chiếu nhanh với các câu đã làm trước đó trong cùng đề - Nhận ra 2 câu trùng ràng buộc giúp tránh viết trùng lặp, đồng thời là một bước kiểm tra hữu ích để chắc chắn không hiểu sai đề.
CÂU 11 (QGV) · TRIGGER AFTER INSERT, UPDATE
Đề bài: Khi phân công giảng dạy một môn học, phải xét đến thứ tự trước sau giữa các môn học (sau khi học xong những môn học phải học trước mới được học những môn liền sau).
Bảng xét tầm ảnh hưởng
| Bảng | Thao tác cần bắt | Vì sao có thể vi phạm |
|---|---|---|
GIANGDAY | INSERT, UPDATE | Phân công 1 lớp học môn X trong khi lớp đó chưa học xong (các) môn tiên quyết của X. |
CREATE TRIGGER TRG_GIANGDAY_KIEMTRA_TIENQUYET
ON GIANGDAY
AFTER INSERT, UPDATE
AS
BEGIN
IF EXISTS (
SELECT *
FROM inserted i
JOIN DIEUKIEN dk ON dk.MAMH = i.MAMH
WHERE NOT EXISTS (
SELECT * FROM GIANGDAY gd
WHERE gd.MALOP = i.MALOP
AND gd.MAMH = dk.MAMH_TRUOC
AND gd.DENNGAY <= i.TUNGAY
)
)
BEGIN
RAISERROR(N'Lớp phải học xong các môn tiên quyết trước khi được phân công học môn liền sau.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;Cách khác (biến tạm - Chỉ đúng khi thêm/sửa đúng 1 phân công)
CREATE TRIGGER TRG_GIANGDAY_KIEMTRA_TIENQUYET_BIENTAM
ON GIANGDAY
AFTER INSERT, UPDATE
AS
BEGIN
DECLARE @MALOP CHAR(3), @MAMH CHAR(10), @TUNGAY SMALLDATETIME;
SELECT @MALOP = MALOP, @MAMH = MAMH, @TUNGAY = TUNGAY FROM inserted;
IF EXISTS (
SELECT * FROM DIEUKIEN dk
WHERE dk.MAMH = @MAMH
AND NOT EXISTS (
SELECT * FROM GIANGDAY gd
WHERE gd.MALOP = @MALOP AND gd.MAMH = dk.MAMH_TRUOC AND gd.DENNGAY <= @TUNGAY
)
)
BEGIN
RAISERROR(N'Lớp phải học xong các môn tiên quyết trước khi được phân công học môn liền sau.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;JOIN DIEUKIEN để lấy hết các môn tiên quyết của môn vừa được phân công; NOT EXISTS lồng bên trong kiểm tra lớp đó đã có dòng GIANGDAY học xong (DENNGAY <= TUNGAY của môn mới) từng môn tiên quyết hay chưa.
1 môn học có thể có nhiều hơn 1 môn tiên quyết (VD môn CSDL trong dữ liệu mẫu có cả CTRR lẫn CTDLGT) - Phải kiểm tra lớp đã học xong tất cả các môn tiên quyết đó, không chỉ 1 môn bất kỳ, nên cần NOT EXISTS đảm bảo không có môn tiên quyết nào còn thiếu.
CÂU 12 (QGV) · TRIGGER AFTER INSERT, UPDATE
Đề bài: Giáo viên chỉ được phân công dạy những môn thuộc khoa giáo viên đó phụ trách.
Bảng xét tầm ảnh hưởng
| Bảng | Thao tác cần bắt | Vì sao có thể vi phạm |
|---|---|---|
GIANGDAY | INSERT, UPDATE | Phân công 1 giáo viên dạy môn không thuộc khoa của giáo viên đó. |
CREATE TRIGGER TRG_GIANGDAY_KIEMTRA_KHOA
ON GIANGDAY
AFTER INSERT, UPDATE
AS
BEGIN
IF EXISTS (
SELECT *
FROM inserted i
JOIN GIAOVIEN gv ON gv.MAGV = i.MAGV
JOIN MONHOC mh ON mh.MAMH = i.MAMH
WHERE gv.MAKHOA <> mh.MAKHOA
)
BEGIN
RAISERROR(N'Giáo viên chỉ được phân công dạy những môn học thuộc khoa mình phụ trách.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;Cách khác (biến tạm - Chỉ đúng khi thêm/sửa đúng 1 phân công)
CREATE TRIGGER TRG_GIANGDAY_KIEMTRA_KHOA_BIENTAM
ON GIANGDAY
AFTER INSERT, UPDATE
AS
BEGIN
DECLARE @MAGV CHAR(4), @MAMH CHAR(10), @MAKHOA_GV CHAR(4), @MAKHOA_MH CHAR(4);
SELECT @MAGV = MAGV, @MAMH = MAMH FROM inserted;
SELECT @MAKHOA_GV = MAKHOA FROM GIAOVIEN WHERE MAGV = @MAGV;
SELECT @MAKHOA_MH = MAKHOA FROM MONHOC WHERE MAMH = @MAMH;
IF (@MAKHOA_GV <> @MAKHOA_MH)
BEGIN
RAISERROR(N'Giáo viên chỉ được phân công dạy những môn học thuộc khoa mình phụ trách.', 16, 1);
ROLLBACK TRANSACTION;
RETURN;
END
END;JOIN đồng thời GIAOVIEN (lấy khoa của giáo viên) và MONHOC (lấy khoa phụ trách môn học), so sánh trực tiếp 2 mã khoa - Khác nhau nghĩa là vi phạm.
Đây là ràng buộc đơn giản nhất trong nhóm QGV - Chỉ cần so khớp đúng 2 cột MAKHOA từ 2 bảng khác nhau, không cần NOT EXISTS hay GROUP BY, nên chọn JOIN trực tiếp là gọn và dễ đọc nhất.
