728x90

declare @tab varchar(256)
declare tabinfo cursor for
select name from sysobjects where xtype='u' order by name --사용자 정의 테이블
open tabinfo
fetch from tabinfo into @tab
while @@fetch_status=0
begin
 select @tab

 select
  syscolumns.name,
  ISNULL(value, '') as FLD_DESC,
  systypes.name,
  syscolumns.length,
  case isnullable when 0 then 'NOT NULL' else 'NULL'
  end 'Nullable'
 from
  systypes,
  syscolumns left outer join
  ::fn_listextendedproperty (NULL, 'user', 'dbo', 'table',@tab, 'column', default) c
  on (syscolumns.name = c.objname collate Korean_Wansung_CI_AS )
 where
  syscolumns.id=object_id(@tab)
 and syscolumns.xusertype =systypes.xusertype
 order by colid
 fetch from tabinfo into @tab
end
close tabinfo
deallocate tabinfo

출처 : http://kr.blog.yahoo.com/fazio127/7

728x90

김기홍 대리 / MCSE, MCDBA, 한국마이크로소프트 고객지원부

어느 날인가 고객으로부터 다급한 목소리로 전화가 걸려왔다.
"SQL Server에서 DTS (Data transformation Service)를 수행하였을 때 원본 테이블 데이터가 사라졌어요"
아니 이럴 수가!! 이것이 사실이라면 정말 큰일인 것이다. RDMS 시스템에서 어떤 버그로 인해 데이터가 손실된다는 것은 상상조차 하기 힘든 일인데, 테이블내용이 통째로 사라지다니.. 일단 고객에게 100리나 되는 거리를 단숨에 달려갔다. 고객은 내 눈앞에서 실제로 원본 테이블내용이 사라지는 것을 시연해 주었다.
"A"라는 원본 SQL Server에서 "B"라는 대상 SQL Server로 테이블을 DTS하니 원본 테이블의 내용이 사라지는 것이다. (이때 DTS의 기본 설정은 대상 테이블이 존재 할 때, 대상 테이블을 제거 후 재생성 하는 옵션을 가지고 있었다.) 필자는 습관적으로 DTS를 수행한 클라이언트 장비에서 SQL Server 네트워크 유틸리티의 별칭을 조사해 보았다. 아니나 다를까 사건의 비밀은 거기에 존재하고 있었다.

고객은 자신의 장비에 "B"라는 이름을 가지고 "A"라는 서버를 연결하도록 설정한 "별칭"이 설정되어 있었다는 사실을 모르고 있었던 것이었다. 이런 황당한 사태를 유발한 "별칭"은 무엇일까? 이 별칭을 설명하기 위해서 MDAC과 SQL Server 클라이언트 네트워크 유틸리티에 대해 먼저 설명하고자 한다.

MDAC (Microsoft Data Access Component)이란?

Microsoft 관련 제품을 사용하다 보면, 간혹 MDAC이란 이름을 접하게 된다.
MDAC은 Windows NT4 Service Pack 4 또는 Internet Explorer 4.0 이후 출시된 대부분의 Microsoft 제품 (Windows, Office, Visual Studio, Internet Explorer 및 모든 서버 제품군)에 포함되어있으며, 사용자도 인지하지 못하는 사이에 (아니 자세히 관찰하면 제품설치 시 MDAC 설치 화면을 볼 수 있다.) 항상 상위 버전으로 자동 설치 될 뿐, 제거되거나 하위 버전으로 변경되는 경우는 거의 없다. 그럼 이 MDAC이란 무엇일까?
Web 또는 LAN 이용하여 데이터를 주고받는 클라이언트/서버 형태의 응용프로그램에서 관계형(SQL) 또는 비관계형에 상관없이 다양한 데이터 소스로부터 쉽게 정보(Information)을 통합-공유할 수 있는 ADO, OLE DB, ODBC 등의 컴포넌트들을 제공하여 준다.

MDAC의 버전

그 동안 배포된 MDAC의 버전을 살펴보면, MDAC 1.5, 2.0, 2.1, 2.5, 2.6, 2.7, 2.8등 다양한 버전이 존재하며, 또한 각 버전별 Service Pack도 존재한다. 이 많은 버전들의 MDAC을 설치하는 원칙은 매우 단순하다. 데이터를 공급 받는 클라이언트(데이터 소비자)의 MDAC 버전은 데이터를 제공하는 서버(데이터 소스)의 MDAC 버전보다 높거나 최소한 같아야 한다. 이렇게 하여야지만 데이터 소스에서 제공하는 모든 기능 및 기존 문제점들을 수용할 수 있게 된다. 일례로 자신의 장비에 MDAC 버전을 최소 어느 버전 이상으로 설치하여야 하는가는 자신의 장비에서 연결하는 최신 SQL Server 버전 및 서버에 설치된 최신 SQL Server Service Pack 버전이 포함한 MDAC 보다 같거나 높으면 된다. 추가로 요즈음 제공되는 MDAC관련 Tool의 경우 설치된 MDAC 버전을 제거하거나, 하위 버전으로 회귀하는 기능들을 제공하기도하지만 절대적인 원칙은 항상 MDAC은 상위 버전으로 변경될 뿐 하위버전으로 변경하는 것을 권장하지 않는다. (일부 클러스터 환경에서는 서버 환경에 특정 버전 이상의 MDAC 설치를 권장하지 않는 경우도 있지만..)

SQL Server의 제품 CD 및 SQL Server Service Pack CD를 살펴 보면 "x86\other\sqlredis.exe"라는 파일을 공통적으로 발견할 수 있다. 이 sqlredis.exe의 경우 최신 MDAC과 완전하게 동일하지는 않지만, 현재 설치된 SQL Server 버전에 문제없이 연결이 가능하도록 구성되었으며, 재배포 가능한 형태의 MDAC 대부분 컴포넌트를 포함하고, Remote에서 설치가 가능한 무인실행 설치 파일이다. 따라서 방화벽 클라이언트 응용프로그램들이 방화벽 또는 프락시 서버에서 공유되도록 하듯이, SQL Server 관리자들은 sqlredis.exe를 SQL Server 상에서 파일 공유하여 해당 SQL Server를 연결하고자 하는 모든 클라이언트는 이 파일(sqlredis.exe) 설치에 의해 SQL Server연결에 관련된 기본 요건을 쉽게 구성할 수 있도록 하는 것도 하나의 유용한 팁이 될 수 있다.

SQL Server를 사용하는 웹서버 및 기타 응용프로그램 서버 관리자의 경우, 잊지 말고 SQL Server Service Pack 적용 시 항상 각각의 웹서버에 이 sqlredis.exe를 수행하여 클라이언트의 MDAC 버전을 서버와 일치 시켜준다면, 향후 겪게 될 많은 문제점들을 피해 갈 수 있을 것이다.

SQL Server 클라이언트 네트워크 유틸리티

위에서 설명된 MDAC이 설치된 경우 여러분은 다음과 같은 방법에 의해 SQL Server 클라이언트 네트워크 유틸리티라는 것이 포함되어 있음을 확인할 수 있다.
    ->[시작] - [실행] 에서 "Cliconfg.exe" 을 입력 후 "확인"을 클릭
    ->이후 화면에 SQL Server 클라이언트 네트워크 유틸리티라는 창이 나타난다.
SQL Server 클라이언트 네트워크 유틸리티의 창 형태는 설치된 MDAC 버전에 따라 화면이 조금씩 틀린 점을 유의하여야 한다.

SQL Server 클라이언트 네트워크 유틸리티는 다음과 같은 용도에 사용하는 그래픽 기반 도구이며, 기본적으로 다음 네 가지의 탭을 가지고 있다.

  • 일반 탭: 지정된 서버에 대한 네트워크 프로토콜 연결을 만들고, 기본 네트워크 프로토콜을 변경가능.
  • 별칭 탭: 클라이언트가 연결하는 컴퓨터의 서버 별칭에 대한 정보를 나열.
  • DB 라이브러리 옵션 탭: 시스템에 현재 설치되어 있는 네트워크 라이브러리에 대한 정보를 표시.
  • 네트워크 라이브러리 옵션 탭: 시스템에 현재 설치되어 있는 DB 라이브러리 버전을 표시하고 DB 라이브러리 옵션의 기본값을 설정.
일반 탭의 경우 SQL Server 2000 출시 이전 MDAC에서는 Named Pipe가 SQL Server 연결 시 사용되는 기본 프로토콜 이었으나, SQL Server 2000 출시 이후 MDAC에서는 TCP/IP 가 기본 프로토콜임을 확인할 수 있다.


별칭(Alias)이란?

SQL Server 클라이언트 네트워크 유틸리티에서 재미있고, 독특한 동작방식을 가지는 것이 별칭 탭에서 설정 가능한 SQL Server 별칭이다.
SQL Server 별칭의 경우 각 SQL Server 별로 사용하는 네트워크 라이브러리 (Named Pipe, TCP/IP, Multiprotocol등)를 지정할 수 있고, 또한 사용하고자 하는 매개변수 (Named Pipe의 파이프 이름, TCP/IP의 Port 번호 등)를 지정이 가능하다. 일반적으로 별칭사용을 많이 접하는 사례는 보안 이슈에 의해 SQL Server를 1433이 아닌 다른 고정 포트로 서비스하는 경우 SQL Server의 클라이언트 (웹서버 및 데스크탑 PC등)들에게 특정 SQL Server의 TCP/IP Port를 지정하는 것은 종종 볼 수 있다. 그러나 이 별칭의 기능이 단순히 이것만 있는 것일까? 활용하기에 따라 처음에 DTS 사례에서 언급했던 것 같은 흥미로운 결과를 초래하는 경우가 많다.

별칭의 동작 방식

별칭은 각 클라이언트 별로 자신에게 설정된 사항만 적용된다.
만약 "Server1"와 "Server2"라는 SQL Server가 존재하고 "Client1"와 "Client2"라는 클라이언트 장비가 존재 할 때 "Client1"에만 서버 별칭이름을 "Server2" 그리고 서버 이름을 "Server1"로 지정 추가한 경우에, 각각의 "Client1"과 "Client2"에서 "Server1"과 "Server2"를 연결하면 어떤 서버가 연결될까? 당연히 "Client2"에서는 "Server1"과 "Server2"가 정상적으로 연결 가능. 하지만 "Client1"에서 "Server1"을 연결하는 경우에는 "Server1"이 연결되고, 별칭 사용에 의해 "Server2"를 연결한 경우 "Server1"로 연결된다. 이 때 "Client1"에서 SQL Server 명을 별도로 확인하지 않으면 자신은 "Server1"에 연결된 상태로 착각할 수 있다.

만약 세 사람이 구름 한 점 없는 푸른 하늘을 쳐다 보고 있다. 이 때 한 사람은 짙은 빨간색 색안경을, 또 한 사람은 짙은 노란색 색안경을, 그리고 나머지 한 사람은 전혀 색안경을 끼지 않은 경우 이 세 사람이 보고 있는 하늘의 색채는 푸른 빛, 붉은 빛, 노란 빛으로 보일 수 있다. 별칭 또한 마찬 가지로 푸른색(Server1)붉은색(Server2)으로 보이게 할 수 있는 색안경과 같은 역할을 한다. 하지만 각기 자기가 착용한 색안경에만 영향을 받을 뿐 다른 사람이 착용한 색안경에는 전혀 영향을 받지 않는다. 그러나 상당히 위험할 수 있는 경우는 처음에 언급된 DTS 사례처럼 자신이 색안경을 착용한지를 모르는 경우 뜻하지 않은 사태가 발생할 수 있음도 유의 하여야 한다.

별칭의 적용 사례

  1. 4대의 웹서버가 SQL Server를 사용하고 있는데 운영중인 SQL Server ("ServerA")의 장애 발생시 Standby SQL Server("ServerB")로 1~2분 내로 연결을 변경하고 싶은데 어떻게 해야 하나요? (이 때 Standby Server는 Log Shipping에 의해 운영 데이터와 15분의 차이는 있지만 ReadOnly 상태로 임시적 운영예정이어서 데이터 동기화는 큰 문제가 없습니다만 IP와 서버명을 변경하여 System을 Restart하기에는 시간이 너무 촉박합니다.)
    : 답은 간단하다. 각 웹서버에 서버 별칭은 "ServerA"로 서버 이름은 "ServerB"로 설정된 별칭을 추가하면, 별칭이 추가된 순간부터 연결되는 SQL Server Connection은 ServerB로 연결된다.
  2. 하나의 Domain을 사용하는 3000대 PC에서 SQL Server를 직접 연결하는데, SQL Server명과 IP, Port 가 모두 바뀌어야 하는 불가피한 상황이 발생 했습니다. 그런데 응용프로그램의 SQL Server 정보를 변경하여 새로 배포하지 않고, 모든 PC에서 기존의 SQL Server가 아닌 새로 변경된 SQL Server명과 IP, Port를 연결할 수 있는 방법이 없을까요?
    : 마찬가지로 방법이 그리 어려운 것은 아니다. Domain LogOn시 다음 registry를 항목을 추가하는 Domain LogOn Script를 설정 하거나, .reg 파일을 배포 및 실행하면 된다. Ex) TCP/IP Connection 사용
    -> Old SQL Server 서버명 : OLDSERVER ; IP : 10.10.10.1 ; Port : 1433
    -> New SQL Server 서버명 : NEWSERVER ; IP : 10.10.10.2 ; Port : 1064

    ----------------------------------
    Windows Registry Editor Version 5.00

    [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\Client\ConnectTo]
    "OLDSERVER"="DBMSSOCN,NEWSERVER,1064"
    "10.10.10.1 "="DBMSSOCN, 10.10.10.2,1064"

    추가 사항:
    - MDAC 2.6 이상에서 Dynamic Port를 사용하고 싶은 경우 예제
    "OLDSERVER "="DBMSSOCN, NEWSERVER"

    - Named Pipe를 사용하는 경우 예제
    "OLDSERVER"="DBNMPNTW,\\\\NEWSERVER\\pipe\\sql\\query"

    - Multiprotocol을 사용하는 경우 예제
    "OLDSERVER "="DBMSRPCN,NEWSERVER"

참고로 SQL Server의 대다수 사용자들이 가장 많이 겪는 문제점들이 SQL Server의 연결과 관련된 문제이므로 아래 첨부된 웹캐스트를 활용하면 많은 도움이 될 것이다.
Support WebCast: Microsoft SQL Server 2000: troubleshooting Connectivity  

SQL Server 2000이 출시된 이후 SQL Server 연결 시 SQL Server Name에 대한 Name Resolution이 필수적인 경우가 많아 연결 문제가 더 잦아지는 경향도 없지 않다. 이때 네트워크 구성에 따라 Hosts 또는 lmhosts 파일에 SQL Server Name을 수동 추가하는 작업으로 인해 상당수의 SQL Server 연결 문제를 해결하는 경우가 상당수 있다.

이상으로 MDAC, SQL Server 클라이언트 네트워크 유틸리티, 별칭 등에 대해 간략하게 살펴 보았다. 만약 지금까지의 내용을 개개인의 PC상에서 점검해 보면 이미 상당수의 사용자들이 SQL Server 연결과 관계된 많은 설정 및 콤포넌트들을 본인들도 인지하지 못하는 사이에 사용하고 있었음을 발견할 수 있을 것이다.

   최종 수정일 : 2005년 8월 22일

728x90
제 1강 : SQL Server 2005 .NET CLR 통합기능

이름 : 김종균
전) 벅스(주) 프로그래머, DBA
현) (주) 테크데이타 SQL Server Technical Support Engineer

개요

SQL 서버 2005에서는 .NET Framework2.0의 Common Language Runtime 기술을 이용하여 매우 향상된 데이터베이스 프로그램을 구현 할 수 있습니다. 이는 Microsoft Visual C#, Microsoft Visual Basic.NET, Microsoft Visual C++ 등의 CLR 언어를 통해서 저장프로시저, 사용자정의 함수, 트리거를 생성할 수 있을 뿐 아니라, 사용자 정의 데이터타입 및 집계의 생성을 가능하게 합니다.

TSQL의 그 능력의 한계

TSQL은 RDBMS만을 위한 언어이기 때문에 DML, DDL외의 프로그래밍 언어로서의 기능이 아주 미약합니다. TSQL에서 제공하는 함수는 제한적이며 (SQL2005에서 기능이 많이 추가되긴 하였지만), TSQL 로는 SQL 서버 외부 개체에 접근할 수 없습니다. 예를 들면 파일시스템 오브젝트를 통해서 파일의 내용을 읽고, 쓰는 등의 작업은 TSQL 로는 상상도 할 수 없습니다. 물론 SQL2000에서도 Extended Stored Procedure를 통해서 SQL외부 개체에 대한 핸들링을 할 수 있었으나, 상대적으로 어려운 C,C++프로그래밍에 대한 접근성과 in-process형태로 Sqlserver프로세스에서 실행되었기 때문에 자칫 확장 저장프로시져에 치명적인 코드결함이 있으면, SQL서버가 다운되는 경우도 있었습니다
하지만 CLR을 사용함으로써 SQL서버에서 기존에는 할 수 없었던 다양한 프로그래밍을 가능하게 합니다.

TSQL과 CLR의 몇 가지 장단점

  • TSQL은 인터프리터 방식이므로 느리다.
  • TSQL의 에러핸들링은 SQL2005에서 많이 향상되긴 했지만 비교적 좋지 않다. 
  • CLR은 컴파일 된 형태이므로 실행이 빠르다. 
  • CLR은 .NET프로그래밍 언어의 방대한 Class라이브러리를 사용하여 막강한 프로그래밍을 할 수 있다. 
  • TSQL은 대량의 데이터 액세스 혹은 데이터 조작 작업등에 유리하다. 
  • TSQL은 절차적 언어가 아니라 선언적 언어라서 문자열 조작, 데이터 포맷팅, 절차적 혹은 반복적인 로직에는 성능이 좋지 않다. 
  • CLR은 계산 집중적이거나 절차적,반복적 로직, 문자열 처리에서 좋은 성능을 나타낸다.

CLR 통합기능을 활용한 개발 절차

   1. SQL2005에서 CLR통합기능 옵션 활성화

   2. .NET Framework에서 지원하는 언어를 이용하여 클래스 작성

   3. 언어컴파일러를 통한 클래스 컴파일

   4. SQL서버에서 ASSEMBLY등록

   5. 등록한 ASSEMBLY 를 이용하여 사용자 개체 생성

.NET CLR통합기능 활성화

SQL2005는 보안상의 이유로 .NET CLR통합기능을 비활성화 해두고 있습니다. 따라서 CLR통합기능을 사용하기 위해서는 ‘Clr Enabled’ 라는 구성옵션을 활성화 해야 합니다. .NET CLR통합 기능을 사용할 수 있게 해주는 옵션을 활성화하는 방법은 두 가지가 입니다.

첫 번째 방법은, sp_configure 구성옵션 명령을 통해서 ‘clr enabled’ 라는 구성옵션을 ‘1’로 세팅해주는 것입니다. 이 옵션은 수정 즉시 적용되므로, SQL서버를 재 시작 할 필요가 없습니다.

SP_CONFIGURE 'clr enabled',1
RECONFIGURE WITH OVERRIDE
GO
SP_CONFIGURE
GO
name minimum maximum config_value run_value
----------------------- ------------ ------------ ------------ ------------
clr enabled 0 1 1 1
lightweight pooling 0 1 0 0

유의해야 할 사항은 clr enabled옵션이 해제되게 되면 등록된 모든 ASSEMBLY가 unload되게 됩니다.
그리고 clr enabled옵션과 lightweight pooling옵션은 병행해서 사용이 불가능합니다.

두 번째 방법은, SQL서버 노출영역 구성에서 기능에 대한 노출영역을 구성하는 것 입니다. 아래 그림과 같이 clr enabled 옵션을 체크 해주면 됩니다.

SQL서버 노출영역 구성

예제1 – 사용자 정의 스칼라 함수 구현

그럼, CLR을 통한 간단한 사용자 정의 함수를 생성하고 SQL서버에 이식시켜 사용해보는 예제를 구현해보면서, CRL을 이용한 SQL프로그래밍에 대한 감을 잡아보겠습니다.

예제로 email주소를 정합성을 검사해주는 사용자함수 클래스를 C#으로 구현해 보겠습니다. 소스코드 작성을 위해서 메모장을 이용하셔도 무방합니다만.. 가능하시면 Visual Studio를 이용하시면 보다 더 훌륭한 개발 인터페이스를 통해서 더 빠르고 쉽게 코딩이 가능합니다. 여기서, c#코드에 대한 설명은 하지 않습니다.

개발 도구를 사용하여 아래 c#코드를 작성합니다.

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Text.RegularExpressions;


public partial class UserDefinedFunctions
{
[Microsoft.SqlServer.Server.SqlFunction]
public static bool emailCheck(string email)
{
string pattern=@"^[a-z][a-z|0-9|]*([_][a-z|0-9]+)*([.][a-z|" + @"0-9]+([_][a-z|0-9]+)*)?@[a-z][a-z|0-9|]*\.([a-z]" + @"[a-z|0-9]*(\.[a-z][a-z|0-9]*)?)$";
Match match = Regex.Match(email, pattern, RegexOptions.IgnoreCase);

if(match.Success) return true;
else return false;
}
};

[소스1] emailCheck.cs

소스코드 작성이 완료되면 c#컴파일러를 통해 cs파일을 dll형태로 컴파일 합니다.

csc.exe /target:library /out:emailcheck.dll emailcheck.cs

참고. csc컴파일러의 위치는 .NetFramework이 설치된 폴더에 있습니다.
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727

자 이렇게 해서 emailcheck.dll 이라는 어셈블리 파일을 생성했습니다. 이 파일을 관리하시는 적절한 폴더에 복사 하십시요. 이 파일은 추후에 SQL2005의 ASSEMBLY로 등록하게 되면, 삭제해도 무관합니다.

다음 단계로 CLR사용자 함수 ASSEMBLY를 SQL서버에 ASSEMBLY로 등록합니다.

CREATE ASSEMBLY SQLER_UDF
FROM 'C:\TEMP\emailcheck.dll'
WITH PERMISSION_SET = SAFE
GO

CREATE ASSEMBLY는 이미 .dll파일로 미리 컴파일된 ASSEMBLY를 SQL2005 내부에서 사용할 수 있도록 업로드 혹은 바인딩 하는 작업입니다.


다음 단계로 ASSEMBLY를 매핑하여 사용자 정의 함수를 생성합니다.

CREATE FUNCTION UDF_VALIDATEEMAIL (@email as nvarchar (100))
RETURNS bit
AS EXTERNAL NAME SQLER_UDF.MyFunctions.emailCheck
GO

사용자 개체 생성시EXTERNAL NAME 지정규칙은 다음과 같습니다.
[SQL Server Assembly Name].[Fully Qualified Path to Class].[Static Method Name]

자. 그럼 이제 생성한 스칼라 사용자 정의 함수를 사용해보겠습니다.

SELECT DBO.UDF_VALIDATEEMAIL ('bellvirus1@naver.com')     --1
SELECT DBO.UDF_VALIDATEEMAIL ('bellvirus1@naver')           --0
SELECT DBO.UDF_VALIDATEEMAIL ('bellvirus1.com')               --0
GO

상기 절차로 .NET 프로그램 코드를 통한 UDF생성을 간단하게 살펴보았습니다.

예제2 – 스칼라 함수, 저장프로시져 및 트리거 구현

다음 예제로 테이블의 전체 행 수를 반환하는 스칼라 함수와, 레코드 셋을 가져오는 저장프로시져, 데이터 삭제에 대해 로그를 기록하는 트리거를 Visual Basic .NET 으로 구현 해보겠습니다.

Imports System
Imports System.Collections.Generic
Imports System.Text
Imports System.Data
Imports System.Data.SqlClient
Imports Microsoft.SqlServer.Server
Imports System.Data.SqlTypes

Public Class MySQLClass
<SqlFunction(DataAccess:=DataAccessKind.Read)>_
Public Shared Function ReturnOrderCount() As Integer
Using conn As New SqlConnection("context connection=true")
conn.Open()
Dim cmd As New SqlCommand("SELECT COUNT(*) AS 'ORDERCNT' FROM SALES.SALESORDERDETAIL", conn)
Return CType(cmd.ExecuteScalar(), Integer)
End Using
End Function


Public Shared Sub GetSalesOrderDetail()
Using conn As New SqlConnection("context connection=true")
conn.Open()
Dim cmd As New SqlCommand("SELECT COUNT(*) AS 'ORDERCNT' FROM SALES.SALESORDERDETAIL", conn)
SqlContext.Pipe.ExecuteAndSend(command)
End Using
End Sub


<Microsoft.SqlServer.Server.SqlTrigger()>_
Public Shared Sub writeDeleteLog()
Dim oTc As SqlTriggerContext
oTc = SqlContext.TriggerContext
If (oTc.TriggerAction = TriggerAction.Delete) Then
Dim oFi As New System.IO.StreamWriter("c:\temp\log.txt", True)
oFi.Write("something deleted !")
oFi.Close()
End If
End Sub

End Class

[소스2] MySQLClass.vb

코드 작성이 완료되면 컴파일러를 통해 DLL형태의 파일을 생성합니다.

vbc /target:library /out:c:\temp\MySQLClass.dll c:\temp\MySQLClass.vb

그런 다음에 SQL서버에서 ASSEMBLY로 등록합니다.
이번 예제에서는 ASSEMBLY생성시에 PERMISSION_SET옵션을 EXTERNAL_ACCESS를 지정하였는데, 그 이유는 writeDeleteLog 메서드가 SQL 외부 개체에 액세스 할 필요가 있기 때문입니다.

여기서 잠깐 PERMISSION_SET옵션에 대해 알아보고 넘어 가겠습니다.

  • SAFE (기본옵션) : 등록하게 되는 어셈블리에서 실행한 코드는 파일, 네트워크, 환경변수 또는 레지스트리와 같은 외부 시스템 리소스에 액세스할 수 없습니다.
  • EXTERNAL_ACCESS : 파일, 네트워크, 환경 변수 또는 레지스트리와 같은 외부 시스템 리소스에 액세스할 수 있습니다.
  • UNSAFE : SQL Server 인스턴스의 내부 리소스와 외부 리소스 모두에 제한 없이 액세스할 수 있습니다.
CREATE ASSEMBLY SQLER_CLASS
FROM 'C:\TEMP\MYSQLClass.dll'
WITH PERMISSION_SET = EXTERNAL_ACCESS
GO

생성 명령을 실행하였으나 아래와 같은 오류가 발생합니다.


메시지10327, 수준14, 상태1, 줄1 어셈블리'MySQLClass'에PERMISSION_SET = EXTERNAL_ACCESS에대한권한이없으므로어셈블리'MySQLClass'에대한CREATE ASSEMBLY가실패했습니다. 어셈블리는DBO(데이터베이스소유자)에게EXTERNAL ACCESS ASSEMBLY 권한이있고데이터베이스에TRUSTWORTHY 데이터베이스속성이있는경우또는어셈블리가현재인증서로서명되어있거나EXTERNAL ACCESS ASSEMBLY 권한이있는관련로그인을소유한비대칭키로서명되어있는경우에권한이부여됩니다.

사용자 정의함수나 저장프로시져에서 외부 개체에 접근하려면 Database의 TRUSTWORTHY 옵션을 활성화 해주어야 합니다. 아래 명령을 실행하여 외부 개체 접근을 허용해줍니다. 그런 다음 다시 ASSEMBLY를 생성해줍니다.

ALTER DATABASE AdventureWorks SET TRUSTWORTHY ON
GO

CREATE ASSEMBLY SQLER_CLASS
FROM 'C:\TEMP\MYSQLClass.dll'
WITH PERMISSION_SET = EXTERNAL_ACCESS
GO

SQLER_CLASS 어셈블리를 활용하여 사용자 정의 스칼라 함수를 생성합니다.

CREATE FUNCTION UDF_getOrderCount()
RETURNS INT
AS EXTERNAL NAME SQLER_CLASS.MySQLClass.ReturnOrderCount
GO

생성한 함수를 실행하여 보겠습니다.

SELECT DBO.UDF_getOrderCount() AS orderCount
GO
orderCount
-----------
121317

SQLER_CLASS 어셈블리를 활용하여 저장프로시져를 생성합니다.

CREATE PROC UP_getSalesOrderDetail
AS EXTERNAL NAME SQLER_CLASS.MySQLClass.GetSalesOrderDetail
GO

프로시져를 실행하여 보겠습니다.

EXEC UP_getSalesOrderDetail
GO

43659  1      4911-403C-98 1      776    1      2024.994
43659  2      4911-403C-98 3      777    1      2024.994
43659  3      4911-403C-98 1      778    1      2024.994
43659  4      4911-403C-98 1      771    1      2039.994
43659  5      4911-403C-98 1      772    1      2039.994
43659  6      4911-403C-98 2      773    1      2039.994
43659  7      4911-403C-98 1      774    1      2039.994
43659  8      4911-403C-98 3      714    1      28.8404
43659  9      4911-403C-98 1      716    1      28.8404

결과를 잘 반환해 줍니다.

다음으로 트리거를 생성하여 보겠습니다.
이 예제를 위해서 간단한 샘플 테이블을 생성하겠습니다.

CREATE TABLE TBL_TEST (idx int, name varchar(20))
GO
INSERT INTO TBL_TEST VALUES (1,'KIM')
INSERT INTO TBL_TEST VALUES (1,'LEE')
INSERT INTO TBL_TEST VALUES (1,'PARK')
GO

TBL_TEST테이블에 대해서 DELETE명령에 대한 트리거 생성합니다.

CREATE TRIGGER TRG_DELETE
ON TBL_TEST
FOR DELETE
AS EXTERNAL NAME SQLER_CLASS.MySQLClass.writeDeleteLog
GO

트리거가 잘 생성되었고, 실제로 데이터를 삭제하여 트리거가 정상적으로 동작하는지 살펴보겠습니다. 앞서 작성한 writeDeleteLog 메서드는 삭제 트리거가 발생하면 메모장에서 log.txt파일에 Something Deleted! 를 기록하게 됩니다.

DELETE FROM TBL_TEST WHERE idx = 1
GO

메모장에서 log.txt파일 확인

예제3. 사용자 정의 집계함수(Aggegation)의 구현

이번 예제에서는 각 행들의 특정 문자열 컬럼을 합해주는 함수를 만들어 보겠습니다.
이 함수는 숫자형 집계함수인 SUM()과 유사합니다 ^^
이번 예제는 온라인 도움말에서 제공하는 샘플을 사용합니다.

using System;
using System.IO;
using System.Data.Sql;
using System.Data.SqlTypes;
using System.Text;
using Microsoft.SqlServer.Server;

[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
"CA1020:AvoidNamespacesWithFewTypes"
, Scope = "namespace", Target =
"Microsoft.Samples.SqlServer")]

namespace Microsoft.Samples.SqlServer
{
[Serializable]
[Microsoft.SqlServer.Server.SqlUserDefinedAggregate(
Microsoft.SqlServer.Server.Format.UserDefined,       //use clr
serialization to serialize the intermediate result

IsInvariantToNulls = true,       //optimizer property
IsInvariantToDuplicates = false,       //optimizer property
IsInvariantToOrder = false,       //optimizer property
MaxByteSize = 8000)       //maximum size in bytes of persisted value
]
public classConcatenate : Microsoft.SqlServer.Server.IBinarySerialize
{
/// <summary>
///The variable that holds the intermediate result of the concatenation
/// </summary>
private StringBuilder intermediateResult;

/// <summary>
/// Initialize the internal data structures
/// </summary>
public void Init()
{
intermediateResult = new StringBuilder();
}

       
/// <summary>

///Accumulate the next value, nop if the value is null
/// </summary>
/// <param name="value"></param>
public void Accumulate(SqlString value)
{

if (value.IsNull)
{
return;
}
intermediateResult.Append(value.Value).Append(',');
}

/// <summary>
///Merge the partially computed aggregate with this aggregate.
/// </summary>
/// <param name="other"></param>
public void Merge(Concatenate other)
{
intermediateResult.Append(other.intermediateResult);
}

/// <summary>
/// Called at the end of aggregation, to return the results of the aggregation
/// </summary>
/// <returns></returns>
public SqlString Terminate()
{
string output = string.Empty;
//delete the trailing comma, if any
if (intermediateResult != null && intermediateResult.Length > 0)
output = intermediateResult.ToString(0, intermediateResult.Length - 1);
return new SqlString(output);
}
public void Read(BinaryReader r)
{
intermediateResult = new StringBuilder(r.ReadString());
}

public void Write(BinaryWriter w)
{
w.Write(intermediateResult.ToString());
}
}
}
[소스3] concat.cs

소스코드 작성이 완료되면 c#컴파일러를 통해 cs파일을 dll형태로 컴파일 합니다.

csc.exe /target:library /out:concat.dll concat.cs

concate.dll파일을 ASSEMBLY로 등록하고, 집계함수를 생성합니다.

CREATE ASSEMBLY STRINGHANDLES3
FROM 'C:\TEMP\concat.dll'

CREATE AGGREGATE [dbo].[Concatenate](@input nvarchar(4000))
RETURNS nvarchar(4000)
EXTERNAL NAME
STRINGHANDLES3.[Microsoft.Samples.SqlServer.Concatenate];
GO

테스트를 위한 샘플 테이블을 생성하고, 데이터를 몇 건 삽입합니다.

CREATE TABLETEST (IDX INT, NAME VARCHAR(10))
GO

INSERT INTO TEST VALUES (1,'AAA')
INSERT INTO TEST VALUES (2,'BBB')
INSERT INTO TEST VALUES (3,'CCC')
INSERT INTO TEST VALUES (4,'DDD')
INSERT INTO TEST VALUES (5,'EEE')
INSERT INTO TEST VALUES (6,'KKK')
GO

집계함수를 활용하여 쿼리를 실행합니다.

SELECT SUM(IDX)AS SUMIDX, dbo.concatenate(name) AS CONCATNAMES
FROM TEST
GO

SUMIDX      CONCATNAMES
---------  ----------------------------------
21          aaa,bbb,ccc,ddd,eee,kkk

(1개행적용됨)

예제4. 사용자 정의 테이블 값 함수의 구현

이번에는 특정 구분자를 이용하여 요소를 분리해주는 SPLIT함수를 구현해 보겠습니다.

using System;
using System.Collections;
using System.Data;
using System.Data.Sql;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;


public sealed class StringSplitter
{
[SqlFunction(FillRowMethodName = "FillRow")]
public static IEnumerable Split(SqlString argument1, Char []
argument2)
{
string value;

if (argument1.IsNull)
value = "";
else
value = argument1.Value;

return value.Split(argument2);
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design",
"CA1021:AvoidOutParameters")]
public static void FillRow(Object obj, out string stringElement)
{
stringElement = (string)obj;
}
}
[소스4] Split.cs

소스를 컴파일 합니다.

csc.exe /target:library /out:split.dll split.cs

어셈블리를 등록합니다.
CREATE ASSEMBLY STRINGHANDLES
FROM 'C:\TEMP\split.dll'
GO

사용자 정의 함수를 생성합니다.

CREATE FUNCTION DBO.UDF_SPILT (@INPUT NVARCHAR (4000), @DELIMETER
NVARCHAR(10))
RETURNS TABLE (ELEMENT NVARCHAR (MAX))
AS EXTERNAL NAME STINGHANDLE.[ StringSplitter]. Split;
GO

실행테스트

SELECT * FROM DBO.UDF_SPILT 'MSSQL/EXCHANGE/OFFICE/VISTA' , >'/');
GO

element
-----------------
MSSQL
EXCHANGE
OFFICE
VISTA
(4개행적용됨)

마치며..

간단한 예제 중심으로 SQL서버2005 .NET CLR통합 기능에 대해서 살펴보았습니다. 이처럼, TSQL만으로는 불가능 했던 프로그래밍을 가능하게 TSQL에 날개를 달아주는 것이 .NET CLR입니다. 언제나 그렇듯이, 좋은 기능을 제대로 사용하지 않거나, 남용하면 역효과를 가져 올 수 있습니다. TSQL만으로 구현 가능한 것들은 TSQL로 구현하는게 바람직 할 것입니다. 주어진 상황에 대해서 TSQL과 CLR 중 어떠한 것을 적용해야 효율적인가 에 대한 많은 고민과 테스트 과정을 통해서 올바른 판단을 해야 할 것입니다.

감사합니다.

+ Recent posts