1.     데이터베이스 파일과 로그 파일은 별도의 드라이브로 분리한다.

è  IO를 분산하여 병목현상을 감소시킨다.

 

2.     Ad Hoc 쿼리 (동적 쿼리) : 정적 쿼리로 대체

è  코드의 실행 시점에 SQL 문이 동적으로 구성되고 실행되는 쿼리

è  문제점 : 실행 시마다 컴파일을 반복하게 됨으로 Cache 재사용을 저해함으로써 CPU, Memory 등 여러가지 문제 발생

è  SP의 이점

n  실행 계획 Caching 을 통한 성능 이득

n  Network Traffic 최소화

n  출력 Parameter, Return 값 사용

n  Ownership Chain 을 통한 권한 처리, SQL Injection 차단 등의 보안 기능

n  업무 논리의 캡슐화, 모듈화

n  SQLXML 3.0 이후 릴리스에서 XML WebService 노출 기능

è  SP 안에 동적 쿼리 형태로 작성 하지 않는다.

 

3.     인덱스 생성 Guide

è  where절에서 자주 사용되는 컬럼

è  between A and B (클러스터인덱스가 유리) : 범위 쿼리 문에서는 클러스터 인덱스가

유리 하다.

è  order by가 항상 사용되는 컬럼

è  join으로 자주 사용되는 컬럼

è  100만건 중에 10개 조회 OR 1000개 조회와 같이 찾는 것이 적은 컬럼 : 중복 데이터가 많은 (조회되는 것이 많은) 컬럼은 인덱스에 좋은 영향은 아님.

è  인덱스로 인해 얻는 손해 : H/W 용량 증가, DML(Data Manipulation Language : INSERT, UPDATE, DELETE) Performance 저하 (특히 INSERT)

 

4.     쿼리 Guide

è  WHERE 절에 가공 컬럼 비교 금지 : WHERE REPLACE(USER_ID, ‘ ’, ‘’) = ‘11111’ 와 같이 컬럼을 가공하게 되면 인덱스를 사용하지 못한다. - INSERT USER_ID에는 ‘ ‘ INSERT 되지 않도록 하고, 조건절은 WHERE USER_ID = ‘11111’

è  WHERE 절에서 LIKE 사용 시 ‘%’ 로 시작하는 비교 금지 : NAME LIKE '%홍길동%' 와 같이 LIKE ‘%’로 시작하는 비교는 TABLE SCAN 한다. - 해당 TABLE의 데이터 건수가 소량이고 일정량을 유지한다면 시스템 Capacity내에서 사용 가능하나 일정량의 데이터가 주기적으로 증가하고 해당 쿼리가 빈번히 사용되는 쿼리라면 '%홍길동%' 기능을 '홍길동%' 으로 바꾸던가 시스템 Capacity 를 고려하여 데이터를 삭제하여야 한다.

è  SELECT 절에 사용자 정의 함수 사용 : 무조건 사용하지 않는다. – join 으로 변경

è  SELECT 절에 하위쿼리 사용 : 무조건 사용하지 않는다. – join 으로 변경

è  잠금 문제 : SELECT 쿼리에 with (nolock) 혹은 with (readuncommitted) Hint 사용 – Commit 되지 않은 데이터를 읽어 나중에 없어지거나 변경될 수 있으나 회계와 같은 중요한 데이터가 아니라면 반드시 사용한다.

SQL Server 의 기본 격리수준은 read committed 이다. 그래서 읽을 때는 공유 잠금이 유지되어, 테이블을 SELECT 할 경우 INSERT, UPDATE등은 BLOCK 된다. 이럴 경우 SELECT DB의 성능이 떨어지게 된다.

각 구문에 적용을 해야 하는 불편함을 해결하는 방법은 SP 상단에 아래 문구를 삽입한다.

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

è  Ad Hoc 쿼리 : 정적 쿼리로 변경 (SP)

è  SELECT * 을 사용하는 것은 피한다. : 사용하지 않는 데이터를 호출하는 것만으로도 이미 많은 부하가 생긴다. 특히 text 타입의 데이터를 호출하는 경우는 그 정도가 심해진다. - 필요한 컬럼만 SELECT 한다.

è  COUNT(*)을 사용하라. : COUNT(컬럼)으로 호출하는 경우가 있다. 이 경우 해당 컬럼의 NULL값을 제외한 COUNT를 가져오게 된다. NULL값을 일일이 체크하면 호출 속도가 저하되게 된다. NULL을 체크해야 하는 경우가 아닌 대부분의 경우 COUNT(*)을 사용한다. COUNT(*) NULL값의 경우도 모두 count에 추가하지만 그로 인해 성능의 저하가 많이 줄어든다.

è  1 row 만 필요하다면 TOP 1을 사용한다.

è  커서 및 임시테이블의 내용을 최대한 자제하라. : 커서보다는 임시 테이블이, 임시테이블 보다는 테이블 변수를 사용하는 것이 성능에 좋다.

è  뷰의 사용을 줄여라. : 직접 쿼리가 단계를 줄이므로 가급적 뷰를 사용하지 않는다.

è  JOIN을 사용하는 경우 INNER JOIN을 되도록 사용하라.
.
동일한 효과를 가지는 쿼리를 작성할 경우 INNER JOIN이 아닌 LEFT OUTER JOIN을 쓰는 경우가 있다. (습관적으로?) 확연히 속도가 차이가 나므로 INNER JOIN을 사용하는 것이 좋다.

è  반드시 인덱스를 사용하도록 한다. : 실행 계획을 통해 인덱스의 사용 유무와 올바른 인덱스를 사용하는지 확인한다.

è  SP에서 데이터 형식 우선 순위에 따른 형식 변환이 일어나지 않도록 컬럼 타입과 동일한 타입의 파라미터를 사용 한다.

@searchColumnA NVARCHAR(12)

SELECT * FROM A WITH (READUNCOMMITTED)

WHERE columnA = @searchColumnA

-       columnA 의 타입이 VARCHAR 이면 NVARCHAR 의 우선순위가 높아 아래와 같이 CONVERT 가 발생하여 Performance 를 저하 시킨다.

SELECT * FROM A WITH (READUNCOMMITTED) WHERE CONVERT(NVARCHAR(12), columnA)

è  set nocount on 을 먼저 실행하라 : 불필요한 메시지를 표시하지 않는다.

 

5.     예상 실행 계획을 자주 확인하라.

실행계획의 내용은 꼼꼼히 따져봐야 한다. 튜닝의 시작은 성능 분석이다.

(그래픽 실행 계획 아이콘)

 

6.     Index를 타는지 항상 체크하라.

Index를 활용하지 않은 검색은 데이터가 많으면 많을수록 성능은 급격히 떨어진다.

7.     Clustered Index Seek를 항상 체크하라.

Clustered Index Scan을 타는 것 만으로도 속도는 향상이 되지만 완전하진 않다.
Clustered Index column
의 일정 구간을 타는 Seek여야 한다.

일반 Non Clustered Index의 경우는 Clustered를 찾기 위해 해당 column Clustered Index 정보를 호출해야 하는 부담이 생긴다.

Clustered Index Non Clustered Index Index 구조의 차이 : (클러스터형 인덱스 구조) , (비클러스터형 인덱스 구조)

 

8.     기타…..

è  Index 설정시 DESC 정렬을 해야 빠른가 ?

그렇지 않다.
오름차순이건 내림차순이건 Index가 걸려있으면 조건에 따라 Clustered Index를 찾아 가게 된다.

è  Clustered Index Seek를 타면 무조건 빠른가 ?

그렇지 않다.

è  Non Clustered Index column은 무조건 마지막엔 Clustered Index Column을 조회하는가 ?

그렇지 않다.

è  Clustered Index Table 당 하나만 존재 한다.

è  Table로부터 하나 혹은 소수의 Row들을 리턴하는 SQL Query인 경우에 Nonclustered Index가 적합하고, 많은 수의 Row들을 리턴하는 것이 필요한 Query들의 경우에는 Clustered Index가 더 적합하다.


'DataBase' 카테고리의 다른 글

샘플 재귀 쿼리문  (0) 2010.06.28
Windows Server 2008 R2에서 SQL Server 2008 원격 접속 허용하기  (1) 2010.06.25
SQL index 초기화  (0) 2009.11.25
새로운 SQL 잘라내기 공격 및 대처 방법  (0) 2009.07.06
MS-SQL 트리거  (0) 2009.07.03
WITH DEPTINFO(DeptCode, UpperDeptCode) AS
(         
	SELECT DeptCode, UpperDeptCode
    FROM dbo.tb_DeptList
    WHERE DeptCode ='A001'
    UNION ALL
    SELECT A.DeptCode, A.UpperDeptCode
    FROM DEPTINFO AS A
    JOIN dbo.tb_DeptList AS B
    ON A.DeptCode = B.UpperDeptCode  
)
SELECT DeptCode, UpperDeptCode
FROM DEPTINFO

'DataBase' 카테고리의 다른 글

SQL Query Tip  (0) 2011.02.13
Windows Server 2008 R2에서 SQL Server 2008 원격 접속 허용하기  (1) 2010.06.25
SQL index 초기화  (0) 2009.11.25
새로운 SQL 잘라내기 공격 및 대처 방법  (0) 2009.07.06
MS-SQL 트리거  (0) 2009.07.03

Windows 방화벽은 기본적으로 Windows Server 2008 R2 설치시 대부분의 포트 및 응용 프로그램에 대한 접근을 보안상의 목적으로 막아져 있다.
그러다보니, SQL Server 2008의 원격 접속이 근본적으로 차단되어져 있는 것이다.

이에 Windows Server 2008 R2에 SQL Server 2008의 원격 접속을 허용하는 과정을 아래 단계를 거쳐서 진행하면 된다.

1. Windows Server 2008 R2에서 [시작]-[모든 프로그램]-[관리 도구]-[서버 관리자]를 실행한다.
아래 그림과 같이 구성 항목의 하위 항목인 [고급 보안이 포함된 Windows 방화벽] 항목을 선택하고,
[인바인드 규칙]에 마우스 오른쪽 버튼을 클릭하여 [새 규칙] 메뉴를 클릭한다.
인바인드 규칙은 원격에서 접속하는 포트 및 프로그램에 대한 설정을 하나 추가할 수 있는 마법사를 실행시켜준다.


2. 새 인바운드 규칙 마법사 창이 뜨면 아래에서 두번재 옵션인 [포트] 항목을 선택하고 [다음] 버튼을 클릭한다.


3. 프로토콜 및 포트 설정에서는 TCP와 특정 로컬 포트에 SQL Server에서 사용하는 1433 포트번호를 입력한다.
 


4. 작업항목에서 [연결 허용] 항목을 선택하여, 위에서 지정한 포트를 열어둘 수 있다.


5. 프로필 설정에서는 3가지 옵션이 있는데, 원격 인터넷을 통한 접속을 허용하고자한다면, [공용] 항목을 포함해서 선택하면된다.    


6. 이름란에는 기억하기 편하고, 추후 Windows Firewall에서 손쉽게 규칙을 적용 또는 해제하기 위한 이름과 설명을 입력한다.


7. 인바운드 규칙을 모두 적용하고 나면 아래 화면처럼,
서버관리자의 인바운드 규칙이 새롭게 적용된 [SQL Server 기본 포트]라는 항목이 추가되어져 있는것을 알 수 있다.  
     


이렇게 함으로써,
원격서버에서 Windows Server 2008 R2 기반에 SQL Server 2008에 접속할 수 있는 최소한의 처리 단계를 구축할 수 있다.

'DataBase' 카테고리의 다른 글

SQL Query Tip  (0) 2011.02.13
샘플 재귀 쿼리문  (0) 2010.06.28
SQL index 초기화  (0) 2009.11.25
새로운 SQL 잘라내기 공격 및 대처 방법  (0) 2009.07.06
MS-SQL 트리거  (0) 2009.07.03

SQL index 초기화 쿼리문
DBCC checkident('테이블명', reseed, 0)

새로운 SQL 잘라내기 공격 및 대처 방법
Baka Neerumalla

SQL 주입을 이용하는 공격은 방화벽과 침입 검색 시스템을 통과해서 데이터 계층을 손상시킬 수 있다는 점 때문에 많은 관심을 끌었습니다.

기본 코드 패턴을 보면 1차 또는 2차 주입 모두 문을 생성할 때 신뢰할 수 없는 데이터를 사용한 경우에 발생하는 다른 주입 문제와 비슷합니다.
대부분의 개발자는 백 엔드에서 매개 변수가 있는 SQL 쿼리를 저장 프로시저와 함께 사용하여 웹 프런트 엔드의 취약점을 완화하고 있지만, 사용자 입력 기반의 DDL(데이터 정의 언어) 문을 생성하는 경우 또는 C/C++로 작성된 응용 프로그램의 경우에는 동적으로 생성된 SQL을 여전히 사용하고 있습니다.
이 기사에서는 구분 문자가 이스케이프된 코드일지라도 SQL 문을 수정하거나 SQL 코드를 주입할 수 있는 몇 가지 새로운 아이디어에 대해 설명합니다.
구분 식별자SQL 리터럴을 생성하는 유용한 방법을 몇 가지 살펴본 다음 응용 프로그램을 보호하는 데 도움이 될 수 있도록 공격자가 SQL 코드를 삽입할 때 사용하는 새로운 방법을 설명합니다.


 

식별자 및 문자열 구분
SQL Server™에는 테이블, 뷰 및 저장 프로시저와 같은 SQL 개체를 고유하게 식별하는 SQL 식별자데이터를 나타내는 리터럴 문자열이라는 두 가지 문자열 변수가 있습니다.
SQL 식별자를 구분하는 방법은 데이터 문자열을 구분하는 방법과는 다릅니다.
이러한 데이터 변수를 사용해야 하는 동적 SQL을 생성하는 유용한 방법을 살펴보겠습니다.
SQL 개체 이름에 키워드가 사용되거나 개체 이름에 특수 문자가 들어 있는 경우 구분 식별자를 사용해야 합니다.
my_dbreader라는 이름의 로그인을 삭제한다고 가정해 봅시다.
이 경우 다음 문을 실행하여 작업을 수행할 수 있습니다.
 
DROP LOGIN my_dbreader
 
키워드이기도 한 DROP을 이름으로 사용하는 로그인을 삭제하려는 경우에는 어떻게 해야 합니까?
다음 SQL 문을 사용하면 SQL Server에서 잘못된 구문 오류가 반환됩니다.
 
DROP LOGIN DROP
 
my][dbreader라는 이름의 로그인을 삭제하려는 경우에는 어떻게 합니까?
이 경우에도 잘못된 구문 오류가 반환됩니다.
두 예제 모두 로그인 이름이 키워드이거나 로그인 이름에 특수 문자가 들어 있기 때문에 SQL Server에서 SQL 문에 있는 개체 이름을 식별할 수 있도록 시작 및 끝 표시를 입력해야 합니다.
큰따옴표대괄호를 SQL 식별자의 구분 기호로 사용할 수 있지만 연결 기반 설정인 QUOTED_IDENTIFIER 설정을 사용하도록 설정한 경우에는 큰따옴표만 사용할 수 있습니다.
복잡하지 않도록 하기 위해 항상 대괄호를 사용하는 것도 좋은 방법입니다.
로그인 이름인 DROP을 삭제하기 위해 다음과 같이 대괄호를 사용하여 SQL 문을 생성할 수 있습니다.
 
DROP LOGIN [DROP]
 
그러나 다음 문은 어떻게 처리되겠습니까?
 
DROP LOGIN [my][dbreader]
 
이와 같은 특별한 경우에는 로그인 이름인 my][dbreader에 구분 문자가 들어 있으므로 SQL에서는 대괄호로 둘러싸인 [my]를 로그인 이름으로 간주합니다.
로그인 이름 뒤에 오는 [dbreader]는 올바른 SQL 문이 아니기 때문에 구문 오류가 발생합니다.
오른쪽 대괄호를 하나 더 사용하여 오른쪽 대괄호를 이스케이프하면 이 문제를 해결할 수 있습니다.
따라서 다음 문을 실행하면 SQL Server에서 로그인 my][dbreader가 삭제됩니다.
 
DROP LOGIN [my]][dbreader]
 
이스케이프 메커니즘은 간단히 오른쪽 대괄호를 두 번 표시하는 것입니다.
왼쪽 대괄호를 포함한 다른 어떤 문자도 변경할 필요가 없습니다.
구분 리터럴을 사용하는 것은 구분 SQL 식별자를 사용하는 것과 비슷하지만 사용해야 하는 구분 문자가 다르다는 데 기본적으로 차이가 있습니다.
비슷한 규칙을 사용하여 구분 문자열 리터럴을 만들기 전에 몇 가지 예제를 살펴보겠습니다.
암호가 P@$$w0rd인 로그인 이름 dbreader를 만든다고 가정해 봅시다.
이 경우 다음 SQL 문을 사용할 수 있습니다.
 
CREATE LOGIN [dbreader] WITH PASSWORD = 'P@$$w0rd'
 
이 문에서 P@$$w0rd는 작은따옴표로 구분된 문자열 데이터이므로 SQL에서 문자열의 시작과 끝이 인식됩니다.
그러나 문자열 데이터에 작은따옴표가 들어 있다면 어떻게 되겠습니까?
문이 유효하지 않기 때문에 SQL Server에서 오류가 발생합니다.
 
CREATE LOGIN [dbreader] WITH PASSWORD = 'P@$$'w0rd'
 
유효한 SQL 문을 만들려면 문자열에 있는 모든 작은따옴표를 이스케이프해야 합니다.
 
CREATE LOGIN [dbreader] WITH PASSWORD = 'P@$$''w0rd'
 
이 문을 실행하면 SQL Server에서 암호가 P@$$'w0rd인 로그인 dbreader가 만들어집니다.
큰따옴표를 구분 기호로 사용할 수도 있지만 앞에서 설명했듯이 이 방법의 성공 여부는 전적으로 QUOTED_IDENTIFIER 설정의 사용 여부에 달려 있습니다.
결과적으로 항상 작은따옴표를 문자열 리터럴의 구분 기호로 사용하는 것이 좋습니다.


 

T-SQL 함수
지금까지 살펴본 것처럼 식별자문자열을 다루는 규칙은 비교적 간단하며 문자열을 미리 알고 있으면 수동으로 구분할 수 있습니다.
그러나 사용자 입력 기반의 동적 T-SQL 문을 생성할 경우에는 어떻겠습니까?
자동으로 이 작업을 수행할 수 있는 방법이 있어야 합니다.
구분 문자열을 준비하는 데 도움이 되는 QUOTENAMEREPLACE라는 2개의 T-SQL 함수를 사용할 수 있습니다.
QUOTENAME입력 문자열을 유효한 식별자로 만들기 위해 구분 기호가 추가된 유니코드 문자열을 반환합니다.
QUOTENAME 함수는 다음 구문을 사용합니다.
 
QUOTENAME ( 'string' [ , 'delimiter' ] )
 
QUOTENAME의 인수는 구분할 문자열구분 기호로 사용할 한 문자로 된 문자열입니다.
구분 기호로 대괄호, 작은따옴표 또는 큰따옴표를 사용할 수 있습니다.
이 함수는 주로 구분 SQL 식별자를 사용하기 위한 것이므로 SQL Server에서 nvarchar(128) 형식인 sysname만 받습니다.
또한 이 함수를 사용하여 구분 SQL 리터럴 문자열을 준비할 수 있지만 인수 길이 제한 때문에 128자 이하의 문자열에만 사용할 수 있습니다.
즉, 이러한 제한 때문에 REPLACE 함수를 사용하게 됩니다.
그림 1에서는 sp_addlogin이 QUOTENAME을 사용하여 구분된 로그인 이름 및 암호 문자열을 만드는 방법을 보여 줍니다.
그림에서 볼 수 있듯이 @loginname과 @passwd가 모두 sysname이므로 QUOTENAME 함수를 사용하여 구분 SQL 식별자와 구분 리터럴을 준비할 수 있습니다.
따라서 @loginname = 'my[]dbreader'와 @passwd = 'P@$$''w0rd'가 전달된다고 하더라도 QUOTENAME이 구분 문자를 올바르게 이스케이프하므로 SQL 주입 기회가 발생하지 않습니다.
 
create login [my[]]dbreader] with password = 'P@$$''w0rd' 
 
Figure 1 QUOTENAME으로 문자열 구분
create procedure sys.sp_addlogin
    @loginame       sysname
   ,@passwd         sysname = Null
   ,@defdb          sysname = ‘master’     
   ,@deflanguage    sysname = Null
   ,@sid            varbinary(16) = Null
   ,@encryptopt     varchar(20) = Null
AS
    -- SETUP RUNTIME OPTIONS / DECLARE VARIABLES --
    -- some code ----
    set @exec_stmt = ‘create login ‘ + quotename(@loginame, ‘[‘)

    if @passwd is null
        select @passwd = ‘‘

    if (@encryptopt is null)
        set @exec_stmt = @exec_stmt + ‘ with password = ‘ +
            quotename(@passwd, ‘‘‘‘)
    else
    -- some code
GO
 
REPLACE 함수는 지정된 문자열을 모두 지정된 대체 문자열로 바꿉니다.
QUOTENAME과는 달리 받는 인수에 대한 길이 제한은 없습니다.
 
REPLACE ( 'string1' , 'string2' , 'string3' )
 
REPLACE는 3개의 문자열을 받습니다.
즉, string1은 편집할 식이고 string2는 바꿔야 하는 string1 내의 항목이며 string3은 string2 대신 사용할 항목입니다.
모든 문자열 식은 문자 또는 이진 데이터일 수 있습니다.
구분 SQL 리터럴을 준비하기 위해 REPLACE를 사용하여 작은따옴표 수를 2배로 만들 수 있습니다.
그러나 이 경우 시작 및 끝 작은따옴표를 사용하는 것처럼 구분 기호를 수동으로 추가해야 합니다.
그림 2에서는 sp_attach_single_file_db에서 이 함수를 사용하여 이스케이프된 물리적 파일 이름을 준비하는 방법을 보여 줍니다. @physname은 nvarchar(260)이므로 QUOTENAME을 구분 리터럴을 준비하는 데 사용할 수 없습니다.
바로 이 점 때문에 REPLACE를 사용하는 것입니다.
따라서 작은따옴표가 있는 문자열을 전달한다고 하더라도 SQL 문을 수정하거나 SQL 코드를 주입할 수 없습니다.
이제 현재 암호의 유효성을 확인한 후 사용자 계정의 암호를 변경하는 저장 프로시저를 살펴보겠습니다(그림 3 참조).
CREATE PROCEDURE sp_setPassword
    @username varchar(25),
    @old varchar(25),
    @new varchar(25)
AS

DECLARE @command varchar(100)

SET @command=
    ‘update Users set password=‘‘‘ + @new +
    ‘‘‘ where username=‘‘‘ + @username +
    ‘‘‘ AND password=‘‘‘ + @old + ‘‘‘‘

EXEC (@command)
GO
 
저장 프로시저를 간단하게 살펴보면 작은따옴표에 대해 이스케이프된 매개 변수가 없어서 SQL 주입 공격에 취약하다는 것을 알 수 있습니다.
공격자는 몇 가지 특정 인수를 전달하여 SQL 문을 다음과 같이 수정할 수 있습니다.
 
update Users set password='NewP@ssw0rd'
where username='admin' --' and password='dummy'
 
결과적으로 실제 암호가 없어도 admin 계정(또는 알려진 계정)의 암호가 설정됩니다.
T-SQL에서는 REPLACE 또는 QUOTENAME을 사용하여 이 코드를 수정할 수 있습니다.
그림 4에서는 REPLACE 함수를 사용하여 수정한 코드를 보여 줍니다.
CREATE PROCEDURE sp_setPassword
    @username varchar(25),
    @old varchar(25),
    @new varchar(25)
AS

-- Declare variables.
DECLARE @command varchar(100)

-- Construct the dynamic SQL
SET @command=
    ‘update Users set password=‘‘‘ + REPLACE(@new, ‘‘‘‘, ‘‘‘‘‘‘) + ‘‘‘‘ +
    ‘ where username=‘‘‘ + REPLACE(@username, ‘‘‘‘, ‘‘‘‘‘‘) + ‘‘‘‘ +
    ‘ AND password = ‘‘‘ + REPLACE(@old, ‘‘‘‘, ‘‘‘‘‘‘) + ‘‘‘‘

-- Execute the command.
EXEC (@command)
GO
그림에서 볼 수 있는 것처럼 REPLACE는 매개 변수에 있는 모든 작은따옴표 수를 2배로 만듭니다.
따라서 공격자가 동일한 인수를 전달하는 경우 다음과 같은 문이 만들어집니다.
 
update Users set password='NewP@ssw0rd'
where username='admin''--' and password='dummy'
 
따라서 일반적인 SQL 주입 문제에 취약하지 않게 됩니다.


 

잘라내기를 통한 수정
앞에서 본 저장 프로시저를 자세히 살펴보면 @command 변수의 길이 제한이 100자임을 알 수 있습니다.
그러나 25자로 된 각 변수에 대한 REPLACE 함수는 모든 문자가 작은따옴표일 경우 50자를 반환할 수 있습니다.
SQL Server 2000 SP4 및 SQL Server 2005 SP1에서는 변수의 버퍼가 충분하지 않으면 데이터가 자동으로 잘립니다.
공격자는 이러한 기회를 틈타 명령 문자열을 자를 수 있습니다.
이 예제에서 누군가가 username='username' 식의 바로 뒤에 있는 명령을 자를 수 있다면 알려진 사용자 계정의 현재 암호를 알지 못하더라도 해당 암호를 변경할 수 있습니다.
웹 응용 프로그램에 administrator라는 이름의 사용자가 있다는 것을 공격자가 알고 있다고 가정합니다.
이 경우 모든 사용자 계정이 대상이 될 수 있습니다.
명령이 너무 길어서 적절하게 잘리도록 하기 위해서는 공격자가 41자 길이의 새 암호를 제공해야 합니다.
즉, 전체 길이가 100자인 명령에서 27자는 update 문에 사용되고 17자는 where 절에 사용되고 13자는 "administrator"에 사용되며 2자는 새 암호를 둘러싸는 작은따옴표에 사용되기 때문에 암호에는 41자가 필요합니다.
공격자는 새 암호로 25자만 전달할 수 있습니다.
그러나 REPLACE 함수에 의해 2배가 되는 작은따옴표를 전달하면 이 문제를 해결할 수 있습니다.
따라서 공격자는 작은따옴표 18개, 대문자 1개, 기호 1개 및 소문자 2개를 전달하여 where username='administrator' 식의 바로 뒤에 있는 명령을 자를 수 있습니다.
공격자가 @new 매개 변수에 대해 ''''''''''''''''''!Abb1을 전달하고 username 매개 변수에 대해 administrator를 전달하면 @command는 다음과 같이 됩니다.
 
update Users set password=
'''''''''''''''''''''''''''''''''''''!Abb1' where username='administrator'
 
그림 5에서는 REPLACE 대신 QUOTENAME을 사용합니다.
이전 예제에서는 개발자가 사용자 이름, 새 암호 및 기존 암호에 대해 작은따옴표를 구분 문자로 추가하지만 이 예제에서는 QUOTENAME 함수를 사용해서 작은따옴표를 추가한다는 점이 이전 예제와 이 예제의 유일한 차이점입니다.
사용자가 제공한 데이터는 변경되지 않기 때문에 이전 예제에 사용한 것과 동일한 공격 문자열을 이 예제에 사용할 수 있습니다.
그림 6에서는 동일한 기능을 수행하는 중간 계층 응용 프로그램에서 작성된 간략한 C/C++ 함수 버전을 보여 줍니다.
이 함수도 동일한 공격에 취약합니다.
DWORD ChangePassword(char* psUserName, char* psOld, char* psNew)
{
    char* psEscapedUserName = NULL;
    char* psEscapedOldPW = NULL;
    char* psEscapedNewPW = NULL;
    char szSQLCommand[100];
    HRESULT hr=0;
   
    // Input Validation
    ...

    // Calculate and allocate the new buffer with length
    // userdatalen*2 + 1
    // Escape all single quotes with double quotes
    ...

    //Construct the query
    hr = StringCchPrintf(szSQLCommand, sizeof(szSQLCommand)/sizeof(char),
        "Update Users set password=‘%s’ where username=‘%s’"
        "AND password=‘%s’,
        psEscapedNewPW, psEscapedUserName, psEscapedOldPW);
   
    if (S_OK != hr)
    {
        // handle error cases
    }

    // Execute and return
}

CREATE PROCEDURE sp_setPassword
    @username varchar(25),
    @old varchar(25),
    @new varchar(25)
AS
-- Declare variables.
DECLARE @command varchar(100)

-- In the following statement, we will need 43 characters
-- to set an administrator password without knowing its current password.
-- 100 - 26 - 16 - 15 = 43 (26 for update stmt, 16 for where clause,
-- 15 for ‘administrator’). But @new only takes 25 characters, which we
-- can get around by using single quotes. So one can pass the following
-- parametes and set admin password. @new = 18 single quotes, 1 Capital
-- letter, 1 symbol, 2 small case letters, 1 digit
-- @username = administrator
-- @command becomes
-- update Users set password=‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘‘!Abb1’
-- where username=‘administrator’
SET @command= ‘update Users set password=‘ + QUOTENAME(@new,’’’’) +
‘ where username=‘ + QUOTENAME(@username,’’’’) + ‘ AND password = ‘ + QUOTENAME(@old,’’’’)

-- Execute the command.
EXEC (@command)
GO


 

잘라내기를 통한 SQL 주입
그림 7에서는 개별 변수를 고정적으로 사용하는 동일한 코드의 여러 변형을 보여 줍니다.
이 코드에서는 이스케이프된 문자열을 개별 변수에 저장하고 @command의 버퍼는 전체 문자열을 저장할 수 있을 정도로 큽니다. @escaped_username, @escaped_oldpw 및 @escaped_newpw는 varchar(25)로 선언되었지만 @username, @old 및 @new의 모든 문자가 25개의 작은따옴표 문자일 경우 50자를 저장해야 합니다.
이 경우 이스케이프된 문자로 구성된 문자열이 잘릴 가능성이 있습니다.
CREATE PROCEDURE sp_setPassword
    @username varchar(25),
    @old varchar(25),
    @new varchar(25)
AS
-- Declare variables.
DECLARE @escaped_username varchar(25)
DECLARE @escaped_oldpw varchar(25)
DECLARE @escaped_newpw varchar(25)
DECLARE @command varchar(250)

SET @escaped_username = REPLACE(@username, ‘‘‘‘, ‘‘‘‘‘‘)
SET @escaped_oldpw = REPLACE(@old, ‘‘‘‘, ‘‘‘‘‘‘)
SET @escaped_newpw = REPLACE(@new, ‘‘‘‘, ‘‘‘‘‘‘)

SET @command =
    ‘update Users set password=‘‘‘ + @escaped_newpw + ‘‘‘‘ +
    ‘ where username=‘‘‘ + @escaped_username + ‘‘‘‘ +
    ‘ AND password = ‘‘‘ + @escaped_oldpw + ‘‘‘‘
EXEC (@command)
GO
 
공격자는 123...n'(여기서 n은 24번째 문자)을 새 암호로 제공하고 @escaped_newpw를 123...n'으로 만든 후(REPLACE 함수에서 반환된 두 번째 작은따옴표 문자가 잘림) 다음과 같은 마지막 쿼리를 만들 수 있습니다.
이 경우 공격자는 username 필드를 통해 코드를 주입하여 공격할 수 있습니다.
 
update users set password='123...n''
where username='<SQL Injection here using Username>
 
이 코드 패턴의 경우 기존 SQL을 단순히 자르는 것에 그치지 않고 SQL 코드를 삽입할 가능성이 열려 있기 때문에 더 위험합니다.
그림 8에서는 REPLACE 대신 QUOTENAME 함수를 사용하는 동일한 코드의 변형을 예제로 보여 줍니다.
QUOTENAME은 구분 기호를 추가하므로 페이로드는 다르지만 여전히 SQL 주입 공격에는 취약합니다.
ALTER PROCEDURE sp_setPassword
    @username varchar(25),
    @old varchar(25),
    @new varchar(25)
AS
-- Declare variables.
DECLARE @quoted_username varchar(25)
DECLARE @quoted_oldpw varchar(25)
DECLARE @quoted_newpw varchar(25)
DECLARE @command varchar(250)

-- In the following statements, all the variables can only hold
-- 25 characters, but quotename() will return 52 characters when all
-- the characters are single quotes.
SET @quoted_username = QUOTENAME(@username, ‘‘‘‘)
SET @quoted_oldpw = QUOTENAME(@old, ‘‘‘‘)
SET @quoted_newpw = QUOTENAME(@new, ‘‘‘‘)

-- By passing the new password as 123...n where n is 24th character,
-- @quoted_newpw becomes ‘123..n
-- Observe carefully that there is no trailing single quote as it gets
-- truncated.
-- So the final query becomes something like this
-- update users set password=‘123...n where username=‘ <SQL Injection
-- here using Username>
SET @command= ‘update Users set password=‘ + @quoted_newpw +
              ‘ where username=‘ + @quoted_username +
              ‘ AND password = ‘ + @quoted_oldpw
EXEC (@command)
GO
 
이 코드에서는 구분 문자열을 개별 변수에 저장하고 @command의 버퍼는 전체 명령 문자열을 저장할 수 있을 정도로 큽니다.
이전 예제와 마찬가지로 따옴표 붙은 변수 @quoted_username, @quoted_oldpw 및 @quoted_newpw에 문제가 있습니다.
이러한 변수는 varchar(25)로 선언되었지만 @username, @old 및 @new의 모든 문자가 25개의 작은따옴표 문자일 경우 52자가 필요합니다.
또한 QUOTENAME은 시작 및 끝 구분 기호를 추가합니다.
이 경우 공격자는 구분 문자로 구성된 문자열을 자를 기회를 얻게 됩니다.
공격자는 123...n(여기서 n은 24번째 문자)을 새 암호로 제공하고 @escaped_newpw를 '123...n으로 만든 후(QUOTENAME 함수가 시작 작은따옴표를 추가함) 다음과 같은 마지막 쿼리를 만들 수 있습니다.
이 경우 공격자는 username 필드를 통해 코드를 주입하여 공격할 수 있습니다.
 
update users set
password='123...n where
username=' <SQL Injection here using Username>
 
그림 9에서는 이 코드와 동일한 기능을 수행하면서 C/C++로 작성된 간단한 코드를 보여 줍니다.
이 코드 또한 동일한 공격 방식에 취약합니다.
 
DWORD ChangePassword(char* psUserName, char* psOld, char* psNew)
{
    char szEscapedUserName[26];
    char szEscapedOldPW[26];
    char szEscapedNewPW[26];
    char szSQLCommand[250];
   
    // Input Validation

    // Escape User supplied data
    Replace(psUserName, "’", "’’", szEscapedUserName,
            sizeof(szEscapedUserName));
    Replace(psPassword, "’", "’’", szEscapedOldPW,
            sizeof(szEscapedOldPW));
    Replace(psPassword, "’", "’’", szEscapedNewPW,
            sizeof(szEscapedNewPW));
   
    // Construct the query
    StringCchPrintf(szSQLCommand, sizeof(szSQLCommand)/sizeof(char),
        "Update Users set password=‘%s’ where username=‘%s’"
        "AND password=‘%s’,
        szEscapedNewPW, szEscapedUserName,szEscapedOldPW);

    // Execute and return
}
 
여기에서는 설명을 위해 T-SQL 코드를 사용하기는 했지만 실제로는 DML(데이터 조작 언어) 코드가 포함된 대부분의 응용 프로그램이 이러한 문제에 취약하지 않으므로 DML 문에 동적 SQL을 사용할 필요는 없습니다.
그림 10에서는 사용자 입력에 따라 동적 DDL 문을 생성하는 예제를 보여 줍니다.
앞서 살펴본 다른 예제와 마찬가지로 다음 문에는 잘라내기 문제가 있습니다.
 
set @escaped_oldpw = quotename(@old, '''')
set @escaped_newpw = quotename(@new, '''')
 
공격자는 @new = '123...'을 전달하여 이러한 문을 공격할 수 있습니다.
여기서 127번째 문자(작은따옴표 아님)는 @old = '; SQL Injection'을 시작하고 다음과 같은 SQL 문이 만들어집니다.
 
alter login [loginname]
with password = '123... old_password = '; SQL
Injection
 
create procedure sys.sp_password
    @old sysname = NULL,        -- the old (current) password
    @new sysname,               -- the new password
    @loginame sysname = NULL    -- user to change password on
as
    -- SETUP RUNTIME OPTIONS / DECLARE VARIABLES --
    set nocount on
    declare @exec_stmt nvarchar(4000)
    declare @escaped_oldpw sysname
    declare @escaped_newpw sysname

    set @escaped_oldpw = quotename(@old, ‘‘‘‘)
    set @escaped_newpw = quotename(@new, ‘‘‘‘)

    set @exec_stmt = ‘alter login ‘ + quotename(@loginame) +
        ‘ with password = ‘ + @escaped_newpw + ‘ old_password = ‘ +
        @escaped_old

    exec (@exec_stmt)

    if @@error <> 0
        return (1)

    -- RETURN SUCCESS --
    return  (0)    -- sp_password
 
저장 프로시저에서 이러한 문제가 쉽게 발생할 수 있는 것처럼 보이기는 하지만 모든 저장 프로시저가 보안에 취약한 것은 아닙니다.
다음 내용을 주의 깊게 검토할 필요가 있습니다.
SQL Server에서는 모든 저장 프로시저가 기본적으로 호출자의 컨텍스트에서 실행됩니다.
따라서 프로시저에 SQL 주입 문제가 있다고 하더라도 프로시저에 대한 실행 권한을 갖고 있는 악의적 로컬 사용자가 자신의 권한을 높일 수 없기 때문에 주입된 코드는 해당 사용자의 컨텍스트에서 실행됩니다.
그러나 EXECUTE AS 기능을 통해 소유자 또는 다른 특정 사용자가 실행할 수 있는 내부 유지 관리 스크립트가 있는 경우에는 호출자가 다른 사용자 컨텍스트에서 코드를 실행하여 호출자의 권한을 해당 사용자의 권한으로 높일 수 있습니다.
모든 잘라내기 문제는 명백히 버그이지만 반드시 보안 취약점이라고 할 수는 없습니다.
그러나 향후 누가 이러한 문제점을 발견하여 악용할 가능성은 있기 때문에 문제를 해결해 두는 것이 좋습니다.
SQL 코드의 주입 취약점을 완화하기 위해 취할 수 있는 다른 방법이 있습니다.
첫 번째는 저장 프로시저에서 DML 문에 대해 동적 SQL을 사용하지 않는 것입니다.
불가피하게 동적 SQL을 사용해야 한다면 sp_executesql을 사용하십시오.
두 번째는 이 기사의 예제에서 설명한 것처럼 버퍼 길이를 올바르게 계산해야 합니다.
마지막으로 C/C++ 코드의 경우 문자열 연산 반환 값을 확인하여 문자열이 잘렸는지 여부를 확인합니다.
문자열이 잘렸으면 실패한 것입니다.
취할 수 있는 단계에 대한 요약을 보려면 "취약점 검색 방법" 보충 기사를 참조하십시오.


 

잘라내기를 통한 주입 검색
잘라내기를 이용한 SQL 주입 문제를 자동화된 도구로 검색하려면 잘라내기 공격의 가능성을 남기는 모든 코드 패턴을 잘 파악하고 있어야 합니다.
서로 다른 문자열 데이터를 사용하여 개별 특정 코드 패턴에 적용할 수 있습니다.
다음 시나리오에서는 n입력 버퍼의 길이라고 가정합니다.
QUOTENAME 구분 문제를 검색하기 위해 먼저 QUOTENAME(또는 C/C++ 응용 프로그램의 경우 비슷한 함수)을 사용해서 구분 식별자 또는 리터럴을 준비했고, 구분된 문자열 버퍼 크기가 2*n + 2보다 작다고 가정합니다.
구분된 문자열 버퍼 길이가 n일 경우 이러한 문제를 검색하려면 비구분 문자로 구성된 긴 문자열을 전달합니다.
후행 구분 기호는 잘리고 다른 입력 변수를 사용하여 주입할 수 있는 기회가 생깁니다.
구분된 버퍼 길이가 홀수일 때 이러한 문제를 검색하려면 작은따옴표(또는 오른쪽 대괄호나 큰따옴표) 문자로 구성된 긴 문자열을 전달합니다.
QUOTENAME은 모든 구분 기호 수를 2배로 만들고 시작 구분 문자를 추가하는 반면, 이스케이프된 문자열 버퍼에는 홀수 개의 문자만을 저장할 수 있으므로 후행 구분 기호는 잘립니다.
구분된 버퍼 길이가 짝수일 때 이러한 문제를 검색하려면 1', 1'', 1''', 1''''과 같이 각 반복에 대해 작은따옴표(또는 오른쪽 대괄호)의 수를 늘리는 방식으로 문자열을 전달합니다.
QUOTENAME은 모든 작은따옴표 수를 2배로 만들기 때문에 시작 구분 기호와 1이 포함된 짝수 개의 작은따옴표가 들어 있는 문자열이 반환되어 짝수 개의 문자를 받게 됩니다.
결과적으로 후행 구분 기호는 잘립니다.
또한 REPLACE(또는 C/C++ 응용 프로그램의 경우 유사한 함수)를 사용하여 이스케이프된 문자열을 준비하고 이스케이프된 문자열 버퍼 크기가 2*n보다 작은 경우에도 이러한 문제를 검색할 수 있습니다.
이스케이프된 문자열 버퍼 길이가 n과 같을 때 이러한 문제를 검색하려면 1', 12', 123' 및 123...n'과 같이 각 반복에 대해 입력 문자열의 길이를 늘리는 방식으로 문자열을 전달합니다.
이 경우 올바른 길이를 입력하면 REPLACE 함수에 의해 마지막 작은따옴표 문자가 2배로 됩니다.
이스케이프된 문자열 변수에는 충분한 버퍼 공간이 없으므로 마지막 작은따옴표가 잘린 채로 저장되어 전달되기 때문에 SQL 문을 수정할 수 있는 기회가 발생합니다.
이스케이프된 버퍼 길이가 홀수일 때 REPLACE를 사용하여 이러한 문제를 검색하려면 ', '', ''' 및 ''''...'와 같이 길이가 길어지는 작은따옴표 문자로 구성된 문자열을 전달하거나 작은따옴표 문자로 구성된 긴 문자열을 전달합니다.
이 경우 REPLACE는 모든 작은따옴표 수를 2배로 만듭니다.
그러나 버퍼 크기가 홀수이기 때문에 마지막 작은따옴표가 잘리면서 SQL 문을 수정할 수 있는 기회가 발생합니다.
이스케이프된 버퍼 길이가 짝수일 때 이러한 문제를 검색하려면 1', 1'', 1''', 1''''과 같이 각 반복에 대해 작은따옴표(또는 오른쪽 대괄호)의 수를 늘리는 방식으로 문자열을 전달합니다.
맨 앞의 1을 제외한 반환 값에는 짝수 개의 문자가 포함되므로 전체 반환 값의 문자 수는 홀수가 됩니다.
그러나 버퍼 길이가 짝수이기 때문에 후행 작은따옴표가 잘리면서 SQL 문을 수정할 수 있는 기회가 발생합니다.
 
 
취약점 검색 방법

코드 검토 사용
코드 검토를 수행할 때 다음과 같은 방법을 사용하여 SQL 문의 문제를 검색할 수 있습니다.

1차 또는 2차 SQL 주입 검색
  • 동적 SQL 문을 실행하는 데 사용한 API를 확인합니다.
  • 동적 SQL 문에 사용된 데이터에 대해 데이터 유효성 검사가 수행되었는지 검토합니다.
  • 데이터 유효성 검사가 수행되지 않은 경우 데이터의 구분 문자(문자열 리터럴의 경우 작은따옴표, SQL 식별자의 경우 오른쪽 대괄호)가 이스케이프되었는지 검토합니다.

잘라내기를 통한 SQL 수정 문제 검색
  • 마지막 동적 SQL 문을 저장하는 데 사용된 버퍼 길이를 검토합니다.
  • 입력이 최대값을 초과하고 SQL 문을 저장하는 데 사용할 버퍼가 충분히 큰 경우 SQL 문을 저장하는 데 필요한 최대 버퍼를 계산합니다.
  • QUOTENAME 또는 REPLACE 함수의 반환 값에 특히 주의합니다.
    이러한 함수는 입력 데이터의 길이가 n자일 때 모든 입력 문자가 구분 문자이면 2*n + 2 또는 2*n을 반환합니다.
  • C/C++ 응용 프로그램의 경우 SQL 문을 준비하는 데 사용한 StringCchPrintf와 같은 API의 반환 값에 대해 버퍼 부족 오류가 발견되었는지 확인합니다.

잘라내기를 통한 SQL 주입 문제 검색
  • 구분 문자열 또는 이스케이프된 문자열을 저장하는 데 사용된 버퍼 길이를 검토합니다.
  • n이 입력 문자열의 길이이면 QUOTENAME의 반환 값을 저장하는 데 2*n + 2가 필요하고 REPLACE의 반환 값을 저장하는 데 2*n이 필요합니다.
  • C/C++ 응용 프로그램의 경우 REPLACE에 상응하는 함수의 반환 값에 대해 버퍼 부족 오류가 발견되었는지 확인합니다.

블랙 박스 메서드 사용
자동화 도구나 지능형 퍼저(Fuzzer)가 있는 경우에는 다음과 같은 방법을 사용하여 SQL 문의 문제를 검색할 수 있습니다.

SQL 주입 문제 검색
  • 작은따옴표를 입력 데이터로 보내서 사용자 입력이 동적 SQL 문에서 문자열 리터럴로 관리 및 사용되지 않는 경우를 검색합니다.
  • 오른쪽 대괄호(] 문자)를 입력 데이터로 사용하여 정리되지 않은 사용자 입력이 SQL 식별자의 일부분으로 사용되는 경우를 검색합니다.

잘라내기 문제 검색
  • 버퍼 오버런 검색을 위해 문자열을 보내는 것처럼 긴 문자열을 보냅니다.

잘라내기를 통한 SQL 수정 문제 검색
  • 작은따옴표 문자(또는 오른쪽 대괄호나 큰따옴표)로 구성된 긴 문자열을 보냅니다.
    이 경우 REPLACEQUOTENAME 함수의 반환 값이 최대값을 초과하게 되어 SQL 문을 저장하는 데 사용되는 명령 변수가 잘릴 수 있습니다.

 

 

 

Bala Neerumalla는 Microsoft의 보안 소프트웨어 개발자이며 응용 프로그램 보안 취약점 조사를 전문적으로 담당하고 있습니다.

 

### 출처 : MSDN Magazine / 저작권자 : Microsoft Corporation ###

 

MS-SQL 트리거(Trigger)에 대해서 간단하게 적어보았습니다.

트리거란?

비디오 대여 샵을 예로 들면 고객이 1번 테이프를 빌려갔는데 '대여중' 표시가 되어 있지 않으면 '대여가능' 이기 때문에 다른 고객에게 실수 할 수 있다.
이럴 때 대여를 해 갔다면 알아서 '대여중'으로 표시되고 다시 반납하면 '대여가능'으로 표시가 된다면 대여 관리가 편해 질것이다.
이처럼 트리거는 하나의 작업이 발생할 때마다 그작업의 발생과 동시에 작동하는 일련의 작업들이다.
한 테이블과 관련되어 존재하는데 Insert, Delete, Update 세가지 형태로 만들수 있다.

트리거 만들기
트리거 생성 구문

CREATE TRIGGER 트리거이름 ON { table | view }
[WITH ENCRYPTION]
{
{ {FOR | AFTER | INSTEAD OF }
{ [INSERT][,][UPDATE][,][DELETE]}

AS
SQL구문

트리거 사용 예제
A테이블의 INSERT 되는 내용을 B테이블에 자동으로 저장 할 때

CREATE TRIGGER tr_INSERT ON A
FOR INSERT
AS
 INSERT INTO B
  SELECT *,'INSERT' FROM INSERTED

A테이블의 DELETE 되는 내용을 B테이블에 자동으로 저장 할 때

CREATE TRIGGER tr_DELETE ON A
FOR DELETE
AS
 INSERT INTO B
  SELECT *,'DELETE' FROM DELETED

A테이블의 UPDATE 되는 내용을 B테이블에 자동으로 저장 할 때
(UPDATE할때는 INSERTED와 DELETED가 함께 발생 합니다.)

CREATE TRIGGER tr_UPDATE ON A
FOR UPDATE
AS
 INSERT INTO B
  SELECT *,'UPDATEINSERT' FROM INSERTED
 INSERT INTO B
  SELECT *,'UPDATEDELETE' FROM DELETED

-- ND_DT 를 테이블의 KEY 로 가정하고 만들었습니다.
-- 여러 ROW 를 한꺼번에 업데이트 할수도 있기 때문에 트리거 안에서 커서를 돌렸습니다.

 

CREATE TRIGGER tr_Save_Data_Temp 
ON dbo.Save_Data_Temp 


FOR UPDATE,INSERT
AS         SET 
NOCOUNT ON
DECLARE 
@ND_DT VARCHAR(20
BEGIN        DECLARE trigger_cur CURSOR        FOR SELECT ND_DT FROM inserted
       
OPEN trigger_cur                FETCH NEXT FROM trigger_cur INTO @ND_DT 

         
WHILE (@@FETCH_STATUS=0)
         
BEGIN 

               
-- Save_Data 에 데이터가 있을시 삭제
               
IF EXISTS     (    SELECT 'TRUE'   FROM  Save_Data WHERE ND_DT =@ND_DT     )
               
BEGIN
                       DELETE 
Save_Data WHERE ND_DT =@ND_DT
               
END                     

               
-- 데이터 입력
               
INSERT INTO Save_Data 
               
SELECT FROM Save_Data_Temp WHERE  ND_DT =@ND_DT

               -- 데이터 삭제
DELETE FROM Save_Data_Temp WHERE  ND_DT =@ND_DT                        FETCH NEXT FROM trigger_cur INTO @ND_DT 
         
END

   CLOSE 
trigger_cur
         
DEALLOCATE trigger_cur
END
 
 

MS-SQL 2005에서는 재귀적 관계를 가지는 데이터를 출력할 수 쿼리를 지원한다.
(MS-SQL2000 지원안함)

Oracle 에서의 Connect By와 같은 역할을 하는것으로
트리 구조의 메뉴를 출력할 때 사용한다.

예를 들어 어느 회사 조직도가 아래와 같다 하자.

부장 홍길동
       차장 김길동
              과장 고길동
                     대리 서길동
                     대리 안길동
              과장 이길동
                     대리 안길동
                     사원 최길동
       차장 채길동
.... 

부장 홍길동을 필두로 하여 트리 구조로 만들어져 있는것을 볼 수 있다.

위 트리 구조를 데이터로 간단하게 표현하면 아래와 같다.

사원번호       직함        성명       소속사번001              부장       홍길동      null
002              차장       김길동      001
003              과장       고길동      002
004              대리       서길동      003
005              대리       안길동      003
006              과장       이길동      002
007              대리       안길동      006
008              사원       최길동      006
009              차장       채길동      001

고길동의 직장 상사는 002 번인 차장 김길동 이다 라는 식으로 찾을 수 있겠다.
그럼 이 트리 구조를 쿼리로 작성할 때 차장 김길동을 포함한 부하직원을 모두 찾는다고 가정하자.
방법으로는 커서를 이용하여 찾는 방법도 있을 것이고, 몇개의 컬럼을 더 포함해서 찾을 수도 있을 것이다.

그러나 CTE(Common Table Expression) 기능중 WITH를 이용하면 쉽게 쿼리가 가능하다.
쿼리는 아래와 같다.

쿼리문
WITH EMP (사원번호, 직함, 성명, 소속사번) AS
(
     SELECT 사원번호, 직함, 성명, 소속사번
     FROM    사원
     WHERE  사원번호 = ''002'' -- 김길동
     UNION ALL
     SELECT  T1.사원번호, T1.직함, T1.성명, T1.소속사번
     FROM    사원 T1 INNER JOIN EMP T2 ON T2.사원번호 = T1.소속사번
)
SELECT *
FROM EMP;

실행 결과
002              차장       김길동      001
003              과장       고길동      002
004              대리       서길동      003
005              대리       안길동      003
006              과장       이길동      002
007              대리       안길동      006
008              사원       최길동      006

+ Recent posts