728x90

MS-SQL 2005에서는 순위를 쉽게 구할수 있도록 몇개의 함수를 추가로 제공한다.

이미 Oracle에서는 제공되는 함수들 이므로 간략하게 비교해 볼까 한다~

DB는 ADVENTUREWORKS의 테이블을 이용할 것이다.


USE ADVENTUREWORKS
GO

를 꼭 하자~


구문

<FUNCTION_NAME>() OVER(

      [PARTITION BY <PARTITION_BY_LIST>] 

      ORDER BY <ORDER_BY_LIST> )


EX) ROW_NUMBER() OVER( PARTITION BY COL3 ORDER BY COL1 )


1. OVER ( ORDER BY <ORDER_BY_LIST> )는 순위를 결정하는 정렬 조건으로 필수이다.

2. PARTITION BY를 지정하지 않으면 순위값은 모든 테이블 행을 대상으로 순위가 계산되지만

지정하면 행 그룹내에서 개별적으로 계산될 수 있다.


☞ 순위 함수


◑ ROW_NUMBER

결과행에 순차적으로 값을 지정할 수 있다. (페이징할때 유용하다.. 이젠 고생할 필요없다 ㅋㅋㅋ)

1. PARTITION BY를 사용할때와 하지 않을때의 차이 비교

SELECT
     CONVERT(VARCHAR(10), ORDERDATE, 121) AS ORDERDATE,
     ROW_NUMBER()OVER (ORDER BY ORDERDATE DESC) AS ROWNUM,
     ROW_NUMBER()OVER (PARTITION BY CONVERT(VARCHAR(10), ORDERDATE, 121) ORDER BY ORDERDATE DESC) AS SUBROWNUM,
     CUSTOMERID,
     TOTALDUE
FROM SALES.SALESORDERHEADER
WHERE TOTALDUE > 100
ORDER BY ROWNUM, SUBROWNUM

사용자 삽입 이미지




















빨간 박스의 ROWNUM과 SUBROWNUM을 비교하여 보자.

SUBROWNUM은 ORDERDATE내에서 다시 1부터 순차적으로 값이 지정되는걸 확인 할 수 있다.


2. 페이징에 적용하여 볼까?

DECLARE @intPageSize INT -- 보여줄갯수
DECLARE @intCurrentPage INT -- 보여줄 페이지 번호
DECLARE @intIDFrom INT
DECLARE @intIDTo INT
 
SET @intPageSize = 10
SET @intCurrentPage = 2
SET @intIDFrom = (@intPageSize * @intCurrentPage) + 1
SET @intIDTo = ( @intIDFrom +  @intPageSize ) -1

SELECT ROWNUM, NAME
FROM ( SELECT ROW_NUMBER()OVER(ORDER BY VENDORID) AS ROWNUM, VENDORID, NAME
  FROM Purchasing.Vendor ) AS D
WHERE ROWNUM BETWEEN @intIDFrom AND @intIDTo
ORDER BY VENDORID
 
페이징에 대해선 이전에 썼던 포스트를 참조 하자!
 
3. 오라클의 ROW_NUMBER()도 똑같다.
SELECT DEPTNO, ROW_NUMBER() OVER(PARTITION BY DEPTNO ORDER BY EMPNO) RID, EMPNO FROM EMP

 

4. 오라클에서 ROWNUM를 이용하여 페이징 기능 구현

SELECT *
FROM (
      SELECT ROWNUM AS INX, T.* FROM (
             SELECT EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO
            FROM EMP
            ORDER BY EMPNO ) T
) T
WHERE INX BETWEEN 1 AND 10;
ROWNUM은 ORDER BY EMPNO가 실행되기 전에 순위값이 지정되므로 정렬 후 ROWNUM을 지정하여야 한다.

◑ RANK 와 DENSE_RANK

순차적으로 값을 제공하는점에서는 ROW_NUMBER과 유사하지만 동률이 적용된다는점에서는 다르다.

1. ROW_NUMBER, RANK, DENSE_RANK 비교

SELECT
    CONVERT(VARCHAR(10), ORDERDATE, 121) AS ORDERDATE,
    ROW_NUMBER()OVER (ORDER BY TOTALDUE DESC) AS ROWNUM,
    RANK() OVER (ORDER BY TOTALDUE DESC)AS RANK,

    DENSE_RANK()OVER (ORDER BY TOTALDUE DESC) AS DENSE_RANK,
    DENSE_RANK()OVER (PARTITION BY CONVERT(VARCHAR(10), ORDERDATE, 121) ORDER BY TOTALDUE DESC) AS SUB_DENSE_RANK,

    TOTALDUE
FROM SALES.SALESORDERHEADER
WHERE TOTALDUE > 100
ORDER BY DENSE_RANK, SUB_DENSE_RANK

사용자 삽입 이미지

결과의 1694와 1695 ROW는 TOTALDUE가 같으므로 동률이다.

ROWNUM은 무조건 순차적으로 값을 제공하지만 RANK와 DENSE_RANK는 동률이 적용된다.

1696 ROW를 보면 RANK는 1694순위가 2개였으므로 다음순위는 1696순위가 되지만

DENSE_RANK는 1694순위가 2개였더라도 다음 순위는 1695가 된다.

PARTITION BY는 날짜별로 순위를 제공한다.


2. 오라클에서도 똑같다

SELECT
    MGR,
    ROW_NUMBER() OVER(ORDER BY SAL DESC) AS INX,
    RANK() OVER(ORDER BY SAL DESC) AS RANK,
    DENSE_RANK()OVER(ORDER BY SAL DESC) AS DENSE_RANK,
    DENSE_RANK()OVER(PARTITION BY MGR ORDER BY SAL DESC) AS SUB_DENSE_RANK,
    SAL
FROM EMP


◑ NTILE

데이터를 지정된 그룹수로 분리할 수 있다.

SELECT
    CONVERT(VARCHAR(10), ORDERDATE, 121) AS ORDERDATE,    
    NTILE(20) OVER(ORDER BY ORDERDATE) AS GRPNO,
    NTILE(5) OVER(PARTITION BY CONVERT(VARCHAR(10), ORDERDATE, 121) ORDER BY ORDERDATE) AS SUB_GRPNO,
    TOTALDUE
FROM SALES.SALESORDERHEADER
WHERE MODIFIEDDATE > CONVERT(DATETIME, '2004-08-01', 121)
ORDER BY ORDERDATE, GRPNO, SUB_GRPNO

사용자 삽입 이미지

전체 187건 ORDERDATE가 2004-07-26인것은 32건 이다.
187/20 = 9.35
전체 20그룹을 각 9건씩 채우면 180건이고 7건이 남는다. (20 * 9 = 180)
남은 7건은 1그룹부터 순차적으로 채워진다.
1~7그룹 : 10건 (7*10 = 70)
8~20그룹 : 9건 (13 * 9 = 117)
PARTITION BY 부분도 위에서 설명한 내용과 같다.~
 
2. 이것역쉬 오라클에서도 같네?
SELECT 
       EMPNO,
       MGR,
       NTILE(5) OVER(ORDER BY EMPNO) AS GRPNO,
       NTILE(2) OVER(PARTITION BY MGR ORDER BY EMPNO) AS SUB_GRPNO
FROM EMP
ORDER BY GRPNO, SUB_GRPNO
728x90

SELECT DISTINCT
  C.TABLE_NAME
, C.CONSTRAINT_TYPE
, C.CONSTRAINT_NAME
, COL.COLUMN_NAME
, ( CASE
 
WHEN C.CONSTRAINT_TYPE = 'PRIMARY KEY' THEN 1
  WHEN C.CONSTRAINT_TYPE = 'FOREIGN KEY' THEN 2
  ELSE 3
 END
) AS DISPLAY
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS C
 INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS COL ON C.CONSTRAINT_NAME = COL.CONSTRAINT_NAME
WHERE
 C.TABLE_NAME NOT IN ('dtproperties')
ORDER BY C.TABLE_NAME, DISPLAY

728x90

저장이나 수정시 for문 안에서 루프 돌면서 insert 하는경우 종종 있죠??


그럴경우 xml형태로 데이터를 전송하여 sp안에서 처리하게 하는건 어떨까요?


머 많은분들이 알고 계시겠지만 그래도~ ㅋㅋㅋ


우선 클릭 이벤트~

// 다중 입력 처리
  private void Button1_Click(object sender, System.EventArgs e)
  {
   string sXML = "";    // xml 저장소
   bool bResult = false;   // 결과값
   DataSet ds = new DataSet();
   ds.Tables.Add(MakeTable());  // 테이블 생성

   if(ds.Tables[0].Rows.Count > 0)
   {

    sXML = ds.GetXml();
   
    bResult  = AddMemo(sXML);

    if(bResult)
    {
     // 처리 성공
    }
    else
    {
     // 처리 실패
    }
   }
   else
   {
    // 테이블 생성 실패
   }
  }


DataTable 형식으로 자료를 만든다. xml로 쓰기 위함

private DataTable MakeTable()
  {
   
   char cSplit = ';';
   string[] sReceiveID = null;

   DataTable dt = new DataTable("MemoAdd");
   dt.Columns.Add("SendID");
   dt.Columns.Add("ReceiveID");
   dt.Columns.Add("Memo");
   dt.Columns.Add("DelState");
   
   string sSendID = AuthUser.CurrentUserID;
   string sMemo = txtMemo.Text.Trim().Replace("\r\n", "<br>");
   string sDelState = txtDelState.Value.Trim();


   // 수신자 아이디
   if(txtReceiveID.Text.IndexOf(cSplit) > 0)
   {
    sReceiveID = txtReceiveID.Text.Split(cSplit);
   }
   else
   {
    sReceiveID = new string[1];
    sReceiveID[0] = txtReceiveID.Text.Trim();
   }

   DataRow dr = null;
   for(int i=0;i<sReceiveID.Length;i++)
   {
    dr = dt.NewRow();
     
    dr["SendID"]    = sSendID;
    dr["ReceiveID"] = sReceiveID[i];
    dr["Memo"]  = sMemo;
    dr["DelState"]  = sDelState;
   
    dt.Rows.Add(dr);
   }
   return dt;
  }


sp 호출

public bool AddMemo(string sXML)
  {
   string sQuery = "USP_SM_MemoAdd_I";  // sp명
   bool bResult = false;         // 결과값
   SqlCommand sqlCmd = null;
   SqlParameterCollection sqlParamColl = null;
   SqlParameter[] sqlParams = null;
   
   try
   {
    sqlCmd = new SqlCommand();
    sqlParamColl = sqlCmd.Parameters;
    sqlParamColl.Add("@VarXML", sXML);
   
    sqlParams = new SqlParameter[sqlParamColl.Count];
    sqlParamColl.CopyTo(sqlParams, 0);
    sqlParamColl.Clear();

    SqlHelper.ExecuteNonQuery(CONNSTR_UTIL, CommandType.StoredProcedure, sQuery, sqlParams);  // 실행
    bResult = true;
   }
   catch(Exception ex)
   {
    throw ex;
   }
   finally
   {
    if (sqlCmd != null) sqlCmd.Dispose();
   }
   return bResult;
  }


sp 내용

Create PROC USP_SM_MemoAdd_I
 @VarXML NTEXT
AS
 DECLARE @SendID varchar(50)
 DECLARE @ReceiveID varchar(50)
 DECLARE @Memo Varchar(7000)
 DECLARE @DelState char(2)

 DECLARE @HDoc    INT
 DECLARE @RootName  VARCHAR(100)

 SET NOCOUNT ON
 BEGIN TRANSACTION

 EXEC SP_XML_PREPAREDOCUMENT @HDoc OUTPUT, @VarXML
 SET @RootName ='/' + (SELECT TOP 1 LocalName FROM OPENXML(@HDoc, '/') WHERE ID = 0) + '/'
 SET @RootName = @RootName + (SELECT TOP 1 LocalName FROM OPENXML(@HDoc, '/') WHERE ParentID = 0)

 DECLARE XML_CURSOR CURSOR FOR 
  SELECT SendID, ReceiveID, Memo, DelState
  FROM OPENXML(@HDoc, @RootName, 1)
  WITH (
   SendID varchar(50) 'SendID',
   ReceiveID varchar(50) 'ReceiveID',
   Memo Varchar(7000) 'Memo',
   DelState char(2) 'DelState'
            )
 OPEN XML_CURSOR
 FETCH NEXT FROM XML_CURSOR INTO @SendID, @ReceiveID, @Memo, @DelState

 WHILE @@FETCH_STATUS = 0
 BEGIN
  IF ( EXISTS(SELECT 1 FROM dbo.TB_SM_BOSUser where UserID = @SendID) and EXISTS(SELECT 1 FROM dbo.TB_SM_BOSUser where UserID = @ReceiveID) )
   BEGIN
    INSERT INTO
     dbo.TB_SM_Memo (SendID, ReceiveID, Memo, DelState)
    VALUES
    (
     @SendID,
     @ReceiveID,
     @Memo,
     @DelState
    )
   END

  IF @@ERROR <> 0
  BEGIN
   ROLLBACK TRANSACTION

   CLOSE  XML_CURSOR
   DEALLOCATE  XML_CURSOR
   EXECUTE SP_XML_REMOVEDOCUMENT @HDoc   -- OpenXML을 사용하고 나서, Document를 닫고, Handle을 제거한다.
   SET NOCOUNT OFF
   RAISERROR 13000 '……'
   RETURN
  END
 
 FETCH NEXT FROM XML_CURSOR INTO @SendID, @ReceiveID, @Memo, @DelState

 END

 CLOSE XML_CURSOR
 DEALLOCATE  XML_CURSOR
 EXECUTE SP_XML_REMOVEDOCUMENT @HDoc   -- OpenXML을 사용하고 나서, Document를 닫고, Handle을 제거한다.
   
 COMMIT TRANSACTION
 SET NOCOUNT OFF
 RETURN
GO

+ Recent posts