728x90

☞ CTE(Common Table Expression)란?

임시로 이름이 지정된 결과 집합이다.

하위에서 서브쿼리 형식으로 사용할 부분을 미리 정의해 놓은것이다.


☞ 구문

WITH CTE명(컬럼, 컬럼) AS
(
    CTE 쿼리 정의

)
CTE를 포함하여 실행할 쿼리


☞ 주의

CTE를 정의하면 쿼리에서 여러 차례 참조할 수 있다.

참조 여부와 상관없이 CTE 정의 다음에 외부 쿼리가 나와야 한다.

하나의 WITH절에 여러개의 CTE를 정의할수 있다. (컴마로 구분)

이전에 정의된 CTE를 참조하는 CTE를 정의할 수 있다.

그러나 이후에 정의된 CTE는 참조할 수 없다. (훔.... 예제에서 다시 설명하겠다!)


☞ 사용 예제

1. 한개의 CTE 정의

USE AdventureWorks;
GO

WITH DirReps_CTE(ManagerID, DirectReports) AS
(
    SELECT ManagerID, COUNT(*)
    FROM HumanResources.Employee AS e
    WHERE ManagerID IS NOT NULL
    GROUP BY ManagerID
)
SELECT ManagerID, DirectReports

FROM DirReps_CTE

WHERE ManagerID <= 50

ORDER BY ManagerID;


CTE를 정의하고 하위에서 검색조건, 정렬조건을 다시 지정하거나

데이터를 가공하여 조회 할 수 있다.

사용자 삽입 이미지

 
 
 




2. 두개 이상의 CTE 정의

USE AdventureWorks;
GO

WITH
EmpOrdersCTE(EmployeeID, Cnt)
AS
(
  SELECT EmployeeID, COUNT(*)
  FROM Purchasing.PurchaseOrderHeader

  GROUP BY EmployeeID
),
MinMaxCTE(MN, MX, Diff)
AS
(
  SELECT MIN(Cnt), MAX(Cnt), MAX(Cnt)-MIN(Cnt)
  FROM EmpOrdersCTE
)
SELECT * FROM MinMaxCTE


EmpOrdersCTE : EmployeeID별 주문수 목록

MinMaxCTE : EmpOrdersCTE를 참조하여 최소, 최대 주문수와 차이


마지막 쿼리에서 MinMaxCTE를 참조하여 최소, 최대, 차이를 구할수 있다.


사용자 삽입 이미지
 



이전에 정의된 CTE는 하위에서 참조할 수 있지만
반대로 이후에 정의된 CTE는 참조할 수 없다.

예를 들어 WITH 문에 CTE1, CTE2, CTE3를 정의할때

CTE2는 CTE1과 CTE2를 참조할 수 있지만 CTE3는 참조할 수 없다.

다음 쿼리를 실행하믄 위와같은 이유로 오류가 발생한다.


WITH
EmpOrdersCTE(EmployeeID, Cnt)
AS
(
  SELECT EmployeeID, COUNT(*)
  FROM Purchasing.PurchaseOrderHeader

  GROUP BY EmployeeID
),

MinCTE(MN)
AS
(

 -- MinMaxCTE가 정의되기 전에 참조할수 없다.
  SELECT MN
  FROM MinMaxCTE
),
MinMaxCTE(MN, MX, Diff)
AS
(
  SELECT MIN(Cnt), MAX(Cnt), MAX(Cnt)-MIN(Cnt)
  FROM EmpOrdersCTE
)
SELECT * FROM MinMaxCTE


재귀쿼리나... 다양한 예제를 보려면 다음을 참조하자 (귀차니즘.. -_-)

참조 : http://msdn2.microsoft.com/ko-kr/library/ms175972.aspx

728x90

-- 테이블 목록
EXEC SP_TABLES


-- 테이블이 컬럼 목록

EXEC SP_COLUMNS @TABLE_NAME

 

-- 스키마 테이블에서 직접 컬럼정보 가져오기~

SELECT  *
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = @TABLE_NAME


-- PK, FK, INDEX등 정보 가져오기

SELECT  *

FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS K
INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS C ON K.CONSTRAINT_NAME = C.CONSTRAINT_NAME
WHERE K.TABLE_NAME = @TABLE_NAME


-- 컬럼에 추가해둔 Description 가져오기

SELECT  *
FROM SYS.EXTENDED_PROPERTIES XP 
WHERE XP.CLASS = 1
 AND  XP.MINOR_ID > 0
 AND XP.MAJOR_ID = OBJECT_ID(@TABLE_NAME) 
 AND XP.NAME IN (N'MS_DESCRIPTION'
ORDER BY XP.MINOR_ID


위의 정보를 이용하여 테이블 정보를 출력해 보자~

프로젝트 산출물 작업시 유용하게 쓰일것이다.


포맷이 다음과 같다면...

컬럼 명 컬럼 ID Type & Length NOT
NULL
PK FK IDX 비고
               
               
               
               


DECLARE @TABLE_NAME AS VARCHAR(30)

SET @TABLE_NAME = 'TEM_BOARD'

 

SELECT

         [컬럼명] = D.COLUMN_DESC

       , [컬럼ID] = C.COLUMN_NAME

       , [Type & Length] = COLUMN_TYPE

       , [NOT NULL] = CASE WHEN C.IS_NULLABLE = 'NO' THEN 'Y' ELSE '' END

       , PK = ISNULL(K.PK, '')

       , FK = ISNULL(K.FK, '')

       , INX = ISNULL(K.INX, '')

FROM

(

 SELECT 

    TABLE_NAME

  , COLUMN_NAME

  , IS_NULLABLE

  , COLUMN_TYPE = CASE WHEN DATA_TYPE IN ('VARCHAR', 'NVARCHAR', 'char', 'nchar') THEN

       DATA_TYPE + '(' + CONVERT(VARCHAR(10), ISNULL(CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION)) + ')'

      WHEN DATA_TYPE IN ('DECIMAL') THEN

       DATA_TYPE + '(' + CONVERT(VARCHAR(10), NUMERIC_PRECISION) + ', ' + CONVERT(VARCHAR(10), NUMERIC_SCALE) + ')'      

      ELSE DATA_TYPE END

  , ORDINAL_POSITION

 FROM INFORMATION_SCHEMA.COLUMNS

 WHERE TABLE_NAME = @TABLE_NAME

) AS C

LEFT JOIN -- 키정보

(

       SELECT COLUMN_NAME

             , PK = MAX(PK)

             , FK = MAX(FK)

             , INX = MAX(INX)

       FROM

       (

             SELECT

                      COLUMN_NAME

                    , PK = CASE WHEN K.CONSTRAINT_TYPE = 'PRIMARY KEY' THEN 'Y' END

                    , FK = CASE WHEN K.CONSTRAINT_TYPE = 'FOREIGN KEY' THEN 'Y' END

                    , INX = CASE WHEN K.CONSTRAINT_TYPE = 'INDEX' THEN 'Y' END

              FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS K

                    INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS C ON K.CONSTRAINT_NAME = C.CONSTRAINT_NAME

              WHERE K.TABLE_NAME = @TABLE_NAME

       ) AS K

       GROUP BY COLUMN_NAME

) AS K ON C.COLUMN_NAME = K.COLUMN_NAME

LEFT JOIN -- Description 정보

(

 SELECT

    COLUMN_NM = COL_NAME(XP.MAJOR_ID, XP.MINOR_ID) 

  , COLUMN_DESC = CAST(XP.[VALUE] AS NVARCHAR(4000))

 FROM SYS.EXTENDED_PROPERTIES XP 

 WHERE XP.CLASS = 1

  AND  XP.MINOR_ID > 0

  AND XP.MAJOR_ID = OBJECT_ID(@TABLE_NAME) 

  AND XP.NAME IN (N'MS_DESCRIPTION') 

) AS D ON C.COLUMN_NAME = D.COLUMN_NM

ORDER BY C.ORDINAL_POSITION

728x90

검색 조건에 특정 기간(datetime)을 주어 조회할 경우 잠깐! 생각해 보자~!


예를 들어 2007-01-01 ~ 2007-01-10의 기간동안  가입한 회원 목록을 조회해야 한다면~
BETWEEN이나.. >= <= 를 이용하여... [ EX1 ]과 같은 방법으로 많이 한다~!


간단히 생각하믄... 맞는결과가 나올듯 하지만... 그렇지 않다.
검색필드가  datetime형식이므로  [년-월-일]은 [년-월-일 00시 00분 000초]인 셈이다.
그래서 [ EX1 ]의 방법을 이용할 경우 2007-01-10의 데이터도 조회되길 원하였으나 결과는 그렇지 못할 것이다.


그래서..[ EX2 ]처럼... 조회하였다...  훔... 허접해. 보인닷!~~~ 그러나 결과는.. 굿~!


그럼... [EX3]은??  마지막날에 +1일을 더하여 마지막날의 데이터도 포함할 수 있도록 하는것이다~..
그중에.. 이게 젤루 맘에 든다...
물론.. 2007-01-11 00:00:000 인 데이터가 존재한다면... 이건 에러다 -_-;; 
하루를 더한것에서 -1 millisecond를 빼? ㅋㅋ (생략하겠음~ -_-;;)


마지막으로.. [EX4]는 필드값을 [YYYY-MM-DD]형식으로 변형하여 조회할 수도 있겠다~

그러나 값을 변형하여.. 보여주는것도 그렇고.. 인덱스도 안걸어 놓겠지만.. 만약 있다면 인덱스 걸리지도 않을꺼구... 맘에.. 안든다..


암튼 방법이야 많겠지만... [ EX1 ]처럼... 틀린 검색 조건을 주어서는 안된다는걸.. 말하고 싶을 뿐이닷~


-- [ EX1 ]
SELECT *
FROM dbo.MEMBER
WHERE 등록일시 BETWEEN '2007-01-01' AND '2007-01-10'


-- [ EX2 ]
SELECT *
FROM dbo.MEMBER
WHERE 등록일시 BETWEEN '2007-01-01' AND '2007-01-10 59:59:999'


-- [ EX3]
SELECT *
FROM dbo.MEMBER
WHERE 등록일시 BETWEEN '2007-01-01' AND DATEADD(DAY, 1, '2007-01-10')


-- [ EX4]
SELECT *
FROM dbo.MEMBER
WHERE CONVERT(VARCHAR(10), 등록일시, 121) BETWEEN '2007-01-01' AND '2007-01-10'

+ Recent posts