TÌM HIỂU VỀ CON TRỎ TRONG SQL SERVERI.Tìm hiểu chung 1.1 Giới thiệu về con trỏ Khái niệm Cursor - Là một cấu trúc dữ liệu ánh xạ đến một tập các dòng dữ liệu kết quả của một câu truy vấn
Trang 1TÌM HIỂU VỀ CON TRỎ TRONG SQL SERVER
I.Tìm hiểu chung
1.1 Giới thiệu về con trỏ
Khái niệm Cursor
- Là một cấu trúc dữ liệu ánh xạ đến một tập các dòng dữ liệu kết quả của một câu truy vấn (select)
- Cho phép duyệt tuần tự qua tập các dòng dữ liệu và đọc giá trị từng dòng
- Thể hiện của cursor là 1 biến, nhưng tên biến này không bắt đầu bằng
’@’
- Vị trí hiện hành của cursor có thể được dùng như điều kiện trong mệnh
đề where của lệnh update hoặc delete: cho phép cập nhật/xoá dữ liệu (dữ liệu thật sự trong CSDL) tương ứng với vị trí hiện hành của cursor
Một con trỏ là một đối tượng cơ sở dữ liệu được sử dụng bởi ứng dụng
để thao tác với các hàng dữ liệu thay vì các tập hợp dữ liệu Sử dụng con trỏ, nhiều tác vụ có thể được thực hiện theo từng hàng trên tập kết quả
mà có thể cần hoặc không cần sự có mặt của bảng gốc Hay nói một cách khác, con trỏ,về mặt khái niệm, trả về một tập hợp kết quả dựa trên các bảng bên trong cơ sở dữ liệu Với con trỏ chúng ta có thể :
Cho phép định vị các hàng chỉ định của tập kết quả
Nhận về một hàng đơn hoặc tập hợp các hàng từ vị trí hiện tại của tập kết quả
Hỗ trợsửa đổi dữ liệu của hàng ở vị trí hiện tại trong tập kết quả
Hỗ trợ nhiều cấp độ quan sát đối với các thay đổi được tạo ra bởi các người dùng khác trên các dữ liệu của tậpkếtquả
Trang 2 Cung cấp các lệnh Transact-SQLtrong các script, thủ tục lưu, và bẫy lỗi để truy nhập dữ liệu trong tập kết quả
1.2 Ý nghĩa
Truy xuất đến tập bản ghi và thực hiện các thao tác do người lập trình xử
lý một số yêu cầu
Xử dụng con trỏ để thực hiện trên từng bản ghi
II.Thao tác trên con trỏ
2.1 Cú pháp
Lệnh declare được sử dụng để tạo một con trỏ Nó chứa lệnh SELECT để
bao gồm các bản ghi từ bảng Có thể sử dụng cú pháp chuẩn SQL 92 hoặc
cú pháp T_SQL mở rộng
− Cú pháp SQL 92 chuẩn:
Declare cursor_name [Insensitive] [Scroll] Cursor
For select_statement
[ For {Read only| Update [of column_name [,…n] ] }]
− Cú pháp T_SQL mở rộng
Declare cursor_name Cursor
[ Local | Global ]
[ Forward_only| Scroll] [ Static| Dynamic]
[ Read_only]
For select_statement
[ For Update [ of column_name [,…n] ] ]
Các thuộc tính được giải nghĩa dưới đây:
Local
Trang 3Chỉ ra phạm vi hạn chế của con trỏ trong thủ tục hoặc bẫy lỗi Hay nói một cách khác, tên của con trỏ là hợp lệ bên trong phạm vi của nó Con trỏ hoàn toàn được giải phóng khi thủ tục lưu trữ hoặc bẫy lỗi bị hủy
bỏ cursor cục bộ, chỉ có thể sử dụng trong phạm vi một khối (query batch) hoặc một thủ tục/ hàm
GLobal
Chỉ ra rằng phạm vi của con trỏ là toàn cục Tên con trỏ có thể được tham chiếu trong bất cứ thủ tục lưu trữ nào Cursor toàn cục, có thể sử dụng trong một thủ tục/hàm hay một query batch bất kỳ hoặc đến khi bị hủy một cách tường minh
Forward_only
Chỉ ra rằng con trỏ chỉ có thể được duyệt từ hàng đầu tiên đến hàng cuối cùng, chế độ này chỉ hỗ trợ tuỳ chọn FETCH NEXT để lấy dữ liệu
Ngầm định con trỏ luôn ở chế độ Forward_only, cursor chỉ có thể duyệt
một chiều từ đầu đến cuối
Scroll
Chỉ ra rằng tất cả các tuỳ chọn để lấy dữ liệu (First,Last, Prior, next, Relative, Absolute) đều được hỗ trợ Nếu Scroll không được chỉ ra trong khai báo con trỏ, nó chỉ hỗ trợ tuỳ chọn NEXT, có thể duyệt lên xuống cursor tùy ý (duyệt theo đa chiều)
Static
Định nghĩa một con trỏ mà sẽ tạo ra môt bản sao của dữ liệu để sử dụng
Tất cả các yêu cầu gửi đến con trỏ được trả lời từ bảng tạm thời tempdb
Vì vậy ,việc sửa đổi dữ liệu trên các
bảng cơ sở không ảnh hưởng tới dữ liệu trả về bởi con trỏ ,và con trỏ này không cho phép sửa đổi
Trang 4Chỉ ra thứ tự của các hàng trong con trỏ là cố định khi con trỏ được mở
Dynamic
Định nghĩa một con trỏ mà ánh xạ toàn bộ thay đổi đối với các hàng trong tập kết quả mỗi khi con trỏ duyệt trong thời gian tồn tại, nội dung của cursor có thể thay đổi nếu dữ liệu trong các bảng liên quan có thay đổi
Fast_forward
Chỉ định con trỏ là FORWARD_ONLY và READ_ONLY
FAST_FORWARD không thể được xác định bởi tuỳ chọn SCROLL hoặc FOR_UPDATE Các con trỏ FORWARDONLY và
FAST_FORWARD là loại trừ lẫn nhau
Read_only
Nghiêm cấm việc cập nhật thông qua con trỏ này Nó không thể được tham chiếu trong mệnh đề WHERE CURRENT OF trong câu lệnh
UPDATE hoặc DELETE chỉ có thể đọc từ cursor, không thể sử dụng cursor để update dữ liệu trong các bảng liên quan (ngược lại với “for update…” )
Scroll_locks
Chỉ ra rằng việc cập nhật hoặc xoá các vị trí thông qua con trỏ được đảm bảo thành công SQL Server khoá các hàng mà chúng đang được đọc vào trong con trỏ để đảm bảo khả năng sẵn sàng của chúng cho việc sửa đổi sau này Tuỳ chọn SCROLL_LOCKS không thể được xác định cùng vớiFAST_FORWARD
Optimistic
Trang 5Chỉ ra rằng việc cập nhật hoặc xoá các vị trí thông qua con trỏ không thành công, nếu hàng được cập nhật khi nó đã được đọc vào con trỏ
Type_warning
Đưa ra một thông điệp cảnh báo gửi đến người dùng nếu con trỏ ngầm chuyển đổi từ một kiểu yêu cầu sang một kiểu khác
Update [OF Column_name[, n]]
Định nghĩa các cột có thể cập nhật trong con trỏ Nếu
OFColumn_name[,…n] được sử dụng, chỉ các cột nằm trong danh sách được phép sửa đổi
Trong số các thuộc tính nêu trên DYNAMIC, STATIC, KEYSET và FORWARD_ONLY định nghĩa các đặc trưng mang tính truy xuất dữ liệu của con trỏ và số còn lại định nghĩa các đặc trưng mang tính chất chức năng của con trỏ
Insensitive/ static: Nội dung của cursor không thay đổi trong suốt thời
gian tồn tại, trong trường hợp này cursor chỉ là read only
Lưu ý: Tên cursor trong các cách khai báo không bắt đầu bằng ký tự “@”
Mặc định khi khai báo cursor nếu không chỉ ra các tùy chọn thì cursor có các tính chất:
- Global
- Forward_only
- Read only hay “for update” tùy thuộc vào câu truy vấn
- Dynamic
Trang 62.2 Các bước tạo một con trỏ
Một con trỏ có thể tồn tại trong nhiều trạng thái Các trạng thái khác nhau của con trỏ liên quan đến các khu vực khác nhau nơi mà chúng được tạo
ra và thực hiện Một con trỏ đơn giản được thực thi theo các bước dưới đây:
Bước 1: Mở con trỏ :
open tên con trỏ
Bước 2: Nhận về các bản ghi :
Fetch tên con trỏ
Bước 3 : Đóng con trỏ
Close tên con trỏ
Bước 4 :Giải phóng con trỏ
Deallocate tên con trỏ
Sau khi con trỏ được tạo, nó phải được mở trước khi các bản ghi được
truy xuất từ nó Lệnh Open được sử dụng để mở một con trỏ.Cú pháp là:
OPEN <Cursor_name>
Mỗi khi con trỏ được mở, các bản ghi được truy xuất từ con trỏ để hiển
thị chúng trên màn hình Lệnh Fetch được sử dụng để hiển thị các bản
ghi từ con trỏ Cú pháp là:
Fetch <Cursor_name>
Một cách tuỳ ý, một con trỏ có thể được đóng tạm thời khi nó không cần
thiết sử dụng lệnh Close Lệnh này đóng con trỏ đang mở bằng cách
giải phóng tập kết quả hiện tại Mỗi khi con trỏ được đóng, các hàng chỉ
có thể được truy xuất sau khi mở lại nó Cú pháp là:
Close <Cursor_name>
Trang 7Khi con trỏ không cần thiết thêm nữa, tham chiếu đến nó được huỷ bỏ Lệnh
Deallocate sử dụng để giải phóng tham chiếu tới con trỏ Cú pháp là:
Deallocate<Cursor_name>
Mỗi khi con trỏ đựoc tạo và mở, các hàng được truy xuất từ con trỏ Chúng ta sẽ xem chi tiết về việc duyệt và nhận về dữ liệu ở chương tiếp
2.3 Truy xuất và duyệt con trỏ
Khi con trỏ được mở, hàng ở vị trí hiện tại của con trỏ về mặt logic ở trước hàng đầu tiên Các con trỏ Transact-SQL có thể truy xuất một hàng tại một thời điểm Các tác vụ để nhận về các hàng từ con trỏ được gọi là
fetching Có rất nhiều thao tác truy xuất:
FETCH FIRST:Truy xuất hàng đầu tiên
FETCH NEXT:Truy xuất hàng tiếp theo hàng truy xuất trước đó
FETCH PRIOR: Truy xuất hàng trước hàng truy xuất trước đó
FETCH LAST:Truy xuất hàng cuối cùng
FETCH ABSOLUTE n:Nếu n là một số nguyên dương, nó sẽ truy xuất n hàng trong con trỏ.
Nếu n là một số nguyên âm, n hàng trước hàng cuối cùng trong con trỏ được truy xuất Nếu n bằng 0 thì không hàng nào được truy xuất.
Vídụ, FETCH Absolute 2 sẽ hiển thị bản ghi thứ hai của một bảng
FETCH RELATIVE n:Truy xuất n hàng từ hàng truy xuất trước đó, nếu
n là số dương Nếu nlà sốâm, n hàng trước hàng truy xuất trước đó được truy xuất Nếu n bằng 0, hàng hiện tại được nhận về.
Trạng thái của mỗi lệnh truy xuất có thể được xác định bởi hai biến toàn cục
Trang 8@@FETCH _STATUS
Biếnnày trảvề một sốnguyênbiễudiễnkết quảcủalệnhtruy
xuấtcuốicùngcủacontrỏ
@@CURSOR_ROWS
Biến này trả về tổng số hàng hiện tại trong con trỏ đang mở
Hình11.8 Hiển thị một ví dụ về việc sử dụng con trỏ, và các hàng của nó được truy xuất cho đến khi biến bằng 0
Tạo con trỏ FORWARD_ONLY (Chỉtiến)
Chúng ta khai báo một con trỏ FORWARD_ONLY để thay đổi dữ liệu
của chính nó Con trỏ này bao gồm tất cả các bản ghi của bảng jobs mà
có giá trị của trường min_lvl= 75 Sau đó thay đổi giá trị cột max_lvl
của bản ghi đầu tiên= 100 Thực hiện đoạn lệnh sau ở trong Query
Analyzer :
Trang 9DECLARE JobsCursor CURSOR FORWARD_ONLY
FOR
SELECT * FROM jobs WHERE min_lvl = 75
FOR UPDATE
OPEN JobsCursor
FETCH JobsCursor
UPDATE jobs
SET max_lvl = 100 WHERE CURRENT OF JobsCursor WHERE CURRENT OF Cursor2
SELECT * FROM jobs WHERE min_lvl = 75
CLOSE JobsCursor
DEALLOCATE JobsCursor.( Kết quả được hiển thị ở hình 2 )
Hình 2 : Ví dụ về con trỏ FORWARD_ONLY
Trang 10Câu lệnh FETCH hiển thị nguyên bản bản ghi đầu tiên của con trỏ
Trường Max_lvl đã được thay đổi giá trị ở bản ghi đầu tiên, và sau đó
câu lệnh SELECT hiển thị giá trị đã thay đổi đó
Tạo contrỏREAD_ONLY(chỉđọc)
Bây giờ chúng ta tạo ra một con trỏ READ_ONLY, nó bao gồm các
hàng của bảng pub_info, các hàng này phải có giá trị của trường pub_id
nằm trong khoảng 1000 đến 2000 Sau đó chúng ta sẽ thử xóa một hàng của con trỏ này và xem kết quả của nó
Thực hiện đoạn lệnh sau:
DECLARE PubInfo Cursor CURSOR READ_ONLY
FOR
SELECT * FROM pub_info
WHERE pub_id between 1000 and 2000
OPEN PubInfo Cursor
FETCH PubInfo Cursor
DELETE FROM pub_info
WHERE CURRENT OF PubInfo Cursor
CLOSE PubInfo Cursor
DEALLOCATE PubInfo Cursor
Lệnh DELETE trả về một lỗi được hiển thị trong hình 3
Trang 11Hình 3:Ví dụ về con trỏ READ_ONLY
Chú ý :
Trước khi lập trình con trỏ ta phải khai báo con trỏ bằng câu lệnh : declare tên con trỏ cursor
For [câu lệnh select]
2.4 Biến cursor
Ta có thể khai báo một biến kiểu cursor và gán cho nó tham chiếu đến một cursor đang tồn tại
Biến cursor có thể được xem như là con trỏ cursor
Biến cursor là một biến cục bộ
Biến cursor sau khi gán giá trị được sử dụng như một cursor thông thường
Ví dụ :
Trang 12set @cur_var = my_cur
my_cur là một cursor đang tồn tại
hoặc:
Declare @cur_var cursor
set @cur_var = cursor for select_statement
Kết hợp cursor với stored procedure
Xây dựng SP tính điểm trung bình và xếp loại cho sinh viên thuộc lớp cho trước Giả sử có các quan hệ như sau:
SinhVien (MaSV, HoTen, DTB, XepLoai, Lop)
MonHoc (MaMH, TenMH)
KetQua (MaMH, MaSV, LanThi, Diem)
Biết rằng
Điểm thi chỉ tính lần thi sau cùng
Xếp loại: Xuất sắc [9, 10], Giỏi [8, 8.9], Khá [7, 7.9], Trung bình [5.0, 6.9], Yếu[0, 4.9]
Kết quả ghi xuống CSDL, đồng thời xuất ra tổng số sinh viên xếp loại giỏi của lớp đó
•Phân tích ví dụ:
Lớp cần xét có nhiều sinh viên, từng sinh viên cần được xử lý thông qua
3 bước:
Tính điểm trung bình cho sinh viên, điểm trung bình phải là điểm của lần thi sau cùng Có thể tái sử dụng thủ tục XepLoaiSVLop
Dựa vào điểm trung bình của sinh viên để xác định xếp loại
Cập nhật điểm và xếp loại vào bảng sinh viên
Trang 13Mọi sinh viên đều lặp lại 3 bước trên Từ phân tích trên ta thấy:
Cần xử lý nhiều phần tử (các sinh viên)
Mỗi phần tử xử lý tương đối phức tạp (truy vấn, tính toán, gọi thủ tục khác, điều kiện rẽ nhánh, cập nhật dữ liệu, …)
Cách xử lý các phần tử là như nhau
⇒ Sử dụng cursor là thích hợp
Cursor chứa các sinh viên của lớp cần xét, chỉ cần chứa mã sinh viên là được
• Xây dựng thủ tục
Create procedure XepLoaiSVLop
@Lop nvarchar(10), @SoSVGioi int out
As
Declare @DTB float
Declare @XepLoai nvarchar(20) Declare @MaSV nvarchar(10) Declare cur_SV cursor
For (select MaSV from SinhVien where Lop=@Lop) Open cur_SV Fetch Next from cur_SV into @MaSV While @@FETCH_STATUS = 0 Begin
End
Exec XepLoaiSV @MaSV, @DTB output, @XepLoai output Update SinhVien set DTB = @DTB, XepLoai=@XepLoai
Where MaSV= @MaSV
Fetch Next from cur_SV into @MaSV
Close cur_SV Deallocate cur_SV
Trang 14Set @SoSVGioi = (select count(*) from sinhvien
where lop = @Lop and XepLoai = N’Giỏi’)
Go
Từ những kiến thức chung về cursor áp dụng thực hiện một số bài toán
2.5 Ví dụ minh họa
Ví dụ 1:
Tạo ra con trỏ có tên là cur_sv chứa tập bản ghi về thông tin sinh viên Hãy duyệt tất cả các bản ghi về thông tin sinh viên đó và hiển thị kết quả
ra màn hình
Declare cur_sv cursor
For (select * from sinhvien)
Mở con trỏ
Open cur_sv
Đặt giá trị khởi đầu cho con trỏ
Fetch first from cur_sv
-Duyệt tất cả các bản ghi của con trỏ
While (@@Fetch_status=0)
-Duyệt bản ghi tiếp theo
Fetch next from Cur_sv
-Đóng con trỏ
Close cur_sv
Giải phóng con trỏ
Deallocate cur_sv
Trang 15Ví dụ 2 :
Tạo hai biến Masv và hoten , hiển thị thông tin của sinh viên thông qua hai biến của masv và hoten đó (coi như đã có bảng sinh viên)
Declare @masv varchar (10)
Declare @hoten nvarchar (50)
Declare @cur_sv cursor
For (select masv, hoten from sinhvien )
Open cur_sv
Fetch next from cur_sv into @masv, @hoten
While @@ Fetch_status =0
Fetch next from cur_sv into @masv, @hoten
Close cur_sv
Deallocate cus_sv
Ví dụ 3
Thêm cột xếp loại rồi cập nhật giá trị cho bảng xếp loại với các thông số tương ứng là: gioi>=8, 7<= kha <8, 5 < = trung bình <7, yếu < 5
Thêm cột xếp loại
alter table ketqua
add xeploai varchar(20)
Thực hiện yêu cầu bài toán
declare @masv varchar(10)
declare @madt varchar(10)
declare @xeploai varchar(20)
Trang 16Tạo con trỏ
declare cur_sv cursor
for select masv, madt,diem from ketqua
Mở con trỏ
open cur_sv
Duyệt bản ghi đầu tiên
fetch next from cur_sv into @masv, @madt, @diem Kiểm tra điều kiện xem có bản ghi hay không ? while(@@fetch_status=0)
begin
Câ lệnh rẽ nhánh
if @diem <5
set
@xeploai='yeu'
if @diem>=5 and @diem<7
set
@xeploai ='Trung binh'
if @diem>=7 and @diem<8
set
@xeploai='Kha'
if @diem>= 8
set
@xeploai='gioi'
Trang 17Thực hiện công việc yêu cầu của bài toán
update ketqua
set
xeploai=@xeploai
where masv=@masv and madt=@madt
Duyệt bản ghi tiếp theo
fetch next from cur_sv into @masv, @madt, @diem
end
Ví dụ 4
Bổ sung thêm cột số lượng vào bảng đề tài , viết thủ tục lưu trữ để cập nhật cột số lượng của đề tài bằng số sinh viên tham gia đề tài đó ( Kết hợp thủ tục vào con trỏ )
Thêm cột số lượng vào bảng đề tài
alter table detai
add soluong int
Tạo ra bảng ảo chứa số lượng sinh viên của mỗi đề tài
declare @madt varchar(10)
declare @soluong int
declare cur_sosv cursor
for select madt,count(masv) from sinhvien_detai group by madt
-Mở con trỏ
OPEN cur_sosv
Duyệt bản ghi đầu tiên
Trang 18fetch next from cur_sosv into @madt,@soluong
Kiểm tra điều kiện có bản ghi không ?
while(@@fetch_status =0)
begin
update detai
set
soluong=@soluong
where madt=@madt
Trường hợp nếu có đề tài mà chưa có sinh viên nào thực hiện update detai
set
soluong=0
where madt not in (select madt from sinhvien_detai)
Duyệt các bản ghi tiếp theo
fetch next from cur_sosv into @madt,@soluong
end
Đóng con trot
close cur_sosv
Giải phóng con trỏ
deallocate cur_sosv