在游標(biāo)的聲明中有一點值得注意的是,如同其它變量的聲明一樣,聲明游標(biāo)的這一段代碼行是不執(zhí)行的,您不能將debug時的斷點設(shè)在這一代碼行上,也不能用IF...END IF語句來聲明兩個同名的游標(biāo),如下列的代碼就是錯誤的。
IF Is_prov="北京"THEN
DECLARE CustomerCursor CURSOR FOR
SELECT acct_no,name,balance
FROM customer
WHERE province="北京";
ELSE
DECLARE CustomerCursor CURSOR FOR
SELECT acct_no,name,balance
FROM customer
WHERE province〈〉"北京";
END IF
由于打開游標(biāo)是對數(shù)據(jù)庫進行一些SQL SELECT的操作,它將耗費一段時間,主要取決于您使用的系統(tǒng)性能和這條語句的復(fù)雜程度。如果執(zhí)行的時間較長,可以考慮將屏幕上顯示的鼠標(biāo)改為hourglass(這句不會)
從語法上講,上面所述的就是一條合法的取數(shù)據(jù)的語句,但是一般我們使用游標(biāo)卻還應(yīng)當(dāng)包括其它的部分。正如我們前面所談到的,游標(biāo)只能一次從后臺數(shù)據(jù)庫中取一條記錄,而在多數(shù)情況下,我們所想要作的是在數(shù)據(jù)庫中從第一條記錄開始提取,一直到結(jié)束。所以我們一般要將游標(biāo)提取數(shù)據(jù)的語句放在一個循環(huán)體內(nèi),直至將結(jié)果集中的全部數(shù)據(jù)提取后,跳出循環(huán)圈。通過檢測SQLCA.SQL-CODE的值,可以得知最后一條FETCH語句是否成功。一般,當(dāng)SQLCODE值為0時表明一切正常,100表示已經(jīng)取到了結(jié)果集的末尾,而其它值均表明操作出了問題,
顯示游標(biāo)
顯示游標(biāo)包括四種屬性:
%ISOPEN :檢測游標(biāo)是否已經(jīng)打開
%FOUND :檢測游標(biāo)結(jié)果集是否存在數(shù)據(jù),存在則返回TRUE
%NOTFOUND :檢測游標(biāo)結(jié)果集是否不存在數(shù)據(jù),不存在則返回TRUE
%ROWCOUNT :返回已提取的實際行數(shù)
使用顯示游標(biāo)
定義游標(biāo):CURSOR cursor_name IS select_statement;
打開游標(biāo):OPEN cursor_name;
提取數(shù)據(jù):FETCH cursor_name INTO variable1[,variable2,…];
FETCH INTO每次只能提取一行數(shù)據(jù),批量數(shù)據(jù)需使用循環(huán)
使用游標(biāo)變量接受數(shù)據(jù):
SQL> declare
2 cursor emp_cursor is
3 select ename,job,sal from emp where deptno=&dno;
4 vname emp.ename%type;
5 vsal emp.sal%type;
6 vjob emp.job%type;
7 begin Oracle 學(xué)習(xí)筆記 第 74 頁 共 90 頁
8 open emp_cursor;
9 loop
10 fetch emp_cursor into vname,vjob,vsal;
11 exit when emp_cursor%notfound;
12 dbms_output.put_line('姓名:'||vname||',崗位:'||vjob||',工資:'||vsal);
13 end loop;
14 close emp_cursor;
15 end;
16 /
輸入 dno 的值: 30
姓名:ALLEN,崗位:SALESMAN,工資:1600
姓名:WARD,崗位:SALESMAN,工資:1250
姓名:JONES,崗位:MANAGER,工資:3272.5
PL/SQL 過程已成功完成。
使用PL/SQL記錄變量接受游標(biāo)數(shù)據(jù):簡化單行數(shù)據(jù)處理
SQL> declare
2 cursor ecur is select ename,sal from emp order by sal desc;
3 erec ecur%rowtype;
4 begin
5 open ecur;
6 loop
7 fetch ecur into erec;
8 exit when ecur%notfound or ecur%rowcount>&n;
9 dbms_output.put_line('姓名:'||erec.ename||',工資:'||erec.sal);
10 end loop;
11 close ecur;
12 end;
13 /
輸入 n 的值: 5
姓名:KING,工資:5000
姓名:FORD,工資:3300
姓名:JONES,工資:3272.5
姓名:BLAKE,工資:2850
姓名:MARY,工資:2000
PL/SQL 過程已成功完成。
使用PL/SQL集合變量接受游標(biāo)數(shù)據(jù):簡化多行多列數(shù)據(jù)處理
SQL> declare
2 cursor ec is select ename,sal from emp where lower(job)=lower('&job');
3 type etype is table of ec%rowtype index by binary_integer;
4 et etype;
5 i int;
6 begin
7 open ec;
8 loop Oracle 學(xué)習(xí)筆記 第 75 頁 共 90 頁
9 i:=ec%rowcount+1;
10 fetch ec into et(i);
11 exit when ec%notfound;
12 dbms_output.put_line('姓名:'||et(i).ename||',工資:'||et(i).sal);
13 end loop;
14 close ec;
15 end;
16 /
輸入 job 的值: manager
姓名:JONES,工資:3272.5
姓名:BLAKE,工資:2850
姓名:CLARK,工資:1500
PL/SQL 過程已成功完成。
游標(biāo)FOR循環(huán)
使用游標(biāo)for循環(huán)時,oracle會隱含的打開游標(biāo),提取數(shù)據(jù)并關(guān)閉游標(biāo)
在游標(biāo)for循環(huán)中引用已定義游標(biāo):
SQL> declare
2 cursor ec is select ename,hiredate from emp order by hiredate desc;
3 begin
4 for erec in ec loop
5 dbms_output.put_line('姓名:'||erec.ename||',工作日期:'||erec.hiredate);
6 exit when ec%rowcount=&n;
7 end loop;
8 end;
9 /
輸入 n 的值: 3
姓名:MARY,工作日期:
姓名:ADAMS,工作日期:23-5月 -87
姓名:SCOTT,工作日期:01-1月 -84
PL/SQL 過程已成功完成。
在游標(biāo)for循環(huán)中直接引用子查詢:
SQL> begin
2 for erec in (select ename,hiredate,rownum from emp order by hiredate) loop
3 dbms_output.put_line('姓名:'||erec.ename||',工作日期:'||erec.hiredate);
4 exit when erec.rownum=&n;end loop;
5 end;
6 /
輸入 n 的值: 2
姓名:ALLEN,工作日期:20-2月 -81
姓名:WARD,工作日期:22-2月 -81
PL/SQL 過程已成功完成。
參數(shù)游標(biāo):參數(shù)只能指定數(shù)據(jù)類型,不能指定長度,而且必須在where子句中引用參數(shù)
SQL> declare Oracle 學(xué)習(xí)筆記 第 76 頁 共 90 頁
2 cursor ec(dno number) is select ename,job from emp where deptno=dno;
3 begin
4 for erec in ec(&dno) loop
5 dbms_output.put_line('姓名:'||erec.ename||',崗位:'||erec.job);
6 end loop;
7 end;
8 /
輸入 dno 的值: 30
姓名:ALLEN,崗位:SALESMAN
姓名:WARD,崗位:SALESMAN
姓名:JONES,崗位:MANAGER
PL/SQL 過程已成功完成。
更新游標(biāo)行
declare
cursor emp_cursor is select ename,sal,deptno from emp for update;
dno int:=&no;
begin
for emp_record in emp_cursor loop
if emp_record.deptno=dno then
dbms_output.put_line(‘姓名:’||emp_record.ename||’,原工資:’||emp_record.sal);
update emp set sal=sal*1.1 where current of emp_cursor;
end if;
end loop;
end;
/
刪除游標(biāo)行
declare
cursor emp_cursor is select ename from emp for update;
name varchar2(10):=lower(‘&name’);
begin
for emp_record in emp_cursor loop
if lower(emp_record.ename)=name then
delete from emp where current of emp_cursor;
else
dbms_output.put_line(‘姓名:’||emp_record.ename);
end if;
end loop;
end;
/
使用for子句在特定表上加共享鎖(涉及多張表時的同步問題)
SQL> declare
2 cursor emp_cursor is Oracle 學(xué)習(xí)筆記第 77 頁 共 90 頁
select a.dname,b.ename from dept a JOIN emp b ON a.deprno=b.deptno;
3 name varchar2(10):=lower('&name');
4 begin
5 for emp_record in emp_cursor loop
6 if lower(emp_record.dname)=name then
7 dbms_output.put_line(‘姓名:’||emp_record.ename);
8 delete from emp where current of emp_cursor;
9 end if;
10 end loop;
11 end;
12 /
輸入 name 的值: sales
PL/SQL過程已完成。
游標(biāo)變量
游標(biāo)變量是基于REF CURSOR類型所定義的變量,它實際上是指向內(nèi)存地址的指針。使用顯式游標(biāo)只能定義靜態(tài)游標(biāo),而通過使用游標(biāo)變量可以在打開游標(biāo)時指定其對應(yīng)的select語句,從而實現(xiàn)動態(tài)游標(biāo)。
使用無返回類型的游標(biāo)變量
SQL> set serveroutput on
SQL> set verify off
SQL> declare
2 type ref_cursor_type is ref cursor;
3 rc ref_cursor_type;
4 v1 number(6);
5 v2 varchar2(10);
6 begin
7 open rc for
8 select &col1 col1,&col2 col2 from &table where &cond;
9 loop
10 fetch rc into v1,v2;
11 exit when rc%notfound;
12 dbms_output.put_line('col1= '||v1||',col2= '||v2);
13 end loop;
14 close rc;
15 end;
16 /
輸入 col1 的值: empno
輸入 col2 的值: ename
輸入 table 的值: emp
輸入 cond 的值: deptno=10
col1= 7782,col2= CLARK
col1= 7839,col2= KING
col1= 7934,col2= MILLER
PL/SQL 過程已成功完成。 Oracle 學(xué)習(xí)筆記 第 78 頁 共 90 頁
使用有返回類型的游標(biāo)變量
SQL> declare
2 type emp_cursor_type is ref cursor return emp%rowtype;
3 ec emp_cursor_type;
4 er emp%rowtype;
5 begin
6 open ec for select * from emp where deptno=&dno;
7 loop
8 fetch ec into er;
9 exit when ec%notfound;
10 dbms_output.put_line('姓名:'||er.ename||',工資:'||er.sal);
11 end loop;
12 close ec;
13 end;
14 /
輸入 dno 的值: 20
姓名:SMITH,工資:800
姓名:JONES,工資:2975
姓名:SCOTT,工資:3000
姓名:ADAMS,工資:1100
姓名:FORD,工資:3000
PL/SQL 過程已成功完成。
使用批量提取
使用fetch…bulk collect提取所有數(shù)據(jù);
SQL> declare
2 cursor ec is
3 select * from emp where lower(job)=lower('&job');
4 type etype is table of emp%rowtype;
5 et etype;
6 begin
7 open ec;
8 fetch ec bulk collect into et;
9 close ec;
10 for i in 1..et.count loop
11 dbms_output.put_line('姓名:'||et(i).ename||',工資:'||et(i).sal);
12 end loop;
13 end;
14 /
輸入 job 的值: clerk
姓名:SMITH,工資:800
姓名:ADAMS,工資:1100
姓名:JAMES,工資:950
姓名:MILLER,工資:1300
PL/SQL 過程已成功完成。 Oracle 學(xué)習(xí)筆記 第 79 頁 共 90 頁
使用LIMIT子句限制提取行數(shù)
SQL> declare
2 cursor ec is select * from emp;
3 type emp_array_type is varray(5) of emp%rowtype;
4 ea emp_array_type;
5 begin
6 open ec;
7 loop
8 fetch ec bulk collect into ea limit &rows;
9 for i in 1..ea.count loop
10 dbms_output.put_line('姓名:'||ea(i).ename||',工資:'||ea(i).sal);
11 end loop;
12 exit when ec%notfound;
13 end loop;
14 close ec;
15 end;
16 /
輸入 rows 的值: 4
姓名:SMITH,工資:800
姓名:ALLEN,工資:1600
姓名:WARD,工資:1250
姓名:JONES,工資:2975
姓名:MARTIN,工資:1250
姓名:BLAKE,工資:2850
姓名:CLARK,工資:2450
姓名:SCOTT,工資:3000
姓名:KING,工資:5000
姓名:TURNER,工資:1500
姓名:ADAMS,工資:1100
姓名:JAMES,工資:950
姓名:FORD,工資:3000
姓名:MILLER,工資:1300
PL/SQL 過程已成功完成。
使用cursor表達式
SQL> declare
2 cursor dept_cursor(no number) is
3 select a.dname,cursor(select * from emp where deptno=a.deptno)
4 from dept a where a.deptno=no;
5 type ref_cursor_type is ref cursor;
6 ec ref_cursor_type;
7 er emp%rowtype;
8 vdname dept.dname%type;
9 begin
10 open dept_cursor(&dno); Oracle 學(xué)習(xí)筆記第 80 頁 共 90 頁
11 loop
12 fetch dept_cursor into vdname,ec;
13 exit when dept_cursor%notfound;
14 dbms_output.put_line('部門名:'||vdname);
15 loop
16 fetch ec into er;
17 exit when ec%notfound;
18 dbms_output.put_line('----雇員名:'||er.ename||',崗位:'||er.job);
19 end loop;
20 end loop;
21 close dept_cursor;
22 end;
23 /
輸入 dno 的值: 10
部門名:ACCOUNTING
----雇員名:CLARK,崗位:MANAGER
----雇員名:KING,崗位:PRESIDENT
----雇員名:MILLER,崗位:CLERK
PL/SQL 過程已成功完成。
在存儲過程或觸發(fā)器中使用 Transact-SQL 游標(biāo)的典型過程為:
- 聲明 Transact-SQL 變量包含游標(biāo)返回的數(shù)據(jù)。為每個結(jié)果集列聲明一個變量。聲明足夠大的變量來保存列返回的值,并聲明變量的類型為可從列數(shù)據(jù)類型隱式轉(zhuǎn)換得到的數(shù)據(jù)類型。
- 使用 DECLARE CURSOR 語句將 Transact-SQL 游標(biāo)與 SELECT 語句相關(guān)聯(lián)。另外,DECLARE CURSOR 語句還定義游標(biāo)的特性,例如游標(biāo)名稱以及游標(biāo)是只讀還是只進。
- 使用 OPEN 語句執(zhí)行 SELECT 語句并填充游標(biāo)。
- 使用 FETCH INTO 語句提取單個行,并將每列中的數(shù)據(jù)移至指定的變量中。然后,其他 Transact-SQL 語句可以引用那些變量來訪問提取的數(shù)據(jù)值。Transact-SQL 游標(biāo)不支持提取行塊。
使用 CLOSE 語句結(jié)束游標(biāo)的使用。關(guān)閉游標(biāo)可以釋放某些資源,例如游標(biāo)結(jié)果集及其對當(dāng)前行的鎖定,但如果重新發(fā)出一個 OPEN 語句,則該游標(biāo)結(jié)構(gòu)仍可用于處理。由于游標(biāo)仍然存在,此時還不能重新使用該游標(biāo)的名稱。DEALLOCATE 語句則完全釋放分配給游標(biāo)的資源,包括游標(biāo)名稱。釋放游標(biāo)后,必須使用 DECLARE 語句來重新生成游標(biāo)。
-----------------------------------------------------
/* Use DECLARE @local_variable, DECLARE CURSOR and SET. */
USE AdventureWorks
GO
DECLARE @MyVariable CURSOR
DECLARE MyCursor CURSOR FOR
SELECT LastName FROM AdventureWorks.Person.Contact
SET @MyVariable = MyCursor
GO
/* Use DECLARE @local_variable and SET */
DECLARE @MyVariable CURSOR
SET @MyVariable = CURSOR SCROLL KEYSET FOR
SELECT LastName FROM AdventureWorks.Person.Contact;
DEALLOCATE MyCursor;
-----------------------------------------------------
USE AdventureWorks
GO
-- Declare the variables to store the values returned by FETCH.
DECLARE @LastName varchar(50), @FirstName varchar(50)
DECLARE contact_cursor CURSOR FOR
SELECT LastName, FirstName FROM Person.Contact
WHERE LastName LIKE 'B%'
ORDER BY LastName, FirstName
OPEN contact_cursor
-- Perform the first fetch and store the values in variables.
-- Note: The variables are in the same order as the columns
-- in the SELECT statement.
FETCH NEXT FROM contact_cursor
INTO @LastName, @FirstName
-- Check @@FETCH_STATUS to see if there are any more rows to fetch.
WHILE @@FETCH_STATUS = 0
BEGIN
-- Concatenate and display the current values in the variables.
PRINT 'Contact Name: ' + @FirstName + ' ' + @LastName
-- This is executed as long as the previous fetch succeeds.
FETCH NEXT FROM contact_cursor
INTO @LastName, @FirstName
END
CLOSE contact_cursor
DEALLOCATE contact_cursor
GO
一個游標(biāo)(cursor)可以被看作指向結(jié)果集(a set of rows)中一行的指針(pointer)。游標(biāo)每個時間點只能指向一行,但是可以根據(jù)需要指向結(jié)果集中其他的行。
例如:SELECT * FROM employees WHERE sex='M'會返回所有性別為男的雇員,在初始的時候,游標(biāo)被放置在結(jié)果集中第一行的前面。使游標(biāo)指向第一行,要執(zhí)行FETCH。當(dāng)游標(biāo)指向結(jié)果集中一行的時候,可以對這行數(shù)據(jù)進行加工處理,要想得到下一行數(shù)據(jù),要繼續(xù)執(zhí)行FETCH。FETCH操作可以重復(fù)執(zhí)行,直到完成結(jié)果集中的所有行
在存儲過程中使用游標(biāo),有如下幾個步驟:
聲明游標(biāo)、打開游標(biāo)、根據(jù)需要一次一行,講游標(biāo)指向的數(shù)據(jù)取到本地變量(local variables)中、結(jié)束時關(guān)閉游標(biāo)
聲明游標(biāo):
>>-DECLARE--cursor-name--CURSOR----+------------+--------->
'-WITH HOLD--'
>-----+--------------------------------+--------------------->
| .-TO CALLER--. |
'-WITH RETURN--+------------+--'
'-TO CLIENT--'
>----FOR--+-select-statement-+----------------------------><
'-statement-name---'
WITH RETURN子句用于將游標(biāo)所定義的結(jié)果集傳遞給另一個存儲過程或者應(yīng)用(an application)
如果select語句中包含CURRENT DATE, CURRENT TIME和CURRENT TIMESTAMP,所有的FETCH語句都會返回相同的日期、時間、時間戳值,因為這些特定寄存器是在打開游標(biāo)(OPEN CURSOR)的時候進行檢查的
FETCH語法:
>>-FETCH--+-------+---cursor-name---------->
'-FROM--'
.-,----------------.
V |
>------INTO-----host-variable---+----------><
FETCH語句使游標(biāo)指向結(jié)果集中的下一行,并且將游標(biāo)現(xiàn)在的位置賦值給特定的過程變量
例如:一個公司,按照如下規(guī)則計算加薪金額:
1.公司中除了總裁(president)外,所有人都會至少增加p_min的薪水
2.任何獎金(bonus)高于$600的員工都會另增加4%
3.員工的傭金(commission)越高,增加越少。傭金(commission)少于$2000的另增加3%,傭金(commission)在$2000到$3000的增加另2%
4.傭金(commission)高于$3000的另增加1%
5.無論每個員工增加多少,增加比例不能高于p_max
CREATE PROCEDURE total_raise ( IN p_min DEC(4,2)
, IN p_max DEC(4,2)
, OUT p_total DEC(9,2) )
LANGUAGE SQL
SPECIFIC total_raise
tr: BEGIN
-- Declare variables
DECLARE v_salary DEC(9,2);
DECLARE v_bonus DEC(9,2);
DECLARE v_comm DEC(9,2);
DECLARE v_raise DEC(4,2);
DECLARE v_job VARCHAR(15) DEFAULT 'PRES';
-- Declare returncode
DECLARE SQLSTATE CHAR(5);
-- Procedure logic
DECLARE c_emp CURSOR FOR
SELECT salary, bonus, comm
FROM employee
WHERE job != v_job; -- (1)這里的SELECT定義了結(jié)果集中的行和
列
OPEN c_emp; -- (2)
SET p_total = 0;
FETCH FROM c_emp INTO v_salary, v_bonus, v_comm; -- (3)得到一行數(shù)據(jù),并將其
復(fù)制給本地變量
WHILE ( SQLSTATE = '00000' ) DO --SQLSTATE 00000: 操作執(zhí)行成功,
并且未產(chǎn)生任何類型的警告或異常情
況。通過這個可以檢查是否到達最后一行
SET v_raise = p_min;
IF ( v_bonus >= 600 ) THEN
SET v_raise = v_raise + 0.04;
END IF;
IF ( v_comm < 2000 ) THEN
SET v_raise = v_raise + 0.03;
ELSEIF ( v_comm < 3000 ) THEN
SET v_raise = v_raise + 0.02;
ELSE
SET v_raise = v_raise + 0.01;
END IF;
IF ( v_raise > p_max ) THEN
SET v_raise = p_max;
END IF;
SET p_total = p_total + v_salary * v_raise;
FETCH FROM c_emp INTO v_salary, v_bonus, v_comm; -- (4)在WHILE邏輯中得到
更多的行數(shù)據(jù)
END WHILE;
CLOSE c_emp; -- (5)
END tr
如果只是想把結(jié)果集中的第一個值復(fù)制給本地變量,而聲明一個游標(biāo)是不恰當(dāng)?shù)?,因為打開游標(biāo)會耗費很多資源。所以如下這段代碼:
DECLARE c_tmp CURSOR FOR
SELECT c1
FROM t1;
OPEN c_emp;
FETCH FROM c_emp INTO v_c1;
CLOSE c_emp;
應(yīng)當(dāng)用有FETCH FIRST 1 ROW ONLY的子句的SQL語句:SELECT c1 INTO v_c1 FROM t1 FETCH FIRST 1 ROW ONLY;
positioned delete:利用游標(biāo)刪除當(dāng)前行
一個用于刪除的游標(biāo)(a deletable cursor)應(yīng)該符合以下的要求:
1.每個outer fullselect中的FROM子句只跟一個表有關(guān)
2.outer fullselect不包含VALUES, GROUP BY, 或者HAVING子句,并且不包括列函數(shù)
3.outer fullselect的select列表中不包含DISTINCT
4.select語句不包含ORDER BY或FOR READ ONLY子句
5.游標(biāo)是靜態(tài)定義的,或者明確了FOR UPDATE子句
>>-DELETE FROM-|----table-name---------|--------------->
+-----view-name---------+
>----WHERE CURRENT OF--cursor-name--------------------><
例如:在emp_act表中,如果記錄的時間比輸入?yún)?shù)p_date早的話,就將該記錄刪除,并返回刪除記錄總數(shù)
CREATE PROCEDURE cleanup_act ( IN p_date DATE
, OUT p_deleted INT )
LANGUAGE SQL
SPECIFIC cleanup_act
ca: BEGIN
-- Declare variable
DECLARE v_date DATE;
-- Declare returncode
DECLARE SQLSTATE CHAR(5);
-- Procedure logic
DECLARE c_emp CURSOR FOR -- (1)和上面那種read-only cursor語法
類似,只是多了FOR UPDATE
SELECT emendate
FROM emp_act
FOR UPDATE;
OPEN c_emp;
FETCH FROM c_emp INTO v_date; --注意此處,不要落了
SET p_deleted = 0;
WHILE ( SQLSTATE = '00000' ) DO
IF ( v_date < p_date ) THEN
DELETE FROM emp_act
WHERE CURRENT OF c_emp; -- (2)
SET p_deleted = p_deleted + 1;
END IF;
FETCH FROM c_emp INTO v_date;
END WHILE;
CLOSE c_emp;
END ca
直接用DELETE語句刪除而不用游標(biāo)被稱作searched delete。像上例這種情況,采用searched delete會比使用positioned delete效率更高。但用positioned delete可以處理更復(fù)雜的邏輯
Positioned Update
一個用于更新的游標(biāo)(A cursor is updatable)應(yīng)該The cursor is deletable
>>-UPDATE----+-table-name-------------------+-------------->
+-view-name--------------------+
>-----SET--| assignment-clause |--------------------------->
>-----WHERE CURRENT OF--cursor-name-----------------------><
CREATE PROCEDURE upd_raise ( IN p_min DEC(4,2)
, IN p_max DEC(4,2) )
LANGUAGE SQL
SPECIFIC upd_raise
ur: BEGIN
-- Declare variables
DECLARE v_salary DEC(9,2);
DECLARE v_bonus DEC(9,2);
DECLARE v_comm DEC(9,2);
DECLARE v_raise DEC(4,2);
-- Declare returncode
DECLARE SQLSTATE CHAR(5);
-- Procedure logic
DECLARE c_emp CURSOR FOR
SELECT salary, bonus, comm
FROM employee
WHERE job!='PRES'
FOR UPDATE OF salary; -- (1)如果只是更新表中的一部分字段,可以利用
FOR UPDATE OF <column list>提高效
率,讓DB2引擎知道只有這些特定列要UPDATE
OPEN c_emp;
FETCH FROM c_emp INTO v_salary, v_bonus, v_comm;
WHILE ( SQLSTATE = '00000' ) DO
SET v_raise = p_min;
IF ( v_bonus >= 600 ) THEN
SET v_raise = v_raise + 0.04;
END IF;
IF ( v_comm < 2000 ) THEN
SET v_raise = v_raise + 0.03;
ELSEIF ( v_comm < 3000 ) THEN
SET v_raise = v_raise + 0.02;
ELSE
SET v_raise = v_raise + 0.01;
END IF;
IF ( v_raise > p_max ) THEN
SET v_raise = p_max;
END IF;
UPDATE employee
SET salary = v_salary * (1 + v_raise)
WHERE CURRENT OF c_emp;
FETCH FROM c_emp INTO v_salary, v_bonus, v_comm;
END WHILE;
CLOSE c_emp;
END ur
使用游標(biāo)時候的COMMIT和ROLLBACK:
數(shù)據(jù)庫程序中很重要的一點就是事務(wù)處理(transaction或者the unit of work(UOW))。事務(wù)當(dāng)中的任何一部分失敗,整個事物就會失敗。利用COMMIT和ROLLBACK進行適當(dāng)?shù)氖聞?wù)控制對于保證數(shù)據(jù)完整性來說是至關(guān)重要的。
當(dāng)在使用游標(biāo)的時候使用COMMIT或者ROLLBACK語句時,游標(biāo)的行動取決于是否在生命的時候加了WITH HOLD子句。如果一個游標(biāo)在聲明的時候沒有指定WITH HOLD,那么它的所有資源(游標(biāo),鎖,大對象數(shù)據(jù)類型或者LOB locators)都將在COMMIT或者ROLLBACK之后被釋放。因此,如果需要在完成一個事務(wù)之后使用游標(biāo),就必須重新打開游標(biāo),并從第一行開始執(zhí)行。如果定義了一個游標(biāo)WITH HOLD
,游標(biāo)就會在事務(wù)之間保存它的位置和鎖(lock)。需要明白的是,只有保證游標(biāo)位置的鎖被held了。
鎖(lock)是個數(shù)據(jù)庫對象(a database object),我們用它來控制多個應(yīng)用訪問同一個資源的方式。而一個LOB locator使存儲在本地變量中的4字節(jié)的值,程序可以用它來查到數(shù)據(jù)庫系統(tǒng)中的LOB對象的值
定義了WITH HOLD的游標(biāo)在COMMIT之后
1.仍然保證是打開(open)的
2.游標(biāo)指向下一個滿足條件的行之前
3.在COMMIT語句之后只允許FETCH和CLOSE
4.Positioned delete和positioned update只在同一事務(wù)中fetch的行上可用
5.所有的LOB locators會被釋放
6.除了保存聲明為WITH HOLD的游標(biāo)位置的鎖,其他鎖都會釋放
7.當(dāng)執(zhí)行了數(shù)據(jù)修改語句或者含有WITH HOLD游標(biāo)的修改語句被commit的時候
所有定義為WITH HOLD的游標(biāo)在ROLLBACK之后:
1.所有游標(biāo)會被關(guān)閉
2.所有在該事務(wù)中的鎖會被釋放
3.所有的LOB locators會被freed
例如:
CREATE PROCEDURE update_department ( )
LANGUAGE SQL
SPECIFIC upd_dept
ud: BEGIN
-- Declare variable
DECLARE v_deptno CHAR(3);
-- Declare returncode
DECLARE SQLSTATE CHAR(5);
DECLARE c_dept CURSOR WITH HOLD FOR
SELECT deptno
FROM department
FOR UPDATE OF location;
-- Declare condition handler
DECLARE CONTINUE HANDLER FOR SQLSTATE '24504', SQLSTATE '24501'
L1: LOOP -- (1)
LEAVE L1;
END LOOP;
-- Procedure logic
OPEN c_dept; --打開游標(biāo),指向第一行前面的位置
FETCH FROM c_dept INTO v_deptno; -- (2)
UPDATE department SET location='FLOOR1' WHERE CURRENT OF c_dept; -- (3)
COMMIT; -- (4)因為該游標(biāo)聲明為WITH
HOLD,此時游標(biāo)依舊打開,并且
指向第二行前面的位置。此時表
中第一行的鎖釋放了,且第一行
的值更新為FLOOR1
FETCH FROM c_dept INTO v_deptno; -- (5)得到第二行數(shù)據(jù),執(zhí)行成功
COMMIT; -- (6)COMMIT后游標(biāo)指向第三行之
前的位置,此時并沒有被fetched
UPDATE department SET location='FLOOR2' WHERE CURRENT OF c_dept; -- (7)這行
命令執(zhí)行失敗,因為此時游標(biāo)沒有指向
任何行,此時游標(biāo)在第二行和第三行之間
FETCH FROM c_dept INTO v_deptno; -- (8)成功
UPDATE department SET location='FLOOR3' WHERE CURRENT OF c_dept; -- (9)成功
COMMIT; -- (10)
FETCH FROM c_dept INTO v_deptno; -- (11)成功
UPDATE department SET location='FLOOR4' WHERE CURRENT OF c_dept; -- (12)成
功,此時第三行和第四行的值都變了
ROLLBACK; -- (13)第四行的值還原。
ROLLBACK之后游標(biāo)關(guān)閉了,
所有的鎖也都釋放了
FETCH FROM c_dept INTO v_deptno; -- (14)錯誤
UPDATE department SET location='FLOOR5' WHERE CURRENT OF c_dept; -- (15)
CLOSE c_dept;
RETURN 0;
END ud
上述存儲過程執(zhí)行前:
DEPTNO LOCATION
------ --------
A00 -
B01 -
C01 -
D01 -
D11 -
上述存儲過程執(zhí)行后:
DEPTNO LOCATION
------ --------
A00 FLOOR1
B01 -
C01 FLOOR3
D01 -
D11 -
如果上例中的游標(biāo)沒有聲明為WITH HOLD,從(5)到(15)的執(zhí)行都會失敗。因為游標(biāo)會在COMMIT或ROLLBACK之后隱性關(guān)閉
存儲過程中的Save Points可用于保存事務(wù)回滾的間斷點
>>- SAVEPOINT--savepoint-name----+--------+--------------------->
'-UNIQUE-'
>--ON ROLLBACK RETAIN CURSORS--+--------------------------+----><
'-ON ROLLBACK RETAIN LOCKS-'
savepoint-name不能以'SYS'開頭,否則會報SQLSTATE 42939的錯誤。UNIQUE選項表示這個save point name不會在Save Point活動期中被reused。ON ROLLBACK RETAIN CURSORS使游標(biāo)在rollback發(fā)生之后還被保留。附加的ON ROLLBACK RETAIN LOCKS防止在ROLLBACK之后鎖丟失
在一個事務(wù)中,可以定義多個save points
使用save points的偽代碼:
savepoint A;
Do program logic;
savepoint B;
Do more program logic;
savepoint C;
Do even more program logic;
之后就可以用含有SAVE POINT的ROLLBACK:ROLLBACK TO SAVEPOINT savepoint-name
如果ROLLBACK到了最后一個save point,那么這個save point之前的save point都還是活動的(active),你依舊可以ROLL BACK到更早的save point
例如:
savepoint a;
Do program logic;
savepoint b;
Do more program logic;
savepoint c;
Do even more program logic;
ROLLBACK TO SAVEPOINT c; (1)將事務(wù)數(shù)據(jù)返回到save point c
Do some new logic;
ROLLBACK TO SAVEPOINT a; (2)將事務(wù)數(shù)據(jù)返回到save point a
Do some more logic;
ROLLBACK TO SAVEPOINT b; (3)錯誤,因為此時save point b已經(jīng)不存在了
游標(biāo)除了可以在存儲過程中處理數(shù)據(jù)外,還可以用于返回結(jié)果集
比如:
CREATE PROCEDURE read_emp ( )
LANGUAGE SQL
SPECIFIC read_emp
DYNAMIC RESULT SETS 1 --(1)如果想用游標(biāo)返回結(jié)果集到一個應(yīng)用程序,必須聲明DYNAMIC RESULT SETS
re: BEGIN
-- Procedure logic
DECLARE c_emp CURSOR WITH RETURN FOR --(2)
SELECT salary, bonus, comm
FROM employee
WHERE job!='PRES';
OPEN c_emp; --(3)為客戶端保持游標(biāo)打開
END re
此時只返回了所有符合條件的員工的salary, bonus和commission字段。之后,結(jié)果集就可以被另外的存儲過程或客戶端程序調(diào)用
在存儲過程中我們除了數(shù)據(jù)操作語言(Data Manipulation Language (DML):SELECT, DELETE和UPDATE),還可以使用數(shù)據(jù)定義語言(Data Definition Language (DDL)),比如定義一個表。我們可以在存儲過程中定義一個表,然后用游標(biāo)返回結(jié)果集。但是游標(biāo)聲明必須在BEGIN ... END的一開始,但如果這樣的話,此時表還沒有建立,編譯時會報錯。但如果先聲明表,編譯也會報錯。這是我們可以用BEGIN ... END可以嵌套這個特性。我們在存儲過程末尾嵌套一個BEGIN ... END來聲明游標(biāo)
如:
CREATE PROCEDURE create_and_return ( )
LANGUAGE SQL
SPECIFIC create_and_return
DYNAMIC RESULT SETS 1
cr: BEGIN
-- Procedure logic
CREATE TABLE mytable (sid INT);
INSERT INTO mytable VALUES (1);
INSERT INTO mytable VALUES (2);
BEGIN --(1)
DECLARE c_cur CURSOR WITH RETURN
FOR SELECT *
FROM mytable;
OPEN c_cur; --(2)
END; --(3)OPEN必須在這個嵌套的BEGIN ... END中,因為游
標(biāo)的定義只在這個BEGIN ... END中有效
END cr
有時我們不只返回一個結(jié)果集,若返回多個結(jié)果集,要求:
1.CREATE PROCEDURE中的DYNAMIC RESULT SETS子句寫明想返回的結(jié)果集的數(shù)量
2.為每一個結(jié)果集聲明含有WITH RETURN的游標(biāo)
3.保證所有游標(biāo)返回給客戶端是打開的
例如:
CREATE PROCEDURE read_emp_multi ( )
LANGUAGE SQL
SPECIFIC read_emp_multi
DYNAMIC RESULT SETS 3 --(1)
re: BEGIN
-- Procedure logic
DECLARE c_salary CURSOR WITH RETURN FOR
SELECT salary
FROM employee;
DECLARE c_bonus CURSOR WITH RETURN FOR
SELECT bonus
FROM employee;
DECLARE c_comm CURSOR WITH RETURN FOR
SELECT comm
FROM employee;
OPEN c_salary;
OPEN c_bonus;
OPEN c_comm;
END re
游標(biāo)打開的順序反映了結(jié)果集返回給客戶端的順序
當(dāng)在存儲過程中使用游標(biāo)的時候,會影響其他應(yīng)用和人們使用這個數(shù)據(jù)庫。鎖的類型取決于游標(biāo)的類型和DB2的隔離級別(isolation level)
鎖模式(Lock Modes):
Table Row Lock Descriptions
|
Lock Mode
|
Applicable Object Type
|
Description
|
S (Share)
|
Rows, blocks, tables
|
The lock owner and all concurrent applications can read, but not update, the locked data.
|
U (Update)
|
Rows, blocks, tables
|
The lock owner can update data. Other UOW can read the data in the locked object, but cannot attempt to update it.
|
X (Exclusive)
|
Rows, blocks, tables, bufferpools
|
The lock owner can both read and update data in the locked object. Only uncommitted read applications can access the locked object.
|
不管是sql server 還是 oracle都提供了游標(biāo),其一般用途就是從查詢結(jié)構(gòu)中遍歷數(shù)據(jù)。游標(biāo)很好用,許多初學(xué)oracle或者sql server的程序員都喜歡用,然而如果濫用游標(biāo)的話,對程序的性能會造成很大的影響。我認(rèn)為:除非不得已,不要使用游標(biāo)。下面是我的一次優(yōu)化經(jīng)歷。
問題:
在我們的系統(tǒng)中,需要管理產(chǎn)品模型,對PDM了解的人知道,其實產(chǎn)品模型可以當(dāng)作一個特殊的零部件來處理,所以在數(shù)據(jù)表的時候產(chǎn)品模型與零部件使用相同的數(shù)據(jù)表來存放數(shù)據(jù),只不過是用一個flag來標(biāo)示是零部件還是產(chǎn)品模型。產(chǎn)品模型具有版本?,F(xiàn)在的要求的是查出最新的所有的零部件信息,查詢的時候允許用戶自定義查詢條件。
用戶自定義查詢條件是從界面上通過各種選項來構(gòu)造的,因此傳遞的是動態(tài)SQL語句,為了提高效率數(shù)據(jù)庫上建立了一個存儲過程專門來查詢產(chǎn)品模型。下面是一個初學(xué)oracle不久的編程人員編寫的存儲過程:
1
PROCEDURE GETPRODMODELBYSQL(p_sql IN VARCHAR2,
2
cur_mi_prodmodel OUT SYS_BASE.BASECURSOR) AS
3
sqlstr varchar2(1000);
4
idstr varchar2(1000);
5
6
7
dis_id number;
8
maxver number;
9
cursor disid_curs is
10
select distinct pt_id
11
from mi_part
12
where pt_flag = 2;
13
BEGIN
14
open disid_curs;
15
loop
16
fetch disid_curs
17
into dis_id;
18
exit when disid_curs% notfound;
19
20
select max(pv_id)
21
into maxver
22
from mi_partver
23
where pv_partid = dis_id;
24
25
if maxver is null then
26
idstr := idstr;
27
else
28
idstr := idstr || maxver || ',';
29
end if;
30
end loop;
31
32
idstr := rtrim(idstr, ',');
33
idstr := ltrim(idstr, ',');
34
if idstr is null then
35
sqlstr := p_sql || ' ORDER BY PV_PRODUCTID,PT_PARTNUMBER';
36
else
37
sqlstr := p_sql || ' and MI_PARTVER.pv_id in (' || idstr || ') ORDER BY PV_PRODUCTID,PT_PARTNUMBER';
38
end if;
39
40
open cur_mi_prodmodel for sqlstr;
41
END GETPRODMODELBYSQL;
這個存儲過程可以正常工作,但是其速度執(zhí)行起來非常慢,如果數(shù)據(jù)表中有很多數(shù)據(jù)的話,其速度很可能是直線下降,究其原因是因為使用游標(biāo)來遍歷然后構(gòu)造查詢語句。對這個存儲過程進行優(yōu)化:
1
PROCEDURE GETPRODMODELBYSQL(p_sql IN VARCHAR2,
2
cur_mi_prodmodel OUT SYS_BASE.BASECURSOR) AS
3
s varchar2(300);
4
sqlstr varchar2(1000);
5
/**//*idstr varchar2(1000);
6
7
8
dis_id number;
9
maxver number;
10
cursor disid_curs is
11
select distinct pt_id
12
from mi_part
13
where pt_flag = 2; */
14
BEGIN
15
/**//*open disid_curs;
16
loop
17
fetch disid_curs
18
into dis_id;
19
exit when disid_curs% notfound;
20
21
select max(pv_id)
22
into maxver
23
from mi_partver
24
where pv_partid = dis_id;
25
26
if maxver is null then
27
idstr := idstr;
28
else
29
idstr := idstr || maxver || ',';
30
end if;
31
end loop;*/
32
s:=' and MI_PARTVER.pv_id in (select a.pv_id from mi_partver a,mi_part c where a.pv_partid = c.pt_id and c.pt_flag =2 and
33
a.pv_id in (select e.pv_id from mi_partver e
34
where e.pv_id in (select max(b.pv_id) from mi_partver b where e.pv_partid = b.pv_partid)))';
35
/**//*idstr := rtrim(idstr, ',');
36
idstr := ltrim(idstr, ',');
37
if idstr is null then
38
sqlstr := p_sql || ' ORDER BY PV_PRODUCTID,PT_PARTNUMBER';
39
else
40
sqlstr := p_sql || ' and MI_PARTVER.pv_id in (' || idstr || ') ORDER BY PV_PRODUCTID,PT_PARTNUMBER';
41
end if;*/
42
sqlstr := p_sql || s || 'ORDER BY PV_PRODUCTID,PT_PARTNUMBER';
43
open cur_mi_prodmodel for sqlstr;
44
END GETPRODMODELBYSQL;
這里的有效語句是使用查詢語句來構(gòu)造查詢,其結(jié)果是速度得到了數(shù)量級的提高,而且避免了一個隱患就是緩沖區(qū)溢出,因為原來的存儲過程中定義了幾個varchar2(1000)的變量,實際上這個變量的上界是不可以預(yù)期的,而緩沖區(qū)溢出也是很多初學(xué)oracle的程序員經(jīng)常忽略甚至想不到的問題。
游標(biāo)(Cursor)是處理數(shù)據(jù)的一種方法,為了查看或者處理結(jié)果集中的數(shù)據(jù),游標(biāo)提供了在結(jié)果集中一次以行或者多行前進或向后瀏覽數(shù)據(jù)的能力。我們可以把游標(biāo)當(dāng)作一個指針,它可以指定結(jié)果中的任何位置,然后允許用戶對指定位置的數(shù)據(jù)進行處理。
1.游標(biāo)的組成
游標(biāo)包含兩個部分:一個是游標(biāo)結(jié)果集、一個是游標(biāo)位置。
游標(biāo)結(jié)果集:定義該游標(biāo)得SELECT語句返回的行的集合。游標(biāo)位置:指向這個結(jié)果集某一行的當(dāng)前指針。
2.游標(biāo)的分類
游標(biāo)共有3類:API服務(wù)器游標(biāo)、Transaction-SQL游標(biāo)和API客戶端游標(biāo)。
其中前兩種游標(biāo)都是運行在服務(wù)器上的,所以又叫做服務(wù)器游標(biāo)。
API服務(wù)器游標(biāo)
API服務(wù)器游標(biāo)主要應(yīng)用在服務(wù)上,當(dāng)客戶端的應(yīng)用程序調(diào)用API游標(biāo)函數(shù)時,服務(wù)器會對API函數(shù)進行處理。使用API函數(shù)和方法可以實現(xiàn)如下功能:
(1)打開一個連接。
(2)設(shè)置定義游標(biāo)特征的特性或?qū)傩?,API自動將游標(biāo)影射到每個結(jié)果集。
(3)執(zhí)行一個或多個Transaction-SQL語句。
(4)使用API函數(shù)或方法提取結(jié)果集中的行。
API服務(wù)器游標(biāo)包含以下四種:靜態(tài)游標(biāo)、動態(tài)游標(biāo)、只進游標(biāo)、鍵集驅(qū)動游標(biāo)(Primary key)
靜態(tài)游標(biāo)的完整結(jié)果集將打開游標(biāo)時建立的結(jié)果集存儲在臨時表中,(靜態(tài)游標(biāo)始終是只讀的)。靜態(tài)游標(biāo)具有以下特點:總是按照打開游標(biāo)時的原樣顯示結(jié)果集;不反映數(shù)據(jù)庫中作的任何修改,也不反映對結(jié)果集行的列值所作的更改;不顯示打開游標(biāo)后在數(shù)據(jù)庫中新插入的行;組成結(jié)果集的行被其他用戶更新,新的數(shù)據(jù)值不會顯示在靜態(tài)游標(biāo)中;但是靜態(tài)游標(biāo)會顯示打開游標(biāo)以后從數(shù)據(jù)庫中刪除的行。
動態(tài)游標(biāo)與靜態(tài)游標(biāo)相反,當(dāng)滾動游標(biāo)時動態(tài)游標(biāo)反映結(jié)果集中的所有更改。結(jié)果集中的行數(shù)據(jù)值、順序和成員每次提取時都會改變。
只進游標(biāo)不支持滾動,它只支持游標(biāo)從頭到尾順序提取數(shù)據(jù)行。注意:只進游標(biāo)也反映對結(jié)果集所做的所有更改。
鍵集驅(qū)動游標(biāo)同時具有靜態(tài)游標(biāo)和動態(tài)游標(biāo)的特點。當(dāng)打開游標(biāo)時,該游標(biāo)中的成員以及行的順序是固定的,鍵集在游標(biāo)打開時也會存儲到臨時工作表中,對非鍵集列的數(shù)據(jù)值的更改在用戶游標(biāo)滾動的時候可以看見,在游標(biāo)打開以后對數(shù)據(jù)庫中插入的行是不可見的,除非關(guān)閉重新打開游標(biāo)。
Transaction-SQL游標(biāo)
該游標(biāo)是基于Declare Cursor 語法,主要用于Transaction-SQL腳本、存儲過程以及觸發(fā)器中。Transaction-SQL游標(biāo)在服務(wù)器處理由客戶端發(fā)送到服務(wù)器的Transaction-SQL語句。
在存儲過程或觸發(fā)器中使用Transaction-SQL游標(biāo)的過程為:
(1)聲明Transaction-SQL變量包含游標(biāo)返回的數(shù)據(jù)。為每個結(jié)果集列聲明一個變量。聲明足夠大的變量來保存列返回的值,并聲明變量的類型為可從數(shù)據(jù)類型隱式轉(zhuǎn)換得到的數(shù)據(jù)類型。
(2)使用Declare Cursor語句將Transaction-SQL游標(biāo)與Select語句相關(guān)聯(lián)。還可以利用Declare Cursor定義游標(biāo)的只讀、只進等特性?!?/p>
(3)使用Open語句執(zhí)行Select語句填充游標(biāo)。
(4)使用Fetch Into語句提取單個行,并將每列中得數(shù)據(jù)移至指定的變量中。注意:其他Transaction-SQL語句可以引用那些變量來訪問提取的數(shù)據(jù)值。Transaction-SQL游標(biāo)不支持提取行塊。
(5)使用Close語句結(jié)束游標(biāo)的使用。注意:關(guān)閉游標(biāo)以后,該游標(biāo)還是存在,可以使用Open命令打開繼續(xù)使用,只有調(diào)用Deallocate語句才會完全釋放。
客戶端游標(biāo)
該游標(biāo)將使用默認(rèn)結(jié)果集把整個結(jié)果集高速緩存在客戶端上,所有的游標(biāo)操作都在客戶端的高速緩存中進行。注意:客戶端游標(biāo)只支持只進和靜態(tài)游標(biāo)。不支持其他游標(biāo)。
3.游標(biāo)的生命周期
游標(biāo)的生命周期包含有五個階段:聲明游標(biāo)、打開游標(biāo)、讀取游標(biāo)數(shù)據(jù)、關(guān)閉游標(biāo)、釋放游標(biāo)。
聲明游標(biāo)是為游標(biāo)指定獲取數(shù)據(jù)時所使用的Select語句,聲明游標(biāo)并不會檢索任何數(shù)據(jù),它只是為游標(biāo)指明了相應(yīng)的Select 語句。
Declare 游標(biāo)名稱 Cursor 參數(shù)
聲明游標(biāo)的參數(shù)
(1)Local與Global:Local表示游標(biāo)的作用于僅僅限于其所在的存儲過程、觸發(fā)器以及批處理中、執(zhí)行完畢以后游標(biāo)自動釋放。Global表示的是該游標(biāo)作用域是整個會話層。由連接執(zhí)行的任何存儲過程、批處理等都可以引用該游標(biāo)名稱,僅在斷開連接時隱性釋放。
(2)Forward_only與Scroll:前者表示為只進游標(biāo),后者表示為可以隨意定位。默認(rèn)為前者。
(3)Static、Keyset與Dynamic: 第一個表示定義一個游標(biāo),其數(shù)據(jù)存放到一個臨時表內(nèi),對游標(biāo)的所有請求都從臨時表中應(yīng)答,因此,對該游標(biāo)進行提取操作時返回的數(shù)據(jù)不反映對基表所作的修改,并且該游標(biāo)不允許修改。Keyset表示的是,當(dāng)游標(biāo)打開時,鍵集驅(qū)動游標(biāo)中行的身份與順序是固定的,并把其放到臨時表中。Dynamic表示的是滾動游標(biāo)時,動態(tài)游標(biāo)反映對結(jié)果集內(nèi)所有數(shù)據(jù)的更改。
(4)Read_only 、Scroll_Locks與Optimistic:第一個表示的是只讀游標(biāo),第二個表示的是在使用的游標(biāo)結(jié)果集數(shù)據(jù)上放置鎖,當(dāng)行讀取到游標(biāo)中然后對它們進行修改時,數(shù)據(jù)庫將鎖定這些行,以保證數(shù)據(jù)的一致性。Optimistic的含義是游標(biāo)將數(shù)據(jù)讀取以后,如果這些數(shù)據(jù)被更新了,則通過游標(biāo)定位進行的更新與刪除操作將不會成功。
標(biāo)準(zhǔn)游標(biāo):
Declare MyCursor Cursor
For Select * From Master_Goods
只讀游標(biāo)
Declare MyCusror Cursor
For Select * From Master_Goods
For Read Only
可更新游標(biāo)
Declare MyCusror Cursor
For Select * From Master_Goods
For UpDate
打開游標(biāo)使用Open語句用于打開Transaction-SQL服務(wù)器游標(biāo),執(zhí)行Open語句的過程中就是按照Select語句進行填充數(shù)據(jù),打開游標(biāo)以后游標(biāo)位置在第一行。
打開游標(biāo)
全局游標(biāo):Open Global MyCursor 局部游標(biāo): Open MyCursor
讀取游標(biāo)數(shù)據(jù):在打開游標(biāo)以后,使用Fetch語句從Transaction-SQL服務(wù)器游標(biāo)中檢索特定的一行。使用Fetch操作,可以使游標(biāo)移動到下一個記錄,并將游標(biāo)返回的每個列得數(shù)據(jù)分別賦值給聲明的本地變量。
Fetch [Next | Prior | First | Last | Absolute n | Relative n ] From MyCursor
Into @GoodsID,@GoodsName
其中:Next表示返回結(jié)果集中當(dāng)前行的下一行記錄,如果第一次讀取則返回第一行。默認(rèn)的讀取選項為Next
Prior表示返回結(jié)果集中當(dāng)前行的前一行記錄,如果第一次讀取則沒有行返回,并且把游標(biāo)置于第一行之前。
First表示返回結(jié)果集中的第一行,并且將其作為當(dāng)前行。
Last表示返回結(jié)果集中的最后一行,并且將其作為當(dāng)前行。
Absolute n 如果n為正數(shù),則返回從游標(biāo)頭開始的第n行,并且返回行變成新的當(dāng)前行。如果n為負,則返回從游標(biāo)末尾開始的第n行,并且返回行為新的當(dāng)前行,如果n為0,則返回當(dāng)前行。
Relative n 如果n為正數(shù),則返回從當(dāng)前行開始的第n行,如果n為負,則返回從當(dāng)前行之前的第n行,如果為0,則返回當(dāng)前行。
關(guān)閉游標(biāo)調(diào)用的是Close語句,方式如下:Close Global MyCursor Close MyCursor
釋放游標(biāo)調(diào)用的是Deallocate語句,方法如下:Deallocate Glboal MyCursor Deallocate MyCursor
游標(biāo)實例:
Declare MyCusror Cursor Scroll
For Select * From Master_Goods Order By GoodsID
Open MyCursor
Fetch next From MyCursor
Into @GoodsCode,@GoodsName
While(@@Fetch_Status = 0)
Begin
Begin
Select @GoodsCode = Convert(Char(20),@GoodsCode)
Select @GoodsName = Convert(Char(20),@GoodsName)
PRINT @GoodsCode + ':' + @GoodsName
End
Fetch next From MyCursor
Into @GoodsCode,@GoodsName
End
Close MyCursor
Deallocate MyCursor
修改當(dāng)前游標(biāo)的數(shù)據(jù)方法如下:
UpDate Master_Goods Set GoodsName = 'yangyang8848' Where Current Of MyCursor;
刪除當(dāng)前游標(biāo)行數(shù)據(jù)的方法如下:
Delete From Master_Goods Where Current Of MyCursor
Select @@CURSOR_ROWS 可以得到當(dāng)前游標(biāo)中存在的數(shù)據(jù)行數(shù)。注意:此變量為一個連接上的全局變量,因此只對應(yīng)最后一次打開的游標(biāo)。
select * from dbo.Products
select * from dbo.Orders
select * from [Order Details]
select * from dbo.Categories Select b.ProductID,datepart(Year,a.OrderDate) as [Year],datepart(Month,a.OrderDate) as [Month],Sum(b.Quantity) as Quantity, c.ProductName,Count(a.OrderID) as Orders from Orders a Inner Join [Order Details] b On a.OrderID = b.OrderID Inner Join Products c On b.ProductID=c.ProductID Group By b.ProductID,a.OrderDate,c.ProductName Order by b.ProductID Asc,a.OrderDate Asc go
--第一題
Alter PROCEDURE USP_Product1 AS BEGIN DECLARE @ID int,@TempID int DECLARE @ProductName NVARCHAR(50) DECLARE @Year NVARCHAR(4) DECLARE @Month NVARCHAR(2) DECLARE @OrderCount int DECLARE @DetailsCount int DECLARE @TotalCount int DECLARE @TotalDetails int DECLARE Cursor_Product CURSOR LOCAL FOR
--STEP1 聲明游標(biāo) Select b.ProductID,datepart(Year,a.OrderDate) as [Year],datepart(Month,a.OrderDate) as [Month],Sum(b.Quantity) as Quantity, c.ProductName,Count(a.OrderID) as Orders from Orders a Inner Join [Order Details] b On a.OrderID = b.OrderID Inner Join Products c On b.ProductID=c.ProductID Group By b.ProductID,a.OrderDate,c.ProductName Order by b.ProductID Asc,a.OrderDate Asc FOR READ ONLY
CREATE TABLE #ProductDetails (產(chǎn)品編號 INT ,產(chǎn)品名稱 NVARCHAR(50),月份 NVARCHAR(10),訂單總數(shù) INT,出貨總量 INT)
--創(chuàng)建臨時表 set @TempID=null --判斷是否為同一個商品 set @TotalCount = 0 set @TotalDetails = 0 OPEN Cursor_Product
--STEP2 打開游標(biāo) WHILE(1=1) BEGIN
--STEP3 從游標(biāo)中提取行 FETCH NEXT FROM Cursor_Product INTO @ID,@Year,@Month,@DetailsCount,@ProductName,@OrderCount IF(@@FETCH_STATUS<>0)BREAK
--跳出循環(huán) if @TempID is null
--判斷是第一次的話為@TempID賦值 BEGIN set @TempID=@ID END if @TempID<>@ID
--判斷是否需要插入小計 BEGIN insert into #ProductDetails Values(null,null,N'小計:',@TotalCount,@TotalDetails) set @TotalCount = 0 set @TotalDetails = 0 set @TempID=@ID Continue
--跳入下次循環(huán) END set @TotalCount = @TotalCount+@OrderCount set @TotalDetails = @TotalDetails+@DetailsCount insert into #ProductDetails Values(@ID,@ProductName,Convert(Nvarchar(50),@Year)+N'年'+Convert(Nvarchar(50),@Month)+N'月',@OrderCount,@DetailsCount) END select * from #ProductDetails CLOSE Cursor_Product
--STEP4 關(guān)閉游標(biāo) DEALLOCATE Cursor_Product
--STEP5 釋放游標(biāo) END GO EXEC USP_Product1 -- DROP Proc USP_Product1 GO Select c.ProductName,Count(a.OrderID) as Orders,SUM(c.UnitPrice*b.Quantity) as Price from Orders a Inner Join [Order Details] b On a.OrderID = b.OrderID Inner Join Products c On b.ProductID=c.ProductID Where c.CategoryID=1 Group By c.ProductName Order by c.ProductName Asc Go
--第二題 Alter PROCEDURE USP_Product2 AS BEGIN DECLARE @ID int,@Temp int DECLARE @CategoryName NVARCHAR(50) DECLARE @ProductName NVARCHAR(50) DECLARE @POCount int DECLARE @POPrices money --小計 DECLARE @OrderCount int DECLARE @OrderPrices money --總計 DECLARE @Orders int DECLARE @Prices money
-- 聲明游標(biāo)1 DECLARE Cursor_Categories CURSOR LOCAL FOR select CategoryID,CategoryName from Categories FOR READ ONLY CREATE TABLE #ProductDetails (產(chǎn)品類別 INT ,類別名稱 NVARCHAR(50),產(chǎn)品名稱 NVARCHAR(50),下單總次數(shù) INT,下單總價格 INT)
--創(chuàng)建臨時表 --小計 SET @OrderCount = 0 SET @OrderPrices = 0
--總計 SET @Orders = 0 SET @Prices = 0 OPEN Cursor_Categories
-- 打開游標(biāo)1 WHILE(1=1) BEGIN
-- 從游標(biāo)1中提取行 FETCH NEXT FROM Cursor_Categories INTO @ID,@CategoryName IF(@@FETCH_STATUS<>0)BREAK
--跳出循環(huán) DECLARE Cursor_Product CURSOR LOCAL FOR
-- 聲明游標(biāo)2 Select c.ProductName,Count(a.OrderID) as Orders,SUM(c.UnitPrice*b.Quantity) as Price from Orders a Inner Join [Order Details] b On a.OrderID = b.OrderID Inner Join Products c On b.ProductID=c.ProductID Where c.CategoryID=@ID Group By c.ProductName Order by c.ProductName Asc FOR READ ONLY Set @Temp = null OPEN Cursor_Product
-- 打開游標(biāo)2 WHILE(1=1) BEGIN FETCH NEXT FROM Cursor_Product INTO @ProductName,@POCount,@POPrices IF(@@FETCH_STATUS<>0)BREAK
--跳出循環(huán) if(@Temp is Null) -- 第一次進入 BEGIN SET @Temp=1 SET @OrderCount=@POCount SET @OrderPrices=@POPrices Insert Into #ProductDetails Values(@ID,@CategoryName,@ProductName,@POCount,@POPrices) END Else BEGIN SET @OrderCount= @OrderCount + @POCount SET @OrderPrices= @OrderPrices + @POPrices Insert Into #ProductDetails(產(chǎn)品名稱,下單總次數(shù),下單總價格) Values(@ProductName,@POCount,@POPrices) END END Insert Into #ProductDetails(產(chǎn)品名稱,下單總次數(shù),下單總價格) Values('小計:',@OrderCount,@OrderPrices)
--累加總計 SET @Orders = @Orders + @OrderCount SET @Prices = @Prices + @OrderPrices CLOSE Cursor_Product
-- 關(guān)閉游標(biāo)2 DEALLOCATE Cursor_Product
-- 釋放游標(biāo)2 END Insert Into #ProductDetails(產(chǎn)品名稱,下單總次數(shù),下單總價格) Values('總計:',@Orders,@Prices) select * from #ProductDetails CLOSE Cursor_Categories
-- 關(guān)閉游標(biāo)1 DEALLOCATE Cursor_Categories
-- 釋放游標(biāo)1 END GO EXEC USP_Product2 -- DROP PROC USP_Product2