쿼리문이 여러 개의 레코드를 반환할 수 있습니다. 데이터의 양이 매우 클 경우 쿼리 결과 세트의 레코드를 하나씩 읽으려면 커서를 사용해야 합니다. 애플리케이션은 필요에 따라 데이터를 스크롤하거나 찾아볼 수 있습니다. 이 글에서는 커서의 개념과 분류, 기본적인 동작을 소개합니다.
1: 커서 이해
커서는 사용자가 개별 데이터 행에 액세스할 수 있도록 하는 SQL Server의 데이터 액세스 메커니즘입니다. 사용자는 각 행을 개별적으로 처리할 수 있으므로 시스템 오버헤드와 잠재적인 차단 상황을 줄일 수 있습니다. 또한 사용자는 이러한 데이터로 생성된 SQL 코드를 사용하여 즉시 실행하거나 출력할 수도 있습니다.
1. 커서의 개념
커서는 주로 저장 프로시저, 트리거 및 T_SQL 스크립트에서 사용되는 데이터 처리 방법으로 결과 집합의 내용을 다른 T_SQL에서 사용할 수 있도록 합니다. 진술. 결과 세트를 보거나 처리하는 동안 데이터를 앞뒤로 이동할 수 있는 기능입니다. C 언어의 포인터와 유사하게 결과 집합의 모든 위치를 가리킬 수 있습니다. 결과 집합을 하나씩 개별적으로 처리하려면 결과 집합을 가리키는 커서 변수를 선언해야 합니다.
SQL Server의 데이터 연산 결과는 모두 집합 지향적이며 WHERE 절을 사용하여 쿼리 결과를 제한하지 않는 한 테이블의 단일 레코드를 설명하는 표현식 형식이 없습니다. 이 기능을 제공할 수 있으며 커서의 사용 및 작동 프로세스가 보다 유연하고 효율적입니다.
2. 커서의 장점
SELECT 문은 결과 집합을 반환하지만 때로는 애플리케이션이 전체 결과 집합을 효과적으로 처리할 수 없는 경우가 있는데, 커서는 이를 한 번에 하나의 레코드를 추출하는 메커니즘을 제공합니다. 여러 레코드가 포함된 결과 집합에서 커서는 항상 단일 홉 SQL 선택 문과 연결되며 결과 집합과 특정 레코드를 가리키는 커서 위치로 구성됩니다. 커서를 사용하면 다음과 같은 이점이 있습니다.
(1) 전체 집합에 대해 동일한 작업을 수행하는 대신 프로그램이 SELECT 쿼리 문에서 반환된 행 집합에서 매번 동일하거나 다른 작업을 수행할 수 있습니다. .
(2) 커서 위치에 따라 행을 삭제하고 업데이트하는 기능을 제공합니다.
(3) 커서는 데이터베이스 관리 시스템과 애플리케이션 설계 사이의 가교 역할을 하며 두 가지 처리 방법을 연결합니다.
3. 커서 분류
SQL Server는 3가지 커서 구현을 지원합니다:
(1) Transact_SQL 커서
는 주로 DECLARE CURSOR 구문을 기반으로 합니다. T_SQL 스크립트, 저장 프로시저 및 트리거에 사용됩니다. T_SQL 커서는 서버에서 구현되며 클라이언트에서 서버로 전송된 T_SQL 문에 의해 관리됩니다. 또한 일괄 처리, 저장 프로시저 또는 트리거에 포함될 수도 있습니다.
(2). API(응용 프로그래밍 인터페이스) 서버 커서
OLE DB 및 ODBC에서 API 커서 기능을 지원하며 API 서버 커서가 서버에 구현됩니다. 클라이언트 애플리케이션이 API 커서 함수를 호출할 때마다 SQL ServerSQL Server Native Client OLE DB 공급자 또는 ODBC 드라이버는 API 서버 커서에서 작동하도록 요청을 서버에 전송합니다.
(3) 클라이언트측 커서
는 SQL Server Native Client ODBC 드라이버와 ADO API를 구현하는 DLL에 의해 내부적으로 구현됩니다. 클라이언트측 커서는 클라이언트측에서 모든 결과 집합 행을 캐시하여 구현됩니다. 클라이언트 애플리케이션이 API 커서 함수를 호출할 때마다 SQL ServerSQL Server Native Client ODBC 드라이버 또는 ADO DLL은 클라이언트에 캐시된 결과 집합의 행에 대해 커서 작업을 수행합니다.
T_SQL 커서와 서버 커서는 서버에 구현되어 있으므로 통칭하여 서버 커서라고 부릅니다.
ODBC와 ADO는 Microsoft SQL Server에서 지원하는 커서 유형을 4가지로 정의하므로 T_SQL 커서에 대해 4가지 커서 유형을 지정할 수 있습니다.
SQL Server에서 지원하는 네 가지 유형의 API 서버 커서는 다음과 같습니다.
(i) Enter 전용 커서
Enter 전용 커서는 스크롤을 지원하지 않습니다. 처음부터 시작하는 커서를 지원합니다. 끝에서 끝까지 순서대로 추출합니다. 행은 데이터베이스에서 추출된 후에만 검색할 수 있습니다. 현재 사용자가 실행했거나 다른 사용자가 제출하여 결과 집합의 행에 영향을 미치는 모든 INSERT, UPDATE 및 DELETE 문의 경우 커서에서 해당 행을 추출하면 효과가 표시됩니다.
커서는 뒤로 스크롤할 수 없으므로 행을 가져온 후 데이터베이스의 행에 적용된 대부분의 변경 사항은 커서를 통해 표시되지 않습니다. 수정된 결과 집합에서 행의 위치를 결정하는 데 값이 사용되는 경우(예: 클러스터형 인덱스에 포함된 열 업데이트) 수정된 값은 커서를 통해 표시됩니다.
(ii). 정적 커서
SQL Server 정적 커서는 항상 읽기 전용입니다. 커서가 열릴 때 전체 결과 집합이 tempdb에 구축됩니다. 정적 커서는 항상 커서가 열릴 때의 결과 집합을 표시합니다.
커서는 결과 집합의 구성원 자격에 영향을 미치는 데이터베이스의 변경 사항을 반영하지 않으며 결과 집합에 결합된 행의 열 값에 대한 변경 사항도 이후에는 나타나지 않습니다. 커서 열기 데이터베이스에 새로 삽입된 행(이 행이 커서 SELECT 문의 검색 조건을 충족하더라도). 결과 집합을 구성하는 행이 다른 사용자에 의해 업데이트되면 새 데이터 값이 정적 커서에 나타나지 않습니다. 정적 커서는 커서가 열린 후 데이터에서 삭제된 행을 표시합니다. UPDATE, INSERT 또는 DELETE 작업은 정적 커서(커서를 닫았다가 다시 열지 않는 한) 또는 커서를 연 동일한 연결을 사용하여 수정한 사항에도 반영되지 않습니다.
(iii) 키 기반 커서
이 커서의 구성원과 행 순서는 고정되어 있습니다. 키 세트 기반 커서는 키 세트라고 하는 고유 식별자(키) 세트에 의해 제어됩니다. 키는 결과 집합의 각 행을 고유하게 식별하는 열 집합에서 생성됩니다. 키 집합은 커서가 열릴 때 SELECT 문의 요구 사항을 충족하는 모든 행의 키 값 집합입니다. 키 세트 기반 커서에 해당하는 키 세트는 커서가 열릴 때 tempdb에 생성됩니다.
(IV). 동적 커서
동적 커서는 정적 커서와 반대입니다. 동적 커서는 커서가 스크롤될 때 결과 집합에 적용된 모든 변경 사항을 반영합니다. 결과 집합의 행 데이터 값, 순서 및 멤버는 가져올 때마다 변경됩니다. 모든 사용자가 수행한 모든 UPDATE, INSERT, DELETE 문은 커서를 통해 볼 수 있습니다. SQLSePos 또는 T_SQL WHERE CURRENT OF 절과 같은 API 함수를 사용하여 커서를 통해 업데이트되면 즉시 표시됩니다. 커서 외부에서 수행된 업데이트는 커서의 트랜잭션 격리 수준이 커밋되지 않은 읽기로 설정되어 있지 않으면 커밋될 때까지 표시되지 않습니다.
2: 커서의 기본 동작
1. 커서 선언
커서는 크게 커서 결과 집합과 커서 위치의 두 부분으로 구성됩니다. 커서를 정의하는 SELECT 문에서 반환된 행 중 커서 위치는 이 결과 집합의 행에 대한 포인터입니다.
커서를 사용하기 전에 커서를 선언해야 합니다. SQL Server의 선언에는 DECLARE CURSOR 문을 사용합니다. 커서 선언에는 커서의 스크롤 동작과 다음에서 작동하는 결과 집합에 대한 사용자 생성 쿼리가 포함됩니다. 구문 형식은 다음과 같습니다.
DECLARE cursor_name CURSOR [ LOCAL | GLOBAL] [ FORWARD_ONLY | SCROLL ] [ STATIC | KEYSET | DYNAMIC | FAST_FORWARD ] [ READ_ONLY | SCROLL_LOCKS | OPTIMISTIC ] [ TYPE_WARNING ] FOR select_statement [ FOR UPDATE [ OF column_name [,...n] ] ]
cursor_name: 정의된 T_SQL 서버 커서의 이름입니다.
LOCAL: 이 커서의 범위는 커서가 생성된 일괄 처리, 저장 프로시저 또는 트리거에 대해 로컬입니다.
GLOBAL: 커서의 범위가 전역임을 지정합니다.
FORWARD_ONLY: 커서가 첫 번째 행에서 마지막 행까지만 스크롤할 수 있도록 지정합니다. FETCH NEXT는 유일하게 지원되는 가져오기 옵션입니다. FORWARD_ONLY가 지정된 경우 STATIC, KEYSET 및 DYNAMIC 키워드가 지정되지 않으면 커서는 FORWARD_ONLY 및 SCROLL이 모두 지정된 경우 DYNAMIC 커서로 작동합니다. STATIC, KEYSET 및 DYNAMIC 키워드가 지정됩니다. 그렇지 않으면 기본값은 FORWARD_ONLY입니다. STATIC, KEYSET 및 DYNAMIC 커서의 기본값은 SCROLL입니다. ODBC 및 ADO와 같은 데이터베이스 API와 달리 STATIC, KEYSET 및 DYNAMIC T_SQL 커서는 FORWARD_ONLY를 지원합니다.
STATIC: 커서가 사용할 데이터의 임시 복사본을 생성하도록 커서를 정의합니다. 커서에 대한 모든 요청은 tempdb의 임시 테이블에서 응답되지 않습니다. 기본 테이블에 대한 수정 사항은 인출 작업 중에 반환된 데이터에 반영되지 않으며 커서는 수정을 허용하지 않습니다.
KEYSET: 커서가 열릴 때 커서 아래 행의 구성원과 순서가 고정되도록 지정합니다. 행을 고유하게 식별하는 키는 tempdb 내의 keyset이라는 테이블에 내장되어 있습니다.
동적: 커서가 스크롤될 때 결과 집합 내의 행에 대한 모든 데이터 변경 사항을 반영하도록 커서를 정의합니다. 각 인출 시 행의 데이터 값, 순서 및 멤버십이 변경되며 ABSOLUTE 인출 옵션은 동적 커서에서 지원되지 않습니다.
FAST_FORWARD: 성능 최적화가 활성화된 FORWARD_ONLY 및 READ_ONLY 커서를 지정합니다. SCROLL 또는 FOR_UPDATE가 지정되면 FAST_FORWARD를 지정할 수 없습니다.
SCROLL_LOCKS: 커서를 통한 위치 업데이트 또는 삭제가 성공해야 함을 지정합니다. SQL Server는 행을 커서로 읽을 때 나중에 수정할 수 있도록 행을 잠급니다. FAST_FORWARD 또는 STATIC도 지정하면 SCROLL_LOCKS를 지정할 수 없습니다.
OPTIMISTIC: 커서를 읽은 후 행이 업데이트된 경우 커서를 통한 위치 업데이트 또는 위치 삭제가 성공하지 않도록 지정합니다. 행을 커서로 읽어들일 때 SQL Server는 행을 잠그지 않고 대신 타임스탬프 열 값 비교를 사용하여 테이블에 타임스탬프 열이 포함되어 있지 않은 경우 행이 수정되었는지 여부를 확인합니다. 대신 체크섬 값을 사용하여 행이 수정되면 위치 지정 업데이트 또는 삭제 시도가 실패하며 FAST_FORWARD도 지정된 경우 OPTIMISTIC을 지정할 수 없습니다.
TYPE_WARNING: 커서가 요청된 유형에서 다른 유형으로 암시적으로 변환될 때 경고 메시지가 클라이언트에 전송되도록 지정합니다.
select_statement: 커서 결과 집합을 정의하는 표준 SELECT 문입니다.
[예시]cursor_fruit라는 이름의 커서를 선언
USE sample_db; GO DECLARE cursor_fruit CURSOR FOR SELECT f_name,f_price FROM fruits;
2. 커서를 엽니다.
커서를 사용하기 전에 열어야 합니다. 커서, 커서를 여는 구문은 다음과 같습니다.
OPEN [ GLOBAL ] cursor_name | cursor_variable_name;
GLOBAL: 커서_이름을 전역 커서로 지정합니다.
cursor_name: 선언된 커서의 이름입니다. 전역 및 로컬 커서가 모두 커서_이름을 이름으로 사용하는 경우 GLOBAL이 지정되면 커서_이름은 전역 커서를 참조하고, 그렇지 않으면 커서_이름은 로컬 커서를 참조합니다.
cursor_variable_name: 커서 변수의 이름입니다.
[예시] 위 예시에서 선언한cursor_fruit라는 이름의 커서를 엽니다.
USE sample_db; GO OPEN cursor_fruit;
3.커서에 있는 데이터를 읽습니다.
커서를 연 후 읽을 수 있습니다. 커서 FETCH 명령은 커서에 있는 특정 데이터 행을 읽을 수 있습니다. FETCH의 구문은 다음과 같습니다.
ETCH [ [ NEXT | PRIOR | FIRST | LAST | ABSOLUTE { n | @nvar } | RELATIVE { n | @nvar } ] FROM ] { { [GLOBAL ] cursor_name } | @cursor_variable_name} [ INTO @variable_name [ ,...n ] ]
NEXT:紧跟当前行返回结果行,并且当前行递增为返回行,如果FETCH NEXT为对游标的第一次提取操作,则返回结果集中的第一行。NEXT为默认的游标提取选项。
PRIOR:返回紧邻当前行前面的结果行,并且当前行递减为返回行,如果FETCH PRIOR为对游标的第一次提取操作,则没有行返回并且游标置于第一行之前。
FIRST:返回游标中的第一行并将其作为当前行。
LAST:返回游标中的最后一行并将其作为当前行。
ABSOLUTE { n | @nvar }:如果n或@nvar为正,则返回从游标头开始向后n行的第n行,并将返回行变成新的当前行。如果n或@nvar为负,则返回从游标末尾开始向前的n行的第n行,并将返回行变成新的当前行。如果n或@nvar为0,则不返回行。n必须是整数常量,并且@nvar的数据类型必须为int、tinyint或smallint.
RELATIVE { n | @nvar }:如果n或@nvar为正,则返回从当前行开始向后的第n行。如果n或@nvar为负,则返回从当前行开始向前的第n行。如果n或@nvar为0,则返回当前行,对游标第一次提取时,如果在将n或@nvar设置为负数或0的情况下指定FETCH RELATIVE,则不返回行,n必须是整数常量,@nvar的数据类型必须是int、tinyint或smallint.
GLOBAL:指定cursor_name是全局游标。
cursor_name:已声明的游标的名称。如果全局游标和局部游标都使用cursor_name作为其名称,那么如果指定了GLOBAL,则cursor_name指的是全局游标,否则cursor_name指的是局部游标。
@cursor_variable_name:游标变量名,引用要从中进行提取操作的打开的游标。
INTO @variable_name [ ,…n ]:允许将提取操作的列数据放到局部变量中。列表中的各个变量从左到右与游标结果集中的相应列相关联。各变量的数据类型必须与相应的结果集列的数据类型相匹配,或是结果集列数据类型所支持的隐士转换。变量的数目必须与游标选择列表中的列数一致。
【例】使用名称为cursor_fruit的光标,检索fruits表中的记录,输入如下:
USE sample_db; GO FETCH NEXT FROM cursor_fruit WHILE @@FETCH_STATUS=0 BEGIN FETCH NEXT FROM cursor_fruit END;
4.关闭游标
SQL Server 在打开游标之后,服务器会专门为游标开辟一定的内存空间存放游标操作的数据结果集,同时游标的使用也会根据具体情况对某些数据进行封锁。所以在不使用游标的时候,可以将其关闭,以释放游标所占用的服务器资源,关闭游标使用CLOSE语句。语法格式如下:
CLOSE [ GLOBAL ] cursor_name | cursor_variable_name
【例】关闭名称为cursor_fruit的游标
CLOSE cursor_fruit;
5.释放游标
游标操作的结果集空间虽然被释放了,但是游标本身也会占用一定的计算集资源,所以使用完游标之后,为了收回被游标占用的资源,应该将游标释放。释放游标使用DEALLOCATE语句,语法格式如下:
DEALLOCATE [GLOBAL] cursor_name | @ccursor_variable_name
@ccursor_variable_name:游标变量的名称,@ccursor_variable_name必须为cursor类型。
DEALLOCATE @ccursor_variable_name 语句只删除对游标变量名称的引用,直到批处理、存储过程或触发器结束时变量离开作用域,才释放变量。
【例】使用DEALLOCATE语句释放名称为cursor_fruit的变量,输入如下:
DEALLOCATE cursor_fruit;
三:游标的运用
1.使用游标变量
声明变量用DECLARE,为变量赋值可以用set或SELECT语句,对于游标变量的声明和赋值,其操作基本相同。在具体使用时,首先要创建一个游标,将其打开后,将游标的值赋给游标变量,并通过FETCH语句从游标变量中读取值,最后关闭释放游标。
【例】声明名称为@varCursor的游标变量,输入如下:
DECLARE @varCursor Cursor --声明游标变量 DECLARE cursor_fruit CURSOR FOR --创建游标 SELECT f_name,f_price FROM fruits; OPEN cursor_fruit --打开游标 SET @varCursor=cursor_fruit --为游标变量赋值 FETCH NEXT FROM @varCursor --从游标变量中读取值 WHILE @@FETCH_STATUS=0 --判断FETCH语句是否执行成功 BEGIN FETCH NEXT FROM @varCursor --读取游标变量中的数据 END CLOSE @varCursor --关闭游标 DEALLOCATE @varCursor; --释放游标
2.用游标为变量赋值
在游标的操作过程中,可以使用FETCH语句将数据值存入变量,这些保持表中列值的变量可以在后面的程序中使用。
【例】创建游标cursor_variable,将fruits表中的记录f_name,f_price值赋给变量@fruitName和@fruitPrice,并打印输出。
3.用ORDER BY 子句改变游标中的执行顺序
游标是一个查询结果集,那么能不能对结果进行排序呢?答案是否定的。与基本的SELECT语句中的排序方法相同,ORDER BY子句添加到查询中可以对游标查询的结果排序。
注意:只有出现在游标中的SELECT语句中的列才能作为ORDER BY 子句的排序列,而对与非游标的SELECT语句中,表中任何列都可以作为ORDER BY 的排序列,即使该列没有出现在SELECT语句的查询结果列中。
【例】声明名称为cursor_order的游标,对fruits表中的记录按照价格字段降序排列,输入语句如下:
4.用游标修改数据
【例】声明整型变量@sid=101,然后声明一个对fruits表进行操作的游标,打开该游标,使用FETCH NEXT方法来获取游标中的每一行的数据,如果获取到的记录的s_id的字段值与@sid值相同,将s_id=@sid的记录中的f_price修改为12.2,最后关闭释放游标,输入如下:
5.用游标删除数据
使用游标删除数据时,既可以删除游标结果集中的数据,也可以删除基本表中的数据
【例】使用游标删除fruits表中s_id=102的记录,如下
以上例子的sql脚本:
USE sample_db; create TABLE fruits( f_id int IDENTITY(1,1) PRIMARY KEY,--水果id s_id int not null, --供应商id f_name varchar(255) not null,--水果名称 f_price decimal(8,2) not null --水果价格 ); insert into fruits (s_id,f_name,f_price) values (101,'apple',5.8), (102,'blackberry',6.8), (105,'orange',4.5), (102,'banana',3.5), (103,'lemon',8.0), (104,'grape',7.6), (101,'melon',10.5); --1.声明名称为cursor_fruit的游标 USE sample_db; GO DECLARE cursor_fruit CURSOR FOR SELECT f_name,f_price FROM fruits; --2.打开游标 OPEN cursor_fruit; --3.读取游标中的数据 --【例】使用名称为cursor_fruit的光标,检索fruits表中的记录,输入如下: USE sample_db; GO FETCH NEXT FROM cursor_fruit WHILE @@FETCH_STATUS=0 BEGIN FETCH NEXT FROM cursor_fruit END; --4.关闭关闭名称为cursor_fruit的游标 CLOSE cursor_fruit --5.释放游标 DEALLOCATE cursor_fruit; --游标的运用 --1.使用游标变量 --声明名称为@varCursor的游标变量 DECLARE @varCursor Cursor --声明游标变量 DECLARE cursor_fruit CURSOR FOR --创建游标 SELECT f_name,f_price FROM fruits; OPEN cursor_fruit --打开游标 SET @varCursor=cursor_fruit --为游标变量赋值 FETCH NEXT FROM @varCursor --从游标变量中读取值 WHILE @@FETCH_STATUS=0 --判断FETCH语句是否执行成功 BEGIN FETCH NEXT FROM @varCursor --读取游标变量中的数据 END CLOSE @varCursor --关闭游标 DEALLOCATE @varCursor; --释放游标 --2.用游标为变量赋值 --创建游标cursor_variable,将fruits表中的记录f_name,f_price值赋给变量@fruitName和@fruitPrice,并打印输出。 DECLARE @fruitName varchar(50),@fruitPrice DECIMAL(8,2) DECLARE cursor_variable CURSOR FOR SELECT f_name,f_price FROM fruits WHERE s_id=101; OPEN cursor_variable FETCH NEXT FROM cursor_variable INTO @fruitName,@fruitPrice PRINT '编号为101的供应商提供的水果种类和价格为:' WHILE @@FETCH_STATUS=0 BEGIN PRINT @fruitName+' '+STR(@fruitPrice,8,2) FETCH NEXT FROM cursor_variable INTO @fruitName,@fruitPrice END CLOSE cursor_variable DEALLOCATE cursor_variable; --3.用ORDER BY子句改变游标中的执行顺序 --声明名称为cursor_order的游标,对fruits表中的记录按照价格字段降序排列,输入语句如下: DECLARE cursor_order CURSOR FOR SELECT f_id,f_name,f_price FROM fruits ORDER BY f_price DESC OPEN cursor_order FETCH NEXT FROM cursor_order WHILE @@FETCH_STATUS=0 FETCH NEXT FROM cursor_order CLOSE cursor_order DEALLOCATE cursor_order; --4.用游标修改数据 --【例】声明整型变量@sid=101,然后声明一个对fruits表进行操作的游标,打开该游标, --使用FETCH NEXT方法来获取游标中的每一行的数据, --如果获取到的记录的s_id的字段值与@sid值相同,将s_id=@sid的记录中的f_price修改为12.2,最后关闭释放游标,输入如下: DECLARE @sid INT,@id INT =101 DECLARE cursor_fruit CURSOR FOR SELECT s_id FROM fruits; OPEN cursor_fruit FETCH NEXT FROM cursor_fruit INTO @sid WHILE @@FETCH_STATUS=0 BEGIN IF @sid=@id BEGIN UPDATE fruits SET f_price=11.1 WHERE s_id=@id END FETCH NEXT FROM cursor_fruit INTO @sid END CLOSE cursor_fruit DEALLOCATE cursor_fruit; SELECT * FROM fruits where s_id=101; --5.使用游标删除数据 --【例】使用游标删除fruits表中s_id=102的记录,如下 DECLARE @sid1 INT,@id1 int=102 DECLARE cursor_delete CURSOR FOR SELECT s_id FROM fruits; OPEN cursor_delete FETCH NEXT FROM cursor_delete INTO @sid1 WHILE @@FETCH_STATUS=0 BEGIN IF @sid1=@id1 BEGIN DELETE FROM fruits where s_id=@id1 END FETCH NEXT FROM cursor_delete INTO @sid1 END CLOSE cursor_delete DEALLOCATE cursor_delete; SELECT * FROM fruits where s_id=102;