[LocalDB] SQL2012 LocalDB의 Collation 문제 해결

 

 

MSSQL 2012의 LocalDB 사용 중 Join, Where명령에 대해서 아래와 같은 오류가 발생했다.

 

Cannot resolve the collation conflict between SQL_Latin1_General_CP1_CI_AS" and "Korean_Wansung_CS_AS" in the equal to operation.

 

 

데이터베이스의 데이터정렬 형식이 서로 맞지 않아서 작업을 수행할 수 없다는 메시지이다. 

왜 이러한 현상이 나타났는가?

MSSQL 2012  LocalDB 인스턴스는 기본적으로 'SQL_Latin1_General_CP1_CI_AS' 형식으로 셋팅되며 이를 변경할 수 없다.

 

http://technet.microsoft.com/en-us/library/hh510202.aspx

 

... 

The instance collation for LocalDB is set to SQL_Latin1_General_CP1_CI_AS and cannot be changed. Database-level, column-level, and expression-level collations are supported normally. Contained databases follow the metadata and tempdb collations rules defined by Contained Database Collations.

....

 

 

그래서 LocalDB에 데이터베이스를 생성 할 때에 옵션의 데이터정렬값을 설정해 주지 않으면 기본값인 SQL_Latin1_General_CP1_CI_AS 로 설정된다.

이 데이터정렬셋은 한글 데이터가 들어오면 ???? 과 같이 깨져서 들어오고  제대로 처리를 해주지 못한다.

데이터베이스에서 한글을 쓰려면  데이터베이스 생성시 Korean_Wansung_CS_AS 등으로 선택해 주어야 한다.

 

물론 데이터베이스가 생성된 이후에도  데이터정렬값을 바꿔줄 수는 있는데, 그 전에 생성한 테이블의 컬럼값들 중 string형(nVarchar, varchar 등) 컬럼들은  여전히 SQL_Latin1_General_CP1_CI_AS 로 설정되어 있고, 데이터정렬 설정 이후에 만들어진 테이블, 컬럼에만 Korean_Wansung_CS_AS 이 적용된다.

 

이 포스팅에서는 이미  SQL_Latin1_General_CP1_CI_AS 로 만들어진 테이블과 컬럼의 Collation 설정을 변경하는 방법을 알아본다.

 

 

 

 

sp_help TABLENAME

 

테이블의 정보를 조회할 수 있다. 두번째 결과테이블에 Column리스트업되고 Collation값을 확인할 수 있다.

숫자형 컬럼은 NULL 이고 문자열타입 컬럼에만 현재 Collation값이 보여진다.

 

 

이제 변경할 컬럼을 찾았으니 값을 변경해 준다.

 

ALTER TABLE T_TYPE
ALTER COLUMN 유형명 NVARCHAR(800) COLLATE Korean_Wansung_CI_AS;

ALTER TABLE T_TYPE
ALTER COLUMN 유형텍스트 NVARCHAR(1000) COLLATE Korean_Wansung_CI_AS;

ALTER TABLE T_TYPE
ALTER COLUMN USE_YN NVARCHAR(5) COLLATE Korean_Wansung_CI_AS;

 

 

Collation이 잘못 들어간 다른 테이블의 컬럼도 모두 찾아서 위와 같은 방법으로 바꿔준다.

 

물론 데이터베이스를 생성할 때 미리 Collation값을 지정해 줬다면 위와 같이 번거로운 작업을 하지 않아도 된다.

 

 

  [ACCESS] Round 함수의 결과값 반환에 대한 이해.

 

 

 

몇일 전 Round함수가 잘못 작동하고 있다는 글을 포스팅하였다.

 

 

하지만  설마하니 Round같은 기초 함수를 설계하는데 버그를 낼리가 없을거라는 생각에 조금 더 조사를 해 보았더니 역시 이것은 의도된 것이었음을 알게 되었다.

그리고 그것이 보다 정확한 결과를 위해서 라는 것도..

 

 

 

이 글을 보니 단숨에 이해가 되었다.

 

 

요지는 이렇다.

 

정수 1개를 기준으로  0.1 ~ 0.9 까지 소수 9개가 반올림 대상 숫자가 된다.

0.1~0.4까지 4개의 소수는 내림 처리를 하게 되고

0.5~0.9까지 5개의 소수는 올림 처리를 하게 된다.

 

이렇게 되면 버려지는 숫자는 4개, 올려지는 숫자는 5개이므로 정수1개당  1/9만큼 불공평하게 나누어진다는 것이다.

살짝 표로 보자면..

 1.0

 2.0

 3.0

 4.0

 1.1 

 2.1

 3.1

 4.1

 1.2

 2.2

 3.2

 4.2

 1.3

 2.3

 3.3

 4.3

 1.4

 2.4

 3.4

 4.4

 1.5

 2.5

 3.5

 4.5

 1.6

 2.6

 3.6

 4.6

 1.7

 2.7

 3.7

 4.7

 1.8

 2.8

 3.8

 4.8

 1.9

 2.9

 3.9

 4.9

 

1.0~4.0까지 숫자 40개 중에서 변하지 않는 숫자는 4개이고

버려지는 숫자는 16개, 올려지는 숫자는 20개이다. 올려지는 숫자가 더 많다..

 

 

그럼 위 링크의 'Banker's Rounding'의 규칙을 적용해 본다면

 

 1.0

 2.0

 3.0

 4.0

 1.1 

 2.1

 3.1

 4.1

 1.2

 2.2

 3.2

 4.2

 1.3

 2.3

 3.3

 4.3

 1.4

 2.4

 3.4

 4.4

 1.5

 2.5

 3.5

 4.5

 1.6

 2.6

 3.6

 4.6

 1.7

 2.7

 3.7

 4.7

 1.8

 2.8

 3.8

 4.8

 1.9

 2.9

 3.9

 4.9

1.0~4.0까지 숫자 40개 중에서 변하지 않는 숫자는 똑같이 4개이고

버려지는 숫자는 18개올려지는 숫자도 18개이다.

 

 

실제로 여러 값의 반올림을 평균한다던지 하는 집계쪽에는 이 방법이  더 근사치와 가까워 질 것이 확실해 보인다.

하지만 개인의 성적을 처리할 경우에는 맞지 않는다. 97.5점을 받아도 98점이고, 98.5점을 받아도 98점이 되버리니..

 

여튼 이제껏 생각 못하고 그냥 마구잡이로 Round를 써왔는데 이번을 계기로 알게 되었으니 앞으로는 상황을 고려해 사용하도록 해야겠다.

[ACCESS] 엑세스의 Round 함수의 버그? 오작동 사례.

 

 

Access를 이용해 데이터 처리를 하던 도중 의도치 않는 결과가 나온다는 보고가 있어서 찾아보았다.

흔히 반올림 처리를 위해 사용하Round 함수가 특정 규칙을 가지고 오작동을 하고 있었다.

0.5 => 1

1.5 => 2

2.5 => 2

3.5 => 4

4.5 => 4

5.5 => 6

6.5 => 6

....

이렇게 격수로 하나씩 건너뛰면서 반올림, 내림, 반올림, 내림 처리를 하고 있는 것이었다.

 

 

증거자료..

 

 

 아래와 같이 실수(Single)타입의 값1~값6 필드를 가진 테이블을 만들어서 값들을 채워넣고..

 

 

 

아래의 쿼리를 돌려서 나온 결과..

SELECT
SUM(값1) AS 원값
,SUM(값2) AS [원값빼기05]
,SUM(값3) AS [원값빼기1]
,SUM(값4) AS [원값빼기15]
,SUM(값5) AS [원값빼기25]
,SUM(값6) AS [원값빼기35]
FROM 테이블1

UNION ALL

SELECT
ROUND(SUM(값1),0)
,ROUND(SUM(값2),0)
,ROUND(SUM(값3),0)
,ROUND(SUM(값4),0)
,ROUND(SUM(값5),0)
,ROUND(SUM(값6),0)
FROM 테이블1;

결과를 보면 17.5는 정상적으로 18로 반올림 처리가 되었으나  12.5의 반올림은 13이 되지 않고 12가 되었다.

역시 7.5는 반올림되어 8이 되었으나 2.5는 내림이 되어 2가 되어버렸다.

 

추측하기에는 실수(Single)값의 부동소숫점 연산 처리때문에 그런게 아닐까 싶지만..

별생각없이 반올림 처리하려고 Round를 자주 쓰게 되는데  이러면 곤란하다..

특히 이건 SAT 채점의 Omit처리를 하다 발견된 건데,, 점수의 계산이 틀어지면 끝장이지 않은가.

 

해서 해결방안은. Round함수를 뜯어고칠수는 없으니,,

Round(SUM(값1)+0.1 , 0) 으로  일괄적으로 0.1을 더해주었다. 정수반올림을 하는데에는 0.1 더해주는거면 충분함..

 

[MSSQL] 써보면 편리한 Split 함수 세 개

 

프로그래밍 언어에는 문자열 관련 함수가 많이 존재하지요. 그만큼 많이 쓰이기도 합니다.

언어마다 약간의 차이는 있지만 대표적인 함수들은 함수명과 매개변수까지 똑같지요.
MS-SQL에도 마찬가지로 기본적으로 문자열 함수가 지원됩니다.


Right, Left, SubString(Mid), Trim, Replace, CharIndex(InStr) ...


그런데 자주 쓰게 되는 문자열함수인 Split 만은 찾아볼 수가 없군요. 저는 Split 을 좋아라하는데..
언어에서 Split 함수는 어떤 문자열을 특정 구분자로 분리해서 배열변수에 담아주는 역할을 합니다.
그런데 MS-SQL에서는 '배열변수' 개념이 없기 때문에 배열변수 대신 테이블변수로 대체해 사용하게 되죠.


사정이 이렇다보니 SQL에서 문자열 Split을 한다면 리턴되는 결과값 변수로 나올수가 없고 테이블이 되어 나올 수밖에 없겠지요.
다른 문자열 함수들처럼 SELECT문에 그냥 갖다쓸수가 없게 됩니다. 그래서 없는가 봅니다.
 
그렇지만 Split은 없으면 아쉬운 편리한 함수 중 하나입니다.
그래서 사용자정의 함수를 만들어 보았습니다. 우리가 일반적으로 흔히 사용하는(알고있는) Split함수의 용법과 다르지 않습니다.
 


 

1. 문자열을 특정 구분자로 단순히 쪼갠다 : FN_SPLIT

    SELECT * FROM FN_SPLIT(@문자열,@구분자)

 

CREATE FUNCTION [dbo].[FN_SPLIT]
(
@리스트 VARCHAR(MAX)
,@분리자 VARCHAR(10)
)
RETURNS @TB TABLE
( 
POS int IDENTITY PRIMARY KEY,
VAL1 varchar(200)
)
AS
/*
- '' 값도 반환한다
- 마지막은 분리자로 끝낸다
SELECT * FROM  dbo.[FN_SPLIT]('^1^^333^2^222^3^333^4^444^5^555^6^666^7^777^8^888^9^999','^') A
SELECT * FROM  dbo.[FN_SPLIT]('1^^22^^^^^^444444','^^') A
*/
BEGIN
 DECLARE
  @시작위치 SMALLINT
 ,@마지막위치 SMALLINT
 ,@카운터 SMALLINT
 ,@분리자크기 SMALLINT
 SELECT @분리자크기 = LEN(@분리자)

 IF RIGHT(@리스트,@분리자크기)!=@분리자
 BEGIN
  SET @리스트=@리스트+@분리자
 END
 SET @리스트=@분리자+@리스트
 
 SET @시작위치 = 1
 SELECT @마지막위치 = CHARINDEX (@분리자,@리스트 ,@시작위치+@분리자크기)
 SET @카운터 = 0
 WHILE (1=1)
 BEGIN
  SET @시작위치 = CHARINDEX (@분리자,@리스트 )
  SELECT @마지막위치 = CHARINDEX (@분리자,@리스트 ,@시작위치+@분리자크기)
        IF @마지막위치 <= 0 BREAK
  INSERT INTO @TB(VAL1) VALUES (SUBSTRING(@리스트,@시작위치+@분리자크기,@마지막위치-@시작위치-@분리자크기))
  SELECT @리스트 = STUFF(@리스트,@시작위치,@분리자크기,'')
  SET @카운터 = @카운터 + 1
 END
 RETURN
END

 

사용 예시

 SELECT * FROM FN_SPLIT('1^2^3^^5^444444','^')

 결과

   

 

테이블형 반환 함수이므로 SELECT * FROM 절에  하나의 테이블처럼 사용하면 됩니다.

클라이언트단에서 특정 순서를 가지고 데이터를 문자열로 이어붙여서 파라메터로 넘기고,
이걸 서버단에서 Split으로 쪼개서 사용할 수 있습니다.


물론 2단 이상 트리구조를 가진 데이터라면 XML로 주고받고 하는게 더 좋겠죠.
하지만 간단한 부분을 처리함에 있어서는 상당히 유용함이 분명합니다.

다만, 성능이 그리 뛰어난 함수는 아니므로 수천개 이상 되는 문자열이라면 이 FN_SPLIT함수 외에 다른방법을 사용하는 것을 추천드립니다.
사실 요즘 서버 성능이 워낙 좋아져서,, 이런 함수 좀 돌린다고 해서 무리가 가거나 부하가 많이 걸릴 일은 별로 없습니다만.. ㅎㅎ

 

 

 

 

2. 문자열을 SPLIT한 후 그룹핑하자 #1:  FN_SPLIT_GRP

   SELECT * FROM FN_SPLIT_GRP(@문자열,@구분자,@그룹수)


다음은 자주사용되지는 않지만 Split많으로 뭔가 살짝 아쉬울 때 사용할 수 있는 함수입니다.
예제를 보시는게 가장 이해가 쉬울 것 같습니다.

 

CREATE  FUNCTION [dbo].[FN_SPLIT_GRP]
(
@리스트 VARCHAR(MAX)
,@분리자 VARCHAR(1)
,@그룹수 SMALLINT
)

RETURNS @TB TABLE
( 
POS int IDENTITY PRIMARY KEY,
VAL1 varchar(500) default '',
VAL2 varchar(500) default '',
VAL3 varchar(500) default '',
VAL4 varchar(500) default '',
VAL5 varchar(500) default '',
VAL6 varchar(500) default '',
VAL7 varchar(500) default '',
VAL8 varchar(500) default '',
VAL9 varchar(500) default ''
)
AS
/*
1^2^3^4^5^6^7^8^9^111^222^333^444^555^666^777^888^999^:한 요소의 집합들로 이루어짐( GROUP형)
====>
1^111^
2^222^
3^333^
4^444^

마지막은 항상 분리자
SELECT * FROM  dbo.[FN_SPLIT_GRP]('1^2^3^4^5^6^7^8^9^111^222^333^444^555^666^777^888^999^','^',1) A
SELECT * FROM  dbo.[FN_SPLIT_GRP]('1^2^3^4^5^6^7^8^9^A^AA^AAA^AAAA^AAAAA^AAAAAA^AAAAAAA^AAAAAAAA^AAAAAAAAA^','^',2) A
SELECT * FROM  dbo.[FN_SPLIT_GRP]('1^2^3^4^5^6^7^8^9^111^222^333^444^555^666^777^888^999^1111^2222^3333^4444^5555^6666^7777^8888^9999^','^',3) A
*/
BEGIN


	DECLARE @RETURNSTR VARCHAR(MAX)
	

	DECLARE @TEM_STR1 varchar(500)
	DECLARE @TEM_STR2 varchar(500)
	DECLARE @TEM_STR3 varchar(500)
	DECLARE @TEM_STR4 varchar(500)
	DECLARE @TEM_STR5 varchar(500)
	DECLARE @TEM_STR6 varchar(500)
	DECLARE @TEM_STR7 varchar(500)
	DECLARE @TEM_STR8 varchar(500)
	DECLARE @TEM_STR9 varchar(500)

	SET @TEM_STR1=''
	SET @TEM_STR2=''
	SET @TEM_STR3=''
	SET @TEM_STR4=''
	SET @TEM_STR5=''
	SET @TEM_STR6=''
	SET @TEM_STR7=''
	SET @TEM_STR8=''
	SET @TEM_STR9=''

	---- 마지막 분리자가 없으면 분리자를 추가한다
	IF RIGHT(@리스트,1)!=@분리자
	BEGIN
		SET @리스트=@리스트+@분리자
	END


	DECLARE
	 @시작위치 SMALLINT
	,@마지막위치 SMALLINT
	,@카운터 SMALLINT

	SET @시작위치 = 0
	SET @마지막위치 = -1
	SET @카운터 = 0
	
	IF (@그룹수=1)
	BEGIN
	    WHILE  CHARINDEX (@분리자,@리스트 ,@시작위치+1) > 0
	    BEGIN

			    SET @마지막위치 = CHARINDEX (@분리자,@리스트 ,@시작위치+1)
    	
			    SELECT @TEM_STR1=SUBSTRING (@리스트 ,@시작위치+1,@마지막위치- @시작위치-1)
		        INSERT INTO @TB(VAL1) VALUES (@TEM_STR1)

			    SET @시작위치 = CHARINDEX (@분리자,@리스트 ,@마지막위치)

			    SET @카운터 = @카운터+1

	    END
	END




	IF (@그룹수>1)
	BEGIN
/*
@TT에 일단 모든 데이터를 입력한 후
===> 그룹별로 한 컬럼씩 순서대로 배치한다
*/

        --테이블 변수 선언
        Declare @TT table
        (
        POS int IDENTITY (1,1) PRIMARY KEY,
        VAL1 varchar(500)
        )

        Declare @TTL_ARRAYNUM INT
        Declare @GROUP_ARRAYNUM INT

	    WHILE  CHARINDEX (@분리자,@리스트 ,@시작위치+1) > 0
	    BEGIN

			    SET @마지막위치 = CHARINDEX (@분리자,@리스트 ,@시작위치+1)

			    SELECT @TEM_STR1=SUBSTRING (@리스트 ,@시작위치+1,@마지막위치- @시작위치-1)
		        INSERT INTO @TT(VAL1) VALUES (@TEM_STR1)

			    SET @시작위치 = CHARINDEX (@분리자,@리스트 ,@마지막위치)

			    SET @카운터 = @카운터+1
	    END

        SELECT @TTL_ARRAYNUM=COUNT(*) FROM @TT
        SET @GROUP_ARRAYNUM=@TTL_ARRAYNUM/@그룹수


        Declare @CURR_NUM INT

        IF (@그룹수>=2)
	    BEGIN
	    /*
	    일단 그룹갯수 만큼 VAL1 컬럼 데이터 입력
	    */
	    
			INSERT INTO @TB(VAL1) 
			SELECT VAL1 
			FROM @TT A
			WHERE
            A.POS <= (@GROUP_ARRAYNUM)*1



			SET @CURR_NUM=2
			UPDATE AA SET VAL2=A.VAL1 
            FROM (
					SELECT (A.POS-((@GROUP_ARRAYNUM)*(@CURR_NUM-1))) POS, VAL1 
					FROM @TT A
					WHERE
						A.POS > (@GROUP_ARRAYNUM)*(@CURR_NUM-1)
						AND
						A.POS <= (@GROUP_ARRAYNUM)*(@CURR_NUM)
					) A,@TB AA
            WHERE
            A.POS =AA.POS

	    END







        IF (@그룹수>=3)
	    BEGIN
	    /*
	    VAL3 컬럼 데이터 업뎃
	    */

			SET @CURR_NUM=3
			UPDATE AA SET VAL3=A.VAL1 
            FROM (
					SELECT (A.POS-((@GROUP_ARRAYNUM)*(@CURR_NUM-1))) POS, VAL1 
					FROM @TT A
					WHERE
						A.POS > (@GROUP_ARRAYNUM)*(@CURR_NUM-1)
						AND
						A.POS <= (@GROUP_ARRAYNUM)*(@CURR_NUM)
					) A,@TB AA
            WHERE
            A.POS =AA.POS

	    END


        IF (@그룹수>=4)
	    BEGIN
	    /*
	    VAL4 컬럼 데이터 업뎃
	    */

			SET @CURR_NUM=4
			UPDATE AA SET VAL4=A.VAL1 
            FROM (
					SELECT (A.POS-((@GROUP_ARRAYNUM)*(@CURR_NUM-1))) POS, VAL1 
					FROM @TT A
					WHERE
						A.POS > (@GROUP_ARRAYNUM)*(@CURR_NUM-1)
						AND
						A.POS <= (@GROUP_ARRAYNUM)*(@CURR_NUM)
					) A,@TB AA
            WHERE
            A.POS =AA.POS

	    END


        IF (@그룹수>=5)
	    BEGIN

			SET @CURR_NUM=5
			UPDATE AA SET VAL5=A.VAL1 
            FROM (
					SELECT (A.POS-((@GROUP_ARRAYNUM)*(@CURR_NUM-1))) POS, VAL1 
					FROM @TT A
					WHERE
						A.POS > (@GROUP_ARRAYNUM)*(@CURR_NUM-1)
						AND
						A.POS <= (@GROUP_ARRAYNUM)*(@CURR_NUM)
					) A,@TB AA
            WHERE
            A.POS =AA.POS

	    END


        IF (@그룹수>=6)
	    BEGIN

			SET @CURR_NUM=6
			UPDATE AA SET VAL6=A.VAL1 
            FROM (
					SELECT (A.POS-((@GROUP_ARRAYNUM)*(@CURR_NUM-1))) POS, VAL1 
					FROM @TT A
					WHERE
						A.POS > (@GROUP_ARRAYNUM)*(@CURR_NUM-1)
						AND
						A.POS <= (@GROUP_ARRAYNUM)*(@CURR_NUM)
					) A,@TB AA
            WHERE
            A.POS =AA.POS

	    END


        IF (@그룹수>=7)
	    BEGIN

			SET @CURR_NUM=7
			UPDATE AA SET VAL7=A.VAL1 
            FROM (
					SELECT (A.POS-((@GROUP_ARRAYNUM)*(@CURR_NUM-1))) POS, VAL1 
					FROM @TT A
					WHERE
						A.POS > (@GROUP_ARRAYNUM)*(@CURR_NUM-1)
						AND
						A.POS <= (@GROUP_ARRAYNUM)*(@CURR_NUM)
					) A,@TB AA
            WHERE
            A.POS =AA.POS

	    END


        IF (@그룹수>=8)
	    BEGIN

			SET @CURR_NUM=8
			UPDATE AA SET VAL8=A.VAL1 
            FROM (
					SELECT (A.POS-((@GROUP_ARRAYNUM)*(@CURR_NUM-1))) POS, VAL1 
					FROM @TT A
					WHERE
						A.POS > (@GROUP_ARRAYNUM)*(@CURR_NUM-1)
						AND
						A.POS <= (@GROUP_ARRAYNUM)*(@CURR_NUM)
					) A,@TB AA
            WHERE
            A.POS =AA.POS

	    END


        IF (@그룹수>=9)
	    BEGIN

			SET @CURR_NUM=9
			UPDATE AA SET VAL9=A.VAL1 
            FROM (
					SELECT (A.POS-((@GROUP_ARRAYNUM)*(@CURR_NUM-1))) POS, VAL1 
					FROM @TT A
					WHERE
						A.POS > (@GROUP_ARRAYNUM)*(@CURR_NUM-1)
						AND
						A.POS <= (@GROUP_ARRAYNUM)*(@CURR_NUM)
					) A,@TB AA
            WHERE
            A.POS =AA.POS

	    END


	END



	RETURN

END

 

사용 예시

SELECT * FROM FN_SPLIT_GRP
('1^2^3^4^5^6^7^8^9^111^222^333^444^555^666^777^888^999
^1111^2222^3333^4444^5555^6666 ^7777^8888^9999^','^',3)

결과

 

 

FN_SPLIT_GRP 에는 매개변수가 하나 더 추가됩니다.

 

‘그룹수’  라는 매개변수로, 1~9까지 쓸 수 있습니다.(그 이상이 필요하다면 다른 방법을 강구하세요.. ㅎㅎ)


 

‘그룹수’가 들어감으로 인해서 이 함수는 최대 9 x n 형태의 테이블을 반환할 수 있게 됩니다.
위 예시에서 보시다시피 문자열은 다음과 같은 3 그룹으로 나눌 수 있습니다.
1^2^3^4^5^6^7^8^9^
111^222^333^444^555^666^777^888^999^
1111^2222^3333^4444^5555^6666^7777^8888^9999^

 

그리고 이것을 각각 SPLIT 해서 하나의 열(Column)로 추가해 주는 것이지요.


물론 성능을 고려하고 만들어진 함수는 아닙니다. 어디에 응용하는가는 각자에게 달려 있음을…ㅋㅋ

 

 

 

3. 문자열을 SPLIT한 후 그룹핑하자 #2: FN_SPLIT_SET

SELECT * FROM FN_SPLIT_SET(@문자열,@구분자,@그룹수)

 

이 함수 역시 문자열 단순 쪼개기에서 한단계 더 나아갑니다.
개인적으로는 FN_SPLIT FN_SPLIT_SET을 가장 많이 사용하게 되더군요.
이것도 역시 사용예시를 보시면 이해가 되실 것 같네요.

 

CREATE  FUNCTION [dbo].[FN_SPLIT_SET]
(
@리스트 VARCHAR(MAX)
,@분리자 VARCHAR(1)
,@그룹수 SMALLINT
)

RETURNS @TB TABLE
( 
POS int IDENTITY PRIMARY KEY,
VAL1 varchar(400),
VAL2 varchar(400),
VAL3 varchar(400),
VAL4 varchar(400),
VAL5 varchar(400),
VAL6 varchar(400),
VAL7 varchar(400),
VAL8 varchar(400),
VAL9 varchar(400)
)
AS
/*
1^111^2^222^3^333^4^444^:앞에서 부터 완결된 셋( SET형)
====> 
1^111^
2^222^
3^333^
4^444^

마지막은 항상 분리자

SELECT * FROM  dbo.[FN_SPLIT_SET]('1^111^2^222^3^333^4^444^5^555^6^666^7^777^8^888^9^999^','^',1) A

SELECT * FROM  dbo.[FN_SPLIT_SET]('1^AAA^2^BBB^3^CCC^4^DDD^5^EEE^6^FFF^7^GGG^8^HHH^9^III^','^',2) A

SELECT * FROM  dbo.[FN_SPLIT_SET]('1^111^2^222^3^333^4^444^5^555^6^666^7^777^8^888^9^999^','^',3) A

SELECT * FROM  dbo.[FN_SPLIT_SET]('1^111^2^222^3^333^4^444^5^555^6^666^7^777^8^888^9^999^','^',4) A

SELECT * FROM  dbo.[FN_SPLIT_SET]('1^111^2^222^3^333^4^444^5^555^6^666^7^777^8^888^9^999^','^',4) A

SELECT * FROM  dbo.[FN_SPLIT_SET]('1^111^2^222^3^333^4^444^5^555^6^666^7^777^8^888^9^999^','^',5) A

*/
BEGIN


	DECLARE @RETURNSTR VARCHAR(400)
	

	DECLARE @TEM_STR1 varchar(400)
	DECLARE @TEM_STR2 varchar(400)
	DECLARE @TEM_STR3 varchar(400)
	DECLARE @TEM_STR4 varchar(400)
	DECLARE @TEM_STR5 varchar(400)
	DECLARE @TEM_STR6 varchar(400)
	DECLARE @TEM_STR7 varchar(400)
	DECLARE @TEM_STR8 varchar(400)
	DECLARE @TEM_STR9 varchar(400)

	SET @TEM_STR1=''
	SET @TEM_STR2=''
	SET @TEM_STR3=''
	SET @TEM_STR4=''
	SET @TEM_STR5=''
	SET @TEM_STR6=''
	SET @TEM_STR7=''
	SET @TEM_STR8=''
	SET @TEM_STR9=''

	---- 마지막 분리자가 없으면 분리자를 추가한다
	IF RIGHT(@리스트,1)!=@분리자
	BEGIN
		SET @리스트=@리스트+@분리자
	END


	DECLARE
	 @시작위치 INT
	,@마지막위치 INT
	,@카운터 INT

	SET @시작위치 = 0
	SET @마지막위치 = -1
	SET @카운터 = 0
	

	WHILE  CHARINDEX (@분리자,@리스트 ,@시작위치+1) > 0
	BEGIN

			SET @마지막위치 = CHARINDEX (@분리자,@리스트 ,@시작위치+1)
	
			IF (@카운터 % @그룹수=0)
			BEGIN
				SET @TEM_STR1=''
				SET @TEM_STR2=''
				SET @TEM_STR3=''
				SET @TEM_STR4=''
				SET @TEM_STR5=''
				SET @TEM_STR6=''
				SET @TEM_STR7=''
				SET @TEM_STR8=''
				SET @TEM_STR9=''

			END

			IF (@카운터 % @그룹수=0)
			BEGIN
				SELECT @TEM_STR1=SUBSTRING (@리스트 ,@시작위치+1,@마지막위치- @시작위치-1)

			END

			IF (@카운터 % @그룹수=1)
			BEGIN
				SELECT @TEM_STR2=SUBSTRING (@리스트 ,@시작위치+1,@마지막위치- @시작위치-1)
			END

			IF (@카운터 % @그룹수=2)
			BEGIN
				SELECT @TEM_STR3=SUBSTRING (@리스트 ,@시작위치+1,@마지막위치- @시작위치-1)
			END

			IF (@카운터 % @그룹수=3)
			BEGIN
				SELECT @TEM_STR4=SUBSTRING (@리스트 ,@시작위치+1,@마지막위치- @시작위치-1)

			END

			IF (@카운터 % @그룹수=4)
			BEGIN
				SELECT @TEM_STR5=SUBSTRING (@리스트 ,@시작위치+1,@마지막위치- @시작위치-1)

			END

			IF (@카운터 % @그룹수=5)
			BEGIN
				SELECT @TEM_STR6=SUBSTRING (@리스트 ,@시작위치+1,@마지막위치- @시작위치-1)

			END

			IF (@카운터 % @그룹수=6)
			BEGIN
				SELECT @TEM_STR7=SUBSTRING (@리스트 ,@시작위치+1,@마지막위치- @시작위치-1)

			END

			IF (@카운터 % @그룹수=7)
			BEGIN
				SELECT @TEM_STR8=SUBSTRING (@리스트 ,@시작위치+1,@마지막위치- @시작위치-1)

			END

			IF (@카운터 % @그룹수=8)
			BEGIN
				SELECT @TEM_STR9=SUBSTRING (@리스트 ,@시작위치+1,@마지막위치- @시작위치-1)

			END

			IF ((@카운터 % @그룹수)=@그룹수-1)
			BEGIN
			    INSERT INTO @TB(VAL1,VAL2,VAL3,VAL4,VAL5,VAL6,VAL7,VAL8,VAL9) VALUES (@TEM_STR1,@TEM_STR2,@TEM_STR3,@TEM_STR4,@TEM_STR5,@TEM_STR6,@TEM_STR7,@TEM_STR8,@TEM_STR9)

			END

			SET @시작위치 = CHARINDEX (@분리자,@리스트 ,@마지막위치)

			SET @카운터 = @카운터+1

	END

	RETURN

END

 

 

사용 예시

SELECT * FROM FN_SPLIT_SET
('1^AAA^2^BBB^3^CCC^4^DDD^5^EEE^6^FFF^7^GGG^8^HHH^9^III^','^',2)

결과

 


FN_SPLIT_GRP와 마찬가지로 ‘그룹수’ 매개변수가 추가되고, 역시 1~9까지만 사용 가능합니다.
문자열과 결과를 보시면 쉽게 이해가 가시리라 사료됩니다.


웹이던 어플리케이션이던,, 9컬럼 이내의 테이블 형태를 DB에 고스란히 집어 넣으려 할때에 사용하면 매우 편리하죠.

 

 

예를 하나 들어볼께요.

 

 

이런 모양의 데이터를 DB에 고대~~로 집어넣을 일이 있을 때, SPLIT_SETSPLIT_GRP 를 각각 이용한다면

 

FN_SPLIT_GRP

 

SELECT * FROM FN_SPLIT_GRP('언어,수리,외국어,사회탐구,사회탐구,사회탐구,언어,수리나,외국어,한국지리,경제지리,세계사,95,90,85,45,40,35,123,120,100,60,55,50,95,91,80,80,70,50,1,2,3,2,4,6',',',6)

 


 

FN_SPLIT_SET


SELECT * FROM FN_SPLIT_SET('언어,언어,95,123,95,1,수리,수리나,90,120,91,2,외국어,외국어,85,100,80,3,사회탐구,한국지리,45,60,80,2,사회탐구,경제지리,40,55,70,4,사회탐구,세계사,35,50,50,6',',',6)

 

둘 다 결과값은 동일하지만 문자열을 이어 붙이는 순서가 다릅니다.

GRPSET 둘 중 어느걸 써도 상관이 없다는 말이죠.


테이블에 바로 인서트 하려면 이렇게 하면 되겠죠.

 

INSERT INTO TBL
SELECT
VAL1 AS 교과
,VAL2 AS 과목
,VAL3 AS 원점수
,VAL4 AS 표준점수
,VAL5 AS 백분위
,VAL6 AS 등급
FROM FN_SPLIT_SET('언어,언어,95,123,95,1,수리,수리나,90,120,91,2,외국어,외국어,85,100,80,3,사회탐구,한국지리,45,60,80,2,사회탐구,경제지리,40,55,70,4,사회탐구,세계사,35,50,50,6',',',6)
ORDER BY POS

 

실제로는 점수테이블을 저런식으로 넣진 않겠지만 예를들기 위해서 써봤습니다.
언제나 응용은 하기 나름이지요..ㅎ

잘 사용하시길...

   

Microsoft SQL Server 2012 Express 에는 새롭게 LocalDB 제품이 포함되었다. 

 

 Express 제품군의 새로운 버전인 LocalDB는 모든 프로그래밍 기능을 포함하지만 사용자 모드에서 실행되며 구성이 필요 없는 빠른 설치가 가능하고 필수 구성 요소가 적은 새로운 경량 버전의 Express입니다. 이 제품을 사용하면 코드를 통해 쉽게 데이터베이스를 만들고 데이터베이스에 대한 작업을 수행할 수 있습니다. 이 제품은 Visual Studio와 같은 데이터베이스 개발 도구 및 응용 프로그램과 함께 제공되거나 로컬 데이터베이스가 필요한 응용 프로그램에 포함될 수 있습니다.

 

로컬에서만 사용할 응용 프로그램에 DB가 필요할 때,
Access나 Excel 파일을 DB 대용으로 사용하기엔 좀 버거울 경우 사용하면 좋을 것 같다.

 

LocalDB는 무료이면서도 복잡한 설치과정이 없고 기존 MS-SQL Server의 MDF와의 호환성도 완벽하게 보장되는 로컬 데이터베이스이다.

 

가장 좋은 점은 뭐니뭐니 해도 기존의 MS-SQL 파일 포맷인 MDF와 LDF를 100% 그대로 가져다 쓸수 있다는 점일 것이다.

 

   

또한 Express Edition보다 설치과정이 쉽고 간단하다.

약 33MB(64bit 기준) 정도의 SqlLocalDB.msi 파일과 5MB(64bit) 정도의 SQL Native Client 만 설치하면 일반 PC에서도 강력한 MS-SQL Server의 기능을 마음껏 활용할 수 있게 된다.

단, LocalDB 자체가 윈도우XP는 지원하지 않기 때문에... Vista 이상의 운영체제를 사용해야 한다.

 

설치파일은 MS 공식 다운로드 사이트에서 받을 수 있다.
http://www.microsoft.com/ko-kr/download/details.aspx?id=29062

   

 

설치 자체는 매우 간단하다. 다운로드 받은 파일을 실행하면 별다른 과정 없이 설치가 완료된다.

 

기존의 서버류 제품들과는 다르게, LocalDB는 작업 인스턴스가 실행되지도 않고, 서비스 등록을 하지도 않는다.

응용 프로그램에서 LocalDB 커넥션 스트링을 호출하는 순간에 sqlservr.exe 프로세스가 자동으로 실행되면서 DB와 연결이 된다.

이로 인해 PC를 켜고나서 최초로 LocalDB 응용 프로그램을 실행할 때에는 약간의 지연시간이 발생하게 된다.

 

MSDN에도 이 부분에 대해 노트를 해 두었다.

 

참고

컴퓨터에서 사용자가 처음으로 LocalDB에 연결하려고 시도하는 경우 자동 인스턴스가 생성되고 시작되어야 합니다. 인스턴스를 만드는 추가 시간으로 인해 시간 초과 메시지와 함께 연결 시도가 실패할 수 있습니다. 이 경우 만들기 프로세스가 완료되도록 몇 초 정도 기다린 후에 다시 연결하십시오.

 

 

뭐 실행해본 결과 체감시간에 그다지 큰 변화는 없었다. Sqlservr.exe 파일은 C:\Program Files\Microsoft SQL Server\110\LocalDB\Binn 에 설치가 되어 있으며, 크기가 187KB로 상당히 컴팩트하다.

 

최초 실행시 인스턴트 생성으로 인한 로스나 패널티는… 무시해도 되는 수준이라고 생각된다.

 

 

 

이제 이 상태에서는 Visual Stutio를 통한 .NET 개발을 진행할 수 있다.

(다만, .net Framework가 SqlClient 제공자를 알고 있어야 커넥션이 가능하기 때문에 닷넷프레임워크 4.0을 업데이트 해줘야 한다.)

Microsoft .NET Framework 4용 업데이트 4.0.2 - 런타임 업데이트(KB2544514)

 

 

VS에서 localdb를 커넥션하고 이용하는 방법은 마이크로소프트 2012년 1월호에 자세히 나와 있어서 생략한다.(이곳에서 확인 가능)

 

 

여기서는 일반적인 ADO를 이용해 접속하는 방법에 대해 알아보겠다.

 

VS에서 닷넷 프레임워크를 4.0.2로 업데이트 한 것과 마찬가지로, 일반 ADO에서 커넥션을 하려면 공급자를 설치해 줘야 한다.

localDB는 [sqlncli11] 공급자가 필요한데, SQL Server Native Client 패키지 안에 포함된 sqlncli.msi 를 설치하면 된다.

아래 링크에서 아래쪽으로 쭉 내려가다 보면 Native Client가 보인다.

 

http://www.microsoft.com/ko-kr/download/details.aspx?id=29065

 

Microsoft® SQL Server® 2012 Native Client

Microsoft SQL Server Native Client(SQL Server Native Client)는 SQL OLE DB Provider와 SQL ODBC 드라이버를 포함하는 단일 DLL(동적 연결 라이브러리)입니다. 여기에는 네이티브 코드 API(ODBC, OLE DB 및 ADO)를 사용하여 Microsoft SQL Server 2005, 2008, 2008 R2 및 SQL Server 2012에 연결하는 응용 프로그램에 대한 런타임 지원이 포함되어 있습니다. SQL Server Native Client는 새로운 SQL Server 2012 기능 활용에 필요한 새로운 응용 프로그램을 작성하거나 기존 응용 프로그램을 향상시키는 데 사용됩니다. SQL Server Native Client에 대한 이러한 재배포 가능 설치 관리자는 새로운 SQL Server 코드 이름 'Denali' 기능을 활용하기 위해 런타임에 필요한 클라이언트 구성 요소를 설치하고, SQL Server Native Client API를 사용하는 응용 프로그램을 개발하는 데 필요한 헤더 파일을 설치하기도 합니다.

X86 패키지(sqlncli.msi)
X64 패키지(sqlncli.msi)

 

X86 패키지는 약 3.2MB, x64 패키지는 5.1MB이다. SqlLocalDB.msi와 마찬가지로 설치는… 별다른 설정 없이 그냥 하면 된다. SSMS만 깔려고 해도 이것저것 서비스팩이며 뭐며 귀찮은데.. Express버전도 설치할라치면 온갖 설정과 계정 등 복잡한 요소가 많은데.. 정말 단순해서 좋다.

 

고객들에게 프로그램을 제공해 줄 때 이것저것 잔뜩 설치하라고 하지 않아도 되니까..

 

여기까지 설치가 되었으면 이제 ADO 커넥션을 위한 모든 준비가 완료되었다.

 

 

 

VB등의 프로그램에서 기존에 OLEDB로 SQL Server에 연결하던 대로 참조를 걸어준다. – (Microsoft ActiveX Data Object 2.6 Library – 2.8도 있고 6.0도 있지만 난 그냥 그나마 오류가 덜 나고 하위호환이 잘 되는 것 같은 2.6을 사용한다.. 사실 별 차이를 모르겠음 -..-)

 

 

 

커넥션 스트링을 만들고 접속해본다.

Dim mydb As ADODB.Connection

Dim myrs As ADODB.Recordset

 

mydsn = ""

mydsn = mydsn & "Provider=SQLNCLI11;Data Source=(localdb)\v11.0;Integrated Security=SSPI;"

 

Set mydb = New ADODB.Connection

mydb.Open (mydsn)

 

mydb.state 를 찍어봐서 1이 나오면 정상적으로 연결된 것이다.

 

나의 경우, Sql Server에서 돌리고 있던 MDF와 LDF를 그대로 내 하드디스크로 복사해 왔다.

기존에 SQL SERVER를 커넥트 하던 솔루션에서 커넥션 스트링만 아래처럼 변경해 준 후 돌려보니 에러메시지 한줄 없이 그대로 LocalDB를 이용해 정상 실행 되었다.

 

mydsn = ""

mydsn = mydsn & "Provider=SQLNCLI11;Data Source=(localdb)\v11.0;Integrated Security=SSPI;"

mydsn = mydsn & "AttachDbFileName=C:\LocalDBFile\ LEEJUNGNAM_ACA.mdf"

 

정말 완벽하게 100% 호환이 되는것일까? 해서 SSMS로 접속을 해 보았다.

MSDN에서 본 대루.. 서버이름은 (localdb)\v11.0

저 서버이름의 v11.0은 명명된 네임스페이스에 속하는 특수한 인스턴스 이름 패턴으로, 꼭 저대로 사용해야만 한다고 한다.

어쨌든… 접속.

 

안된다. SSMS 2008에서는 (localdb)\v11.0 이라는 서버의 존재를 인식하지 못한다.

 

해서 SSMS 2012버전을 설치했다. LocalDB 를 다운로드 했던 페이지에 같이 있다.

헌데.. SSMS 2008은 200메가 남짓이더니 2012는 600메가가 넘는 파일을 다운로드 하네.. 괜히 싫다..

 

아무튼 설치를 마치고 다시 접속을 시도.

 

연결이 잘 된다. 개체탐색기를 보니 아래와 같이 한번 커넥션 했던 개체들에 대해서 자동으로 데이터베이스 등록이 되어있다.

저장 프로시저와 함수, 테이블구조, 데이터, id 시드값, 심지어 다이어그램까지 모든 환경이 100% 그대로 호환된다.

 

헌데, SQL Server를 이용해서 잘 만들어져 있는 프로그램을 왜 굳이 로컬DB로 바꾸는 것일까?

고객은 자신의 데이터가 자기의 PC가 아닌 다른 곳에 존재하고, 누군가가 그걸 들여다볼 수 있다는 사실에 꺼림칙해 한다.

그렇다고, 매 고객마다 비싼 MS-SQL Server를 호스팅받아서 마련하라고 할 수도 없는 노릇이다.

고객이 MS-SQL Server를 마련한다고 해도, 어차피 프로그램이 구동되려면 커넥션스트링에 DB 아이디와 비번이 들어가야 하므로 개발/공급한 사람은 언제든지 DB에 접근할 수 있다.

또 개발해준 입장에서는 DB 스키마나 프로시저 등 고유한 솔루션의 소스코드까지 몽땅 권리를 넘겨준 것이 아니므로,, 고객의 SQL Server에 이 프로그램을 구축해주기가 찜찜한건 마찬가지다.

또 이상하게도, 자기PC에 파일을 보관하는 것보다 IDC에 존재하는 SQL Server의 데이터 안정성이 훨씬더 높은데도 불구하고, 그 호스팅하는 업체가 망하거나(-_-), DB가 따운되면 프로그램을 이용하지 못하는게 아니냐는 걱정을 한다.. 거 참..-_-;;

그래서.. LocalDB를 이용하면 위와 같은 문제에 대해서 고객과 개발사 양쪽이 어느정도 합의가능한 도출점을 찾기가 쉬워진다.

고객은 자신이 원하는 바 대로 로컬PC에 자신들의 데이터를 보존할 수 있고,(물론 안전성 측면에서는 몇배 취약하지만..)

외부의 다른 접근이 불가능 하므로 보안을 유지할 수도 있다.

개발사의 입장에서는 개발이 용이한 SQL 솔루션을 그대로 유지하고 별다른 마이그레이션이나 작업을 할 필요가 없다.

물론 고객이 SSMS를 설치해서 DB구조나 소스를 볼 수는 있다. localDB는 기본적으로 윈도우 사용자인증이기 때문에..

하지만 권리를 넘겨 준 만큼 책임에서도 자유로워 질 수 있다.

고객이 DB를 접근하고, 제어하는 순간 그 뒤 발생하는 모든 오류나 문제에 대해서 책임지지 않아도 된다.

데이터 보관 및 백업에 대한 책임도 없다.

 

매 번 localDB를 사용할 일은 없겠지만,

필요에 따라 사용하기엔 더할나위없이 좋은 기능이라 생각된다.


오류 :

Microsoft OLE DB Provider for SQL Server 오류 '80004005'
사용자 'sa'의 계정이 현재 잠겨 있으므로 로그인하지 못했습니다. 시스템 관리자가 잠금을 해제할 수 있습니다.

해결 :

1) 쿼리 실행
ALTER LOGIN sa WITH PASSWORD = '1234' UNLOCK
go
2) 윈도우 인증으로 SQL 접속

[보안] - [로그인] - [sa : 속성] 에서 "암호 정책 강제 적용", "암호 만료 강제 적용" 체크 해제

김정선님의 쿼리 튜닝 글을 보고  몇가지 실제사례에 넣어본 결과.

자료 : 5천ROW정도의 문제테이블 (MQ_Q_INFO)
         300ROW정도의 유형테이블_카테고리  (MQ_Q_TYPE)
         600ROW정도의  기존출제문제 히스토리테이블 (MQ_Q_HST)
         200ROW정도의 회원테이블(MQ_MEM)
        ※ FN_SPLIT은 문자열을 분리해서 POS값과 VALUE값을 테이블로 반환해주는 사용자함수.

액션 : 문제테이블에서 이번에 출제할 대상문항을 여러 조건에 의해 추려내야한다.
조건1. 정해진 유형에 소속된 문항만 추출. (20010^20210^ .... 문자열을 SPLIT 처리)
          유형은 10개를 선택할수도 있고 100개를 선택할수도 있다..
조건2. 과거 출제경력을 조회해서 최근 5회차에 출제된 문제를 배제시킨다.(TIMES로 관리됨)
조건3. 조건2와 더불어서  기존에 3회이상 출제됐었던 문제를 배제시킨다. (SETTIMES로 누적관리됨)

조건1(선택유형 내에서만 뽑는부분)은 걍 SPLIT해서 해당키값을 유형테이블에서 Q_TYPE_CD로 바꿔온다음 문제테이블에서 쪼인해버리는걸로 처리했다.
이렇게 해서 뽑았을때 5천ROW중 708문제가 대상문제로 추출됨.

조건2와 3은 UNION으로 묶고 DISTINCT해준다음 (90개문항이 배제대상문항으로 나옴)
그걸  김정선님의 차집합 스킬로 처리했다.. 최종 산출 row는 618개.

한번에 만들어진게 아니고 계속 조립식으로 만들다보니 너저분해지긴 했지만..
요점은  김정선님의 차집합 추천스킬의 결과를 보는게 목적이므로......


참고블로그 : http://blog.naver.com/visualdb/50028645471
여기서 3,4,5번만 가지고 테스트.


3. NOT IN + 상관서브쿼리+ TOP 1  (평가 : (Very) Good)

 SELECT TPB1.* FROM MQ_Q_INFO TPB1
 , ( SELECT A.Q_TYPE_CD
  FROM MQ_Q_TYPE A,
  (SELECT * FROM  dbo.[FN_SPLIT]('20010^20210^20700^20710^20720^20220^20230^20240^20250^20260^20270^20280^20290^20300','^')) B
  WHERE
  A.CTG2_CD = B.VAL1
 ) TPB2
 WHERE
 TPB1.Q_TYPE_CD = TPB2.Q_TYPE_CD
 
 AND TPB1.Q_NUM NOT IN (SELECT TOP 1 TPC1.Q_NUM FROM
       (
        SELECT DISTINCT(YY.Q_NUM) FROM 
         (
          SELECT Q_NUM FROM MQ_Q_HST
          WHERE STU_ID = '8701290008'
          AND SETTIMES > 3
          UNION
          SELECT Q_NUM FROM MQ_Q_HST
          WHERE STU_ID = '8701290008'
          AND TIMES > (SELECT MAX(TIMES)-5 FROM MQ_Q_HST WHERE STU_ID = '8701290008')
         ) YY
         ) TPC1
         WHERE TPC1.Q_NUM = TPB1.Q_NUM
         )
총실행시간
시도횟수 1 : 2220 ms
시도횟수 2~10 :  1087 ~ 1165  (평균 1097)



4. NOT EXISTS + 상관서브쿼리+ TOP 1 (평가 : Not bad )

 SELECT TPB1.* FROM MQ_Q_INFO TPB1
 , ( SELECT A.Q_TYPE_CD
  FROM MQ_Q_TYPE A,
  (SELECT * FROM  dbo.[FN_SPLIT]('20010^20210^20700^20710^20720^20220^20230^20240^20250^20260^20270^20280^20290^20300','^')) B
  WHERE
  A.CTG2_CD = B.VAL1
 ) TPB2
 WHERE
 TPB1.Q_TYPE_CD = TPB2.Q_TYPE_CD
 
 AND NOT EXISTS (SELECT TOP 1 TPC1.Q_NUM FROM
       (
        SELECT DISTINCT(YY.Q_NUM) FROM 
         (
          SELECT Q_NUM FROM MQ_Q_HST
          WHERE STU_ID = '8701290008'
          AND SETTIMES > 3
          UNION
          SELECT Q_NUM FROM MQ_Q_HST
          WHERE STU_ID = '8701290008'
          AND TIMES > (SELECT MAX(TIMES)-5 FROM MQ_Q_HST WHERE STU_ID = '8701290008')
         ) YY
         ) TPC1
         WHERE TPC1.Q_NUM = TPB1.Q_NUM
         )


총실행시간
시도횟수 1 : 145 ms
시도횟수 2~10 : 17ms ~ 60ms (평균 57.7ms)

 엄청빠르다 +_+

 

 

5. 상관 서브쿼리+ TOP 1 + IS NULL (평가 : 김정선의 서브쿼리 튜닝 필살기 (Very) Good)

 SELECT TPB1.* FROM MQ_Q_INFO TPB1
 , ( SELECT A.Q_TYPE_CD
  FROM MQ_Q_TYPE A,
  (SELECT * FROM  dbo.[FN_SPLIT]('20010^20210^20700^20710^20720^20220^20230^20240^20250^20260^20270^20280^20290^20300','^')) B
  WHERE
  A.CTG2_CD = B.VAL1
 ) TPB2
 WHERE
 TPB1.Q_TYPE_CD = TPB2.Q_TYPE_CD
 
 AND (SELECT TOP 1 TPC1.Q_NUM FROM
  (
   SELECT DISTINCT(YY.Q_NUM) FROM 
    (
     SELECT Q_NUM FROM MQ_Q_HST
     WHERE STU_ID = '8701290008'
     AND SETTIMES > 3
     UNION
     SELECT Q_NUM FROM MQ_Q_HST
     WHERE STU_ID = '8701290008'
     AND TIMES > (SELECT MAX(TIMES)-5 FROM MQ_Q_HST WHERE STU_ID = '8701290008')
    ) YY
    ) TPC1
    WHERE TPC1.Q_NUM = TPB1.Q_NUM
    ) IS NULL

총실행시간
시도횟수 1 : 202 ms
시도횟수 2~10 : 175ms ~ 217ms (평균 186.5ms)









결론 : 다른부분은 전혀 튜닝도 안되고 구조도 제멋대로인 부분이지만   차집합구하기 부분만 놓고 봤을때는 어떤 형태의 쿼리에 얹히느냐에 따라  성능의 우위가 바뀔수도 있음을 확인할수 있다.

똑같은작업을 할때 NOT IN이냐 NOT EXISTS냐에 따라 20배에 가까운 속도를 단축할수 있다는데에 새삼 놀랍다....



쓸데없는 DISTINCT를 뺏더니 57.7ms에서 36ms로  또 급 단축되네..ㅎㅎㅎ

+ Recent posts