MSSQL - Cursor - 커서의 개념, 종류, 구문, 설명, 커서와 쿼리의 차이

반응형

 

해당 커서의 문법은 Transact-SQL 구문 표기 규칙을 참고하여 작성하였습니다.

ANSI 구문을 확인하시려면 맨 아래의 참고 부분의 SQL 문서를 확인해주세요.

커서를 가급적 사용하면 안되지만 불가피하게 사용해야 할 때가 있습니다.

그 때를 위해 학습해둡시다! 😊


커서 (Cursor)

  • SQL문들은 거의가 집합 지향적인 반면 커서는 행 지향적 처리
  • 결과 집합에서 커서를 열면 결과 집합을 한 번에 한 행씩 처리
  • 커서 데이터 형식의 변수 또는 매개 변수에 할당

커서 vs 쿼리

  • 가급적이면 SET 지향의 쿼리로 바꾸는 것이 좋음
  • 서버 커서는 서버의 tempdb에 데이터 셋을 저장하여 서버의 자원을 사용하므로 가급적 사용을 피하는 것이 좋음
  • 커서는 한 행 단위로 읽어오기 때문에 성능이 느림

  • 커서를 사용하지 말아야 하는 경우
    • 정상적인 set oriented 처리로 충분히 처리 가능한 경우
    • 클라이언트에 캐쉬될 정도의 작은 양의 데이터를 처리할 때
    • 저장 프로시저로 처리할 수 있는 경우
  • 커서를 사용해야 하는 경우
    • 동적 쿼리를 사용하여 결과가 매번 다른 값이 출력 될 때
    • 하나의 INSERT, UPDATE, DELETE문으로 집합 지향적 처리를 해야할 때
  • 결론 : 웬만하면 쿼리 사용

커서의 종류 (서버 커서 기준)

Dynamic Cursor

  • 현재 키 값만 tempdb에 저장
  • 다른 행으로 옮겨가면 tempdb는 원래의 테이블로부터 갱신
    • 원 테이블에 대한 모든 수정의 결과가 커서에 반영
  • 데이터가 변화 될 수 있기 때문에 ABSOLUTE 사용 불가
  • 성능면에서는 가장 나쁨

Keyset Cursor

  • 커서 데이터 셋으로부터 모든 키 값만을 tempdb에 저장
  • 포함된 행의 구성원과 순서가 고정
  • 행을 고유하게 식별하는 키 집합(keyset)이 tempdb의 테이블에서 생성
  • 원 테이블의 키가 아닌 값을 변경하면 커서 스크롤 시 변경 사항 표시
    • 다른 사용자가 삽입한 데이터는 확인 불가
  • 키 값 업데이트 시 행 전체를 삭제하고 새 행을 삽입하는 식으로 동작
  • 삭제된 행 호출시 행을 가져온(fetch) 상태를 보여주는 @@FETCH_STATUS 함수는 -2 값을 반환
    • 키 값 업데이트와 삭제의 경우 해당
  • WHERE CURRENT OF 절을 지정하여 커서를 통해 업데이트를 수행하면 새 값 확인 가능

Static Cursor (ANSI 문법 =Insensitive)

  • 전체 커서 데이터 셋을 tempdb에 저장
  • 커서에 대한 모든 요청은 tempdb의 임시 테이블에서 응답
  • 원 테이블에서 처음 한 번만 데이터를 가져온 뒤에는 원 테이블이 수정되어도 커서에 반영되지 않음
  • 성능면에서는 가장 빠름

커서 범주

로컬 커서

  • 커서의 범위가 커서를 선언한 배치, 트리거, 프로시저 안으로 제한
  • 다른 프로시저나 트리거 등에서는 사용 불가

글로벌 커서

  • 명시적으로 DEALLOCATE 시키거나 커넥션을 끊을때만 DEALLOCATE 됨
  • 지정하지 않았을 때의 기본값은 default to local cursor 데이터베이스 옵션에 따라 설정
    • 이 데이터베이스 옵션이 설정되어 있으면 로컬 커서로 지정

커서 기본 문법 (Cursor Syntax)

  • 커서를 만들고 사용 할 때 다섯 단계를 거침
    • DECALRE : 선언
    • OPEN : 커서 열기
    • FETCH : 하나의 행을 가져옴
    • CLOSE : 열었던 커서를 닫기
    • DELLOCATE : 커서와 커서 이름 또는 커서 변수 간의 관계를 제거
      • 커서에서 실행되는 명령문은 커서 이름이나 커서 변수를 사용하여 커서를 참조하기 때문
/* 커서 선언 */
DECLARE 커서이름 CURSOR FOR
    -- SQL Query
    
/* 커서 열기 */
OPEN 커서이름

/* 커서 한 행씩 불러오기 */
FETCH NEXT FROM -- 커서이름
	INTO -- 행의 값 저장 변수
WHILE @@FETCH_STATUS = 0 -- 행을 성공적으로 가져오면 해당 값 출력
BEGIN
	-- 수행할 쿼리 작성
    -- 예시) print(@변수명) : 각 불러온 행의 값 출력
    FETCH NEXT FROM 커서이름
    	INTO -- 행의 값 저장 변수
END

/* 커서 닫기 */
CLOSE 커서이름

/* 커서 자원 할당 해제 */
DEALLOCATE 커서이름

DECLARE

  • 커서를 선언하는 키워드
-- 커서 선언 구문
DECLARE 커서이름 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 ] ] ]
  • LOCAL / GLOBAL
    • 커서의 범주를 로컬 커서로 할지 글로벌 커서로 할지 정의
    • 로컬 커서를 선언하지 않으면 기본적으로 글로벌 커서로 생성
  • FORWARD_ONLY
    • 첫번째 행에서 마지막 행 방향으로만 커서를 스크롤 할 수 있도록 지정
    • 유일하게 FETCH NEXT만 사용 가능
    • STATIC, KEYSET, DYNAMIC 키워드를 사용하지 않고 사용하면 자동적으로 DYNAMIC 커서로 작동
    • .FORWARD_ONLY, SCROLL, STATIC, KEYSET, DYNAMIC 키워드를 하나라도 지정하지 않으면 기본 값은 FORWARD_ONLY
  •  SCROLL
    • 커서의 행을 앞 뒤 마음대로 이동
      • FIRST, LAST, PRIOR, NEXT, RELATIVE, ABSOLUTE
    • 지정하지 않으면 다음 행만 가져옴 (NEXT)
    • STATIC, KEYSET, DYNAMIC 커서의 기본값은 SCROLL 
  • FAST_FORWARD
    • 성능 최적화가 설정된 FORWARD_ONLY + READ_ONLY 커서를 지정
    • SCROLL 또는 FOR UPDATE가 지정된 경우 FAST_FORWARD 지정 불가
      • 커서는 내부에서 데이터 수정을 허용하지 않기 때문
  •  READ_ONLY
    • 읽기 전용으로만 사용
    • UPDATE / DELETE .. WHERE CURRENT OF 절 사용 불가
  • FOR UPDATE / FOR UPDATE OF 컬럼명
    • 모든 컬럼을 수정하거나 특정 컬럼만 수정
  • SCROLL_LOCKS
    • 커서를 통해 현재 위치 업데이트 또는 삭제가 반드시 실행되도록 지정
    • 업데이트나 삭제가 성공하도록 불러오는 행을 잠금을 검
    • FAST_FORWARD가 지정된 경우 SCROLL_LOCKS 지정 불가
  • OPTIMISTIC
    • 커서를 통해 현재 위치 업데이트된 경우 업데이트 또는 삭제가 되지 않도록 지정
    • 업데이트나 삭제가 일어나지 않았기 때문에 행의 잠금을 걸지 않음
      • 대신 timestamp 컬럼 값을 비교하거나 테이블에 timestamp 컬럼이 없을 경우 검사 값을 비교하여 행이 수정되었는지 확인, 행이 수정된 경우 업데이트나 삭제 실패
    • FAST_FORWARD가 지정된 경우 OPTIMISTIC 지정 불가
  • TYPE_WARNING
    • 요청한 커서 형식이 다른 형식으로 암시적으로 변환된 경우 클라이언트에게 경고 메시지 출력

OPEN

  • DECLARE에서 정의한 커서를 여는 키워드
  • 이 때 실제로 tempdb에 데이터 셋이 복사
  • 종류에 따라 복사되는 내용이 다름
    • INSENSITIVE 또는 STATIC 일 때는 임시 테이블을 만들고 전체 결과를 저장
    • KEYSET 일 때는 keyset만 저장
-- 커서를 여는 구문
OPEN { { [ GLOBAL ] 커서이름 } | 커서변수 }

 

-- 열기한 커서가 정상적으로 열렸는지 확인하는 구문
SELECT @@cursor_rows
  • 커서를 열고 @@CURSOR_ROWS 변수를 사용하여 상태를 검사하는 것을 권장
    • 반환값 0 : 열기에 실패, 커서를 열어서 가져온 행이 없음
    • 반환값 양수 : 숫자만큼의 행을 열음
    • 반환값 -1 : 커서는 다이나믹 커서로 열림, 다이나믹의 경우 원본을 가져오므로 행의 개수가 의미 없음
    • 반환값 음수 : 숫자만큼의 행을 비동기적으로 열음, 실제 커서로 인해 열린 행의 개수는 너무 커서 알 수 없고 단지 현재 열린 행의 개수를 의미

FETCH

  • OPEN으로 연 커서에서 하나의 행을 가져오는 키워드
-- 커서 행 불러오는 구문
FETCH   
          [ [ NEXT | PRIOR | FIRST | LAST   
                    | ABSOLUTE { n | @nvar }   
                    | RELATIVE { n | @nvar }   
               ]   
               FROM   
          ]   
{ { [ GLOBAL ] 커서이름 } | @커서변수 }   
[ INTO @커서변수 [ ,...n ] ]
  • NEXT
    • 현재 행 바로 다음의 결과 행을 반환
    • 현재 행을 반환되는 행 앞의 행으로 만듬
    • FETCH NEXT가 첫번째 인출인 경우 결과 집합의 첫번째 행을 반환
    • NEXT는 커서 인출의 기본값
  • PRIOR
    • 현재 행 바로 앞의 결과행을 반환
    • 현재 행을 반한되는 행 뒤에 행으로 만듬
    • FETCH PRIOR가 첫번째 인출인 경우 행이 반환되지 않으며 첫번째 행 앞에 위치
  • FIRST
    • 커서의 첫번째 행을 반환하며 그 행을 현재 행으로 만듬
  • LAST
    • 커서의 마지막 행을 반환하며 그 행을 현재 행으로 만듬
  • RELATIVE {n |@nvar}
    • 설정값(n 또는 @nvar)이 양수인 경우 현재 행에서 위로 n번째 행을 반환하며 그 행을 현재 행으로 만듬
    • 설정값이 음수인 경우 현재 행에서 앞으로 n번째 행을 반환하며 그 행을 현재 행으로 만듬
    • 설정값이 0인 경우 현재 행 반환
    • 설정값이 음수, 0인 커서에 대해 수행되도록 FETCH RELATIVE가 지정된 경우 행을 반환하지 않음
    • n은 정수 상수여야하며, @nvar는 smallint, tinyint, int여야 함
  • FORWARD_ONLY 또는 FAST_FORWARD를 지정하면 유일하게 지원되는 FETCH 옵션은 NEXT
  • DYNAMIC, FORWARD_ONLY 또는 FAST_FORWARD를 지정하지 않고 KEYSET, STATIC, SCROLL 중 하나를 지정하면 모든 FETCH 옵션 지원
  • DYNAMIC SCROLL 커서는 ABSOLUTE를 제외한 모든 FETCH 옵션 지원

 

-- 커서 불러온 행을 검사하는 구문
WHILE (@@fetch_status = 0)
BEGIN
	FETCH NEXT FROM 커서이름
END
  • FETCH 이후 @@FETCH_STATUS 변수를 사용하여 오류가 있는지 점검하는 것을 권장
    • 반환값 0 : 성공적으로 행을 가져옴
    • 반환값 -1 : 실패
    • 반환값 -2 : 삭제된 행이 이제는 더이상 커서 데이터셋에 존재하지 않음, 주로 Keyset 커서에서 발생

CLOSE

  • 열었던 커서를 닫는 키워드
  • 닫았다고해서 완전히 커서가 지워진 것은 아님
  • DEALLOCATE 하기 전에는 다시 OPEN 가능
CLOSE { { [ GLOBAL ] 커서이름} | 커서변수 }

DEALLOCATE

  • 커서와 커서 이름 또는 커서 변수 간의 관계를 제거
  • 커서가 할당 해제되고 사용하던 모든 자원이 해제
  • FETCH를 위해 사용되었던 스크롤의 잠금이 해제
  • 업데이트를 보호하는 트랜잭션 잠금은 트랜잭션이 종료될 때까지 유지
DEALLOCATE { { [ GLOBAL ] 커서이름 } | @커서변수 }

간단 사용 예시

커서를 이용해서 한줄씩 테이블을 읽고 특정 테이블의 값을 출력해보자

-- 커서에서 불러온 정보를 저장할 변수
declare @id

-- 커서 생성
declare c_1 cursor for
	-- 커서가 읽을 테이블
	select id from t1

-- 커서 열기
open c_1

-- 커서를 테이블의 첫행부터 실행
fetch next from c_1
	-- into @ ~ : 커서가 불러온 정보를 저장할 변수
    into @id

-- 커서의 다음 행이 존재할 경우
while @@FETCH_STATUS = 0 begin
    -- 여기에 쿼리 작성
    /* 커서를 통해 한 줄 씩 읽고, 읽은 행의 컬럼(id)를 출력 해보기 */
    print(@id)
    
    -- 커서를 다음행으로 이동
    fetch next from c_1
        into @id
end

close c_1
deallocate c_1

/* print
1
2
3
4
...
*/

 

 


참고

Microsoft - SQL 문서 - 커서

 

반응형