CHƯƠNG 5 : THỦ TỤC LƯU TRỮ, HÀM VÀ TRIGGER
5.3.4 Sử dụng trigger trong trường hợp cõu lệnh INSERT,UPDATE và DELETE cú tỏc
DELETE cú tỏc động đến nhiều dũng dữ liệu
Trong cỏc vớ dụ trước, cỏc trigger chỉ thực sự hoạt động đỳng mục đớch khi cỏc cõu lệnh kớch hoạt trigger chỉ cú tỏc dụng đối với đỳng một dũng dữ liờu. Ta cú thể nhận thấy là cõu lệnh UPDATE và DELETE thường cú tỏc dụng trờn nhiều dũng, cõu lệnh INSERT mặc dự ớt rơi vào trường hợp này nhưng khụng phải là khụng gặp; đú là khi ta sử dụng cõu lệnh cú dạng INSERT INTO ... SELECT ... Vậy làm thế nào để trigger hoạt động đỳng trong trường hợp những cõu lệnh cú tỏc động lờn nhiều dũng dữ liệu?
Cú hai giải phỏp cú thể sử dụng đối với vấn đề này:
• Sử dụng truy vấn con.
• Sử dụng biến con trỏ.
5.3.4.1 Sử dụng truy vấn con
Ta hỡnh dung vấn đề này và cỏch khắc phục qua vớ dụ dưới đõy:
Vớ dụ 5.17: Ta xột lại trường hợp của hai bảng MATHANG và NHATKYBANHANG như sơđồ dưới đõy:
Trigger dưới đõy cập nhật lại số lượng hàng của bảng MATHANG khi cõu lệnh UPDATE được sử dụng để cập nhật cột SOLUONG của bảng NHATKYBANHANG.
CREATE TRIGGER trg_nhatkybanhang_update_soluong ON nhatkybanhang
FOR UPDATE AS
IF UPDATE(soluong) UPDATE mathang
SET mathang.soluong = mathang.soluong – (inserted.soluong-deleted.soluong) FROM (deleted INNER JOIN inserted ON
deleted.stt = inserted.stt) INNER JOIN mathang ON mathang.mahang = deleted.mahang
Với trigger được định nghĩa như trờn, nếu thực hiện cõu lệnh:
UPDATE nhatkybanhang
SET soluong = soluong + 10 WHERE stt = 1
thỡ dữ liệu trong hai bảng MATHANG và NHATKYBANHANG sẽ là:
Bảng MATHANG Bảng NHATKYBANHANG Tức là số lượng của mặt hàng cú mó H1 đó được giảm đi 10. Nhưng nếu thực hiện tiếp cõu lệnh: UPDATE nhatkybanhang SET soluong=soluong + 5 WHERE mahang='H2'
Bảng MATHANG Bảng NHATKYBANHANG
Ta cú thể nhận thấy số lượng của mặt hàng cú mó H2 cũn lại 40 (giảm đi 5) trong khi đỳng ra phải là 35 (tức là phải giảm 10). Như vậy, trigger ở trờn khụng hoạt động đỳng trong trường hợp này.
Để khắc phục lỗi gặp phải như trờn, ta định nghĩa lại trigger như sau:
CREATE TRIGGER trg_nhatkybanhang_update_soluong ON nhatkybanhang
FOR UPDATE AS
IF UPDATE(soluong) UPDATE mathang
SET mathang.soluong = mathang.soluong -
(SELECT SUM(inserted.soluong-deleted.soluong) FROM inserted INNER JOIN deleted
ON inserted.stt=deleted.stt WHERE inserted.mahang = mathang.mahang) WHERE mathang.mahang IN (SELECT mahang
FROM inserted)
hoặc:
CREATE TRIGGER trg_nhatkybanhang_update_soluong ON nhatkybanhang
FOR UPDATE AS
IF UPDATE(soluong)
/* Nếu số lượng dũng được cập nhật bằng 1 */ IF @@ROWCOUNT = 1
BEGIN
UPDATE mathang
SET mathang.soluong = mathang.soluong – (inserted.soluong-deleted.soluong) FROM (deleted INNER JOIN inserted ON
deleted.stt = inserted.stt) INNER JOIN mathang ON mathang.mahang = deleted.mahang
END ELSE BEGIN
UPDATE mathang
SET mathang.soluong = mathang.soluong -
(SELECT SUM(inserted.soluong-deleted.soluong) FROM inserted INNER JOIN deleted
ON inserted.stt=deleted.stt WHERE inserted.mahang = mathang.mahang) WHERE mathang.mahang IN (SELECT mahang
FROM inserted) END
5.3.4.2 Sử dụng biến con trỏ
Một cỏch khỏc để khắc phục lỗi xảy ra như trong vớ dụ 5.17 là sử dụng con trỏ để duyệt qua cỏc dũng dữ liệu và kiểm tra trờn từng dũng. Tuy nhiờn, sử dụng biến con trỏ trong trigger là giải phỏp nờn chọn trong trường hợp thực sự cần thiết.
Một biến con trỏđược sử dụng để duyệt qua cỏc dũng dữ liệu trong kết quả của một truy vấn và được khai bỏo theo cỳ phỏp như sau:
DECLARE tờn_con_trỏ CURSOR FOR cõu_lệnh_SELECT
Trong đú cõu lệnh SELECT phải cú kết quả dưới dạng bảng. Tức là trong cõu lệnh khụng sử dụng mệnh đề COMPUTE và INTO.
Để mở một biến con trỏ ta sử dụng cõu lệnh:
OPEN tờn_con_trỏ
Để sử dụng biến con trỏ duyệt qua cỏc dũng dữ liệu của truy vấn, ta sử dụng cõu lệnh FETCH. Giỏ trị của biến trạng thỏi @@FETCH_STATUS bằng khụng nếu chưa duyệt hết cỏc dũng trong kết quả truy vấn.
Cõu lệnh FETCH cú cỳ phỏp như sau:
FETCH [[NEXT|PRIOR|FIST|LAST] FROM] tờn_con_trỏ [INTO danh_sỏch_biến ]
Trong đú cỏc biến trong danh sỏch biến được sử dụng để chứa cỏc giỏ trị của cỏc trường ứng với dũng dữ liệu mà con trỏ trỏ đến. Số lượng cỏc biến phải bằng với số lượng cỏc cột của kết quả truy vấn trong cõu lệnh DECLARE CURSOR.
Vớ dụ 5.18: Tập cỏc cõu lệnh trong vớ dụ dưới đõy minh hoạ cỏch sử dụng biến con trỏ để duyệt qua cỏc dũng trong kết quả của cõu lệnh SELECT
DECLARE contro CURSOR
FOR SELECT mahang,tenhang,soluong FROM mathang OPEN contro
DECLARE @mahang NVARCHAR(10) DECLARE @tenhang NVARCHAR(10) DECLARE @soluong INT
/*Bắt đầu duyệt qua cỏc dũng trong kết quả truy vấn*/ FETCH NEXT FROM contro
INTO @mahang,@tenhang,@soluong WHILE @@FETCH_STATUS=0
BEGIN
PRINT 'Ma hang:'+@mahang PRINT 'Ten hang:'+@tenhang
PRINT 'So luong:'+STR(@soluong) FETCH NEXT FROM contro
INTO @mahang,@tenhang,@soluong END
/*Đúng con trỏ và giải phúng vựng nhớ*/ CLOSE contro
DEALLOCATE contro
Vớ dụ 5.19: Trigger dưới đõy là một cỏch giải quyết khỏc của trường hợp được đề cập ở vớ dụ 5.17
CREATE TRIGGER trg_nhatkybanhang_update_soluong ON nhatkybanhang
FOR UPDATE AS
IF UPDATE(soluong) BEGIN
DECLARE @mahang NVARCHAR(10) DECLARE @soluong INT
DECLARE contro CURSOR FOR SELECT inserted.mahang,
inserted.soluong-deleted.soluong AS soluong FROM inserted INNER JOIN deleted
ON inserted.stt=deleted.stt
FETCH NEXT FROM contro INTO @mahang,@soluong WHILE @@FETCH_STATUS=0
BEGIN
UPDATE mathang SET soluong=soluong-@soluong WHERE mahang=@mahang
FETCH NEXT FROM contro INTO @mahang,@soluong END CLOSE contro DEALLOCATE contro END END Bài tập chương 5
Dựa trờn cơ sở dữ liệu ở bài tập chương 2, thực hiện cỏc yờu cầu sau:
5.1 Tạo thủ tục lưu trữđể thụng qua thủ tục này cú thể bổ sung thờm một bản ghi mới cho bảng MATHANG (thủ tục phải thực hiện kiểm tra tớnh hợp lệ của dữ liệu cần bổ sung: khụng trựng khoỏ chớnh và đảm bảo toàn vẹn tham chiếu)
5.2 Tạo thủ tục lưu trữ cú chức năng thống kờ tổng số lượng hàng bỏn được của một mặt hàng cú mó bất kỳ (mó mặt hàng cần thống kờ là tham số của thủ tục).
5.3 Viết hàm trả về một bảng trong đú cho biết tổng số lượng hàng bỏn được của mỗi mặt hàng. Sử dụng hàm này để thống kờ xem tổng số lượng hàng (hiện cú và đó bỏn) của mỗi mặt hàng là bao nhiờu.
5.4 Viết trigger cho bảng CHITIETDATHANG theo yờu cầu sau:
• Khi một bản ghi mới được bổ sung vào bảng này thỡ giảm số lượng hàng hiện cú nếu số lượng hàng hiện cú lớn hơn hoặc bằng số lượng hàng được bỏn ra. Ngược lại thỡ huỷ bỏ thao tỏc bổ sung.
• Khi cập nhật lại số lượng hàng được bỏn, kiểm tra số lượng hàng được cập nhật lại cú phự hợp hay khụng (số lượng hàng bỏn ra khụng được vượt quỏ số lượng hàng hiện cú và khụng được nhỏ hơn 1). Nếu dữ liệu hợp lệ thỡ giảm (hoặc tăng) số lượng hàng hiện cú trong cụng ty, ngược lại thỡ huỷ bỏ thao tỏc cập nhật.
5.5 Viết trigger cho bảng CHITIETDATHANG để sao cho chỉ chấp nhận giỏ hàng bỏn ra phải nhỏ hơn hoặc bằng giỏ gốc (giỏ của mặt hàng trong bảng MATHANG)
5.6 Để quản lý cỏc bản tin trong một Website, người ta sử dụng hai bảng sau: Bảng LOAIBANTIN (loại bản tin)
CREATE TABLE loaibantin (
maphanloai INT NOT NULL PRIMARY KEY,
tenphanloai NVARCHAR(100) NOT NULL , bantinmoinhat INT DEFAULT(0) )
Bảng BANTIN (bản tin)
CREATE TABLE bantin (
maso INT NOT NULL PRIMARY KEY,
ngayduatin DATETIME NULL , tieude NVARCHAR(200) NULL , noidung NTEXT NULL ,
maphanloai INT NULL FOREIGN KEY
REFERENCES loaibantin(maphanloai) )
Trong bảng LOAIBANTIN, giỏ trị cột BANTINMOINHAT cho biết mó số của bản tin thuộc loại tương ứng mới nhất (được bổ sung sau cựng).
Hóy viết cỏc trigger cho bảng BANTIN sao cho:
• Khi một bản tin mới được bổ sung, cập nhật lại cột BANTINMOINHAT của dũng tương ứng với loại bản tin vừa bổ sung.
• Khi một bản tin bị xoỏ, cập nhật lại giỏ trị của cột BANTINMOINHAT trong bảng LOAIBANTIN của dũng ứng với loại bản tin vừa xúa là mó số của bản tin trước đú (dựa vào ngày đưa tin). Nếu khụng cũn bản tin nào cựng loại thỡ giỏ trị của cột này bằng 0.
• Khi cập nhật lại mó số của một bản tin và nếu đú là bản tin mới nhất thỡ cập nhật lại giỏ trị cột BANTINMOINHAT là mó số mới.
# Lời giải:
5.1 CREATE PROCEDURE sp_insert_mathang(
@tenhang NVARCHAR(50),
@macongty NVARCHAR(10) = NULL, @maloaihang INT = NULL,
@soluong INT = 0,
@donvitinh NVARCHAR(20) = NULL, @giahang money = 0)
AS
IF NOT EXISTS(SELECT mahang FROM mathang WHERE mahang=@mahang)
IF (@macongty IS NULL OR EXISTS(SELECT macongty FROM nhacungcap
WHERE macongty=@macongty)) AND
(@maloaihang IS NULL OR
EXISTS(SELECT maloaihang FROM loaihang WHERE maloaihang=@maloaihang))
INSERT INTO mathang
VALUES(@mahang,@tenhang,
@macongty,@maloaihang,
@soluong,@donvitinh,@giahang) 5.2 CREATE PROCEDURE sp_thongkebanhang(@mahang NVARCHAR(10)) AS
SELECT mathang.mahang,tenhang,
SUM(chitietdathang.soluong) AS tongsoluong FROM mathang LEFT OUTER JOIN chitietdathang
ON mathang.mahang=chitietdathang.mahang WHERE mathang.mahang=@mahang
GROUP BY mathang.mahang,tenhang
5.3 Định nghĩa hàm:
CREATE FUNCTION func_banhang() RETURNS TABLE
AS
RETURN (SELECT mathang.mahang,tenhang, CASE
WHEN sum(chitietdathang.soluong) IS NULL THEN 0 ELSE sum(chitietdathang.soluong)
END AS tongsl
FROM mathang LEFT OUTER JOIN chitietdathang ON mathang.mahang = chitietdathang.mahang
GROUP BY mathang.mahang,tenhang)
Sử dụng hàm đó định nghĩa:
SELECT a.mahang,a.tenhang,soluong+tongsl
FROM mathang AS a INNER JOIN dbo.func_banhang() AS b ON a.mahang=b.mahang
5.4 CREATE TRIGGER trg_chitietdathang_insert ON chitietdathang
FOR INSERT AS
BEGIN
DECLARE @mahang NVARCHAR(100) DECLARE @soluongban INT
DECLARE @soluongcon INT
SELECT @mahang=mahang,@soluongban=soluong FROM inserted
SELECT @soluongcon=soluong FROM mathang WHERE mahang=@mahang
IF @soluongcon>=@soluongban
UPDATE mathang SET soluong=soluong-@soluongban WHERE mahang=@mahang
ELSE
ROLLBACK TRANSACTION END
CREATE TRIGGER trg_chitietdathang_update_soluong ON chitietdathang
FOR UPDATE AS
IF UPDATE(soluong) BEGIN
IF EXISTS(SELECT sohoadon FROM inserted WHERE soluong<0) ROLLBACK TRANSACTION ELSE BEGIN UPDATE mathang SET soluong=soluong- (SELECT SUM(inserted.soluong-deleted.soluong) FROM inserted INNER JOIN deleted
inserted.mahang=deleted.mahang WHERE inserted.mahang=mathang.mahang GROUP BY inserted.mahang)
WHERE mahang IN (SELECT DISTINCT mahang FROM inserted)
IF EXISTS(SELECT mahang FROM mathang WHERE soluong<0)
ROLLBACK TRANSACTION END
END
5.5 CREATE TRIGGER trg_chitietdathang_giaban ON chitietdathang
FOR INSERT,UPDATE AS
IF UPDATE(giaban)
IF EXISTS(SELECT inserted.mahang
FROM mathang INNER JOIN inserted
ON mathang.mahang=inserted.mahang WHERE mathang.giahang>inserted.giaban) ROLLBACK TRANSACTION
C
Chhươươngng 66
G
GIIAAOO TTÁÁCC SSQQLL
6.1 Giao tỏc và cỏc tớnh chất của giao tỏc
Một giao tỏc (transaction) là một chuỗi một hoặc nhiều cõu lệnh SQL được kết hợp lại với nhau thành một khối cụng việc. Cỏc cõu lệnh SQL xuất hiện trong giao tỏc thường cú mối quan hệ tương đối mật thiết với nhau và thực hiện cỏc thao tỏc độc lập. Việc kết hợp cỏc cõu lệnh lại với nhau trong một giao tỏc nhằm đảm bảo tớnh toàn vẹn dữ liệu và khả năng phục hồi dữ liệu. Trong một giao tỏc, cỏc cõu lệnh cú thể độc lập với nhau nhưng tất cả cỏc cõu lệnh trong một giao tỏc đũi hỏi hoặc phải thực thi trọn vẹn hoặc khụng một cõu lệnh nào được thực thi.
Cỏc cơ sở dữ liệu sử dụng nhật ký giao tỏc (transaction log) để ghi lại cỏc thay đổi mà giao tỏc tạo ra trờn cơ sở dữ liệu và thụng qua đú cú thể phục hồi dữ liệu trong trường hợp gặp lỗi hay hệ thống cú sự cố.
Một giao tỏc đũi hỏi phải cú được bồn tớnh chất sau đõy:
• Tớnh nguyờn tử (Atomicity): Mọi thay đổi về mặt dữ liệu hoặc phải được thực hiện trọn vẹn khi giao tỏc thực hiện thành cụng hoặc khụng cú bất kỳ sự thay đổi nào về dữ liệu xảy ra nếu giao tỏc khụng thực hiện được trọn vẹn. Núi cỏch khỏc, tỏc dụng của cỏc cõu lệnh trong một giao tỏc phải như là một cõu lệnh đơn.
• Tớnh nhất quỏn (Consistency): Tớnh nhất quan đũi hỏi sau khi giao tỏc kết thỳc, cho dự là thành cụng hay bị lỗi, tất cả dữ liệu phải ở trạng thỏi nhất quỏn (tức là sự toàn vẹn dữ liệu phải luụn được bảo tồn).
• Tớnh độc lập (Isolation): Tớnh độc lập của giao tỏc cú nghĩa là tỏc dụng của mỗi một giao tỏc phải giống như khi chỉ mỡnh nú được thực hiện trờn chớnh hệ thống đú. Núi cỏch khỏc, một giao tỏc khi được thực thi đồng thời với những giao tỏc khỏc trờn cựng hệ thống khụng chịu bất kỳ sựảnh hưởng nào của cỏc giao tỏc đú.
• Tớnh bền vững (Durability): Sau khi một giao tỏc đó thực hiện thành cụng, mọi tỏc dụng mà nú đó tạo ra phải tồn tại bền vững trong cơ sở dữ liệu, cho dự là hệ thống cú bị lỗi đi chăng nữa.
SELECT INSERT UPDATE DELETE UPDATE SELECT INSERT UPDATE DELETE SELECT INSERT UPDATE Lỗi ch−ơng trình! Lỗi phần cứng! R O L L B A C K R O L L B A C K Trạng thái CSDL tr−ớc khi giao tác tiến hành Trạng thái CSDL sau khi giao tác tiến hành
Giao tác
Hỡnh 6.1: Giao tỏc SQL
6.2 Mụ hỡnh giao tỏc trong SQL
Giao tỏc SQL được định nghĩa dựa trờn cỏc cõu lệnh xử lý giao tỏc sau đõy:
• BEGIN TRANSACTION: Bắt đầu một giao tỏc
• SAVE TRANSACTION: Đỏnh dấu một vị trớ trong giao tỏc (gọi là điểm đỏnh dấu).
• ROLLBACK TRANSACTION: Quay lui trở lại đầu giao tỏc hoặc một điểm đỏnh dấu trước đú trong giao tỏc.
• COMMIT TRANSACTION: Đỏnh dấu điểm kết thỳc một giao tỏc. Khi cõu lệnh này thực thi cũng cú nghĩa là giao tỏc đó thực hiện thành cụng.
• ROLLBACK [WORK]: Quay lui trở lại đầu giao tỏc.
• COMMIT [WORK]: Đỏnh dấu kết thỳc giao tỏc.
Một giao tỏc trong SQL được bắt đấu bởi cõu lệnh BEGIN TRANSACTION. Cõu lệnh này đỏnh dấu điểm bắt đầu của một giao tỏc và cú cỳ phỏp như sau:
BEGIN TRANSACTION [tờn_giao_tỏc]
• Cõu lệnh COMMIT TRANSACTION (hoặc COMMIT WORK) được thực thi. Cõu lệnh này bỏo hiệu sự kết thỳc thành cụng của một giao tỏc. Sau cõu lệnh này, một giao tỏc mới sẽđược bắt đầu.
• Khi cõu lệnh ROLLBACK TRANSACTION (hoặc ROLLBACK WORK) được thực thi để huỷ bỏ một giao tỏc và đưa cơ sở dữ liệu về trạng thỏi như trước khi giao tỏc bắt đầu. Một giao tỏc mới sẽ bắt đầu sau khi cõu lệnh ROLLBACK được thực thi.
• Một giao tỏc cũng sẽ kết thỳc nếu trong quỏ trỡnh thực hiện gặp lỗi (chẩng hạn hệ thống gặp lỗi, kết nối mạng bị “đứt”,...). Trong trường hợp này, hệ thống sẽ tựđộng phục hồi lại trạng thỏi cơ sở dữ liệu như trước khi giao tỏc bắt đầu (tương tự như khi cõu lệnh ROLLBACK được thực thi để huỷ bỏ một giao tỏc). Tuy nhiờn, trong trường hợp này sẽ khụng cú giao tỏc mới được bắt đầu.
Vớ dụ 6.1: Giao tỏc dưới đõy kết thỳc do lệnh ROLLBACK TRANSACTION và mọi thay đổi vể mặt dữ liệu mà giao tỏc đó thực hiện (UPDATE) đều khụng cú tỏc dụng.
BEGIN TRANSACTION giaotac1
UPDATE monhoc SET sodvht=4 WHERE sodvht=3
UPDATE diemthi SET diemlan2=0 WHERE diemlan2 IS NULL ROLLBACK TRANSACTION giaotac1
cũn giao tỏc dưới đõy kết thỳc bởi lệnh COMMIT và thực hiện thành cụng việc cập nhật dữ liệu trờn cỏc bảng MONHOC và DIEMTHI.
BEGIN TRANSACTION giaotac2
UPDATE monhoc SET sodvht=4 WHERE sodvht=3
UPDATE diemthi SET diemlan2=0 WHERE diemlan2 IS NULL COMMIT TRANSACTION giaotac2
Cõu lệnh:
SAVE TRANSACTION tờn_điểm_dỏnh_dấu
được sử dụng để đỏnh dấu một vị trớ trong giao tỏc. Khi cõu lệnh này được thực thi, trạng thỏi của cơ sở dữ liệu tại thời điểm đú sẽ được ghi lại trong nhật ký giao tỏc. Trong quỏ trỡnh thực thi giao tỏc cú thể quay trở lại một điểm đỏnh dấu bằng cỏch sử dụng cõu lệnh:
ROLLBACK TRANSACTION tờn_điểm_đỏnh_dấu
Trong trường hợp này, những thay đổi về mặt dữ liệu mà giao tỏc đó thực hiện từđiểm đỏnh dấu đến trước khi cõu lệnh ROLLBACK được triệu gọi sẽ bị huỷ bỏ. Giao tỏc sẽ được tiếp tục với trạng thỏi cơ sở dữ liệu cú được tại điểm đỏnh dấu . Hỡnh 6.2 mụ tả cho ta thấy hoạt động của một giao tỏc cú sử dụng cỏc điểm đỏnh dấu:
BEGIN TRANSACTION trans_example INSERT Trạng thái CSDL tr−ớc khi giao tác tiến hành Trạng thái CSDL sau khi giao tác tiến hành UPDATE SAVE TRANSACTION a UPDATE SAVE TRANSACTION b INSERT UPDATE ROLLBACK TRANSACTION b UPDATE SELECT COMMIT TRANSACTION Trạng thái CSDL tại điểm đánh dấu a
Trạng thái CSDL tại điểm đánh dấu b Giao tác: Bắt đầu bởi lệnh BEGIN TRANSACTION và kết thúc bởi lệnh COMMIT TRANSACTION Hỡnh 6.2: Hoạt động của một giao tỏc
Sau khi cõu lệnh ROLLBACK TRANSACTION được sử dụng để quay lui lại một điểm đỏnh dấu trong giao tỏc, giao tỏc vẫn được tiếp tục với cỏc cõu lệnh sau đú. Nhưng nếu cõu lệnh này được sử dụng để quay lui lại đầu giao tỏc (tức là huỷ bỏ giao tỏc), giao tỏc sẽ kết thỳc và do đú cõu lệnh COMMIT TRANSACTION trong trường hợp này sẽ gặp lỗi.
Vớ dụ 6.2: Cõu lệnh COMMIT TRANSACTION trong giao tỏc dưới đõy kết thỳc thành cụng một giao tỏc
BEGIN TRANSACTION giaotac3
SAVE TRANSACTION a
UPDATE monhoc SET sodvht=4 WHERE sodvht=3 ROLLBACK TRANSACTION a
UPDATE monhoc SET sodvht=2 WHERE sodvht=3 COMMIT TRANSACTION giaotac3
và trong vớ dụ dưới đõy, cõu lệnh COMMIT TRANSACTION gặp lỗi:
BEGIN TRANSACTION giaotac4
UPDATE diemthi SET diemlan2=0 WHERE diemlan2 IS NULL SAVE TRANSACTION a
UPDATE monhoc SET sodvht=4 WHERE sodvht=3 ROLLBACK TRANSACTION giaotac4
UPDATE monhoc SET sodvht=2 WHERE sodvht=3 COMMIT TRANSACTION giaotac4