본문 바로가기
프로그래밍/ASP

[뭉충닷컴 펌]ASP 페이징 쉽게 처리하기

by 백룡화검 2010. 4. 23.
<LINK href="/mianamssi/include/star-light/star-light.css" type=text/css rel=stylesheet>

게시물 페이징이 사용자한텐 상당히 편한 기능이지만 개발자한테는 귀찮고 괴로운 작업중 하나다. 페이징을 해보면 알겠지만 페이징 처리를 위해서 목록 페이지 곳곳에 이런 저런 처리를 해줘야한다. 단지 페이지 이동 링크들 넣을 뿐인데 그 작업량은 상당하다.

이런 개노다(?) 작업외에 무시할 수 없는 부분이 페이징시 사용되는 쿼리문이다. 일전에 "프로그래밍 Tip"란에 큰머리님의 페이징 테스트 자료를 올려두었는데, 그 게시물만 보더라도 페이징 관련 쿼리문이 여러가지 있다. (페이징 테스트 자료로 가기)

이 테스트 자료를 보면 가장 빠른 페이징을 6번째 방법이였는데 솔직히 이 방법은 실무에서 사용하긴 좀 부족한 감이 있다. 실제 개발에선 테이블 하나만 가지고 페이징하진 않는다. 몇개씩 조인한 테이블의 데이타들을 페이징 하는것은 물론 심한 경우 10개이상의 테이블에서 조인한 데이타를 페이징 해야하는 경우도 있다. (이 10개이상 조인은 상당히 비추천이지만 어쩔수 없는 경우가 있다 -_-;;;)

거기다 또 다른 문제는 쿼리문에 Distinct, Group, Having..등등 절을 사용했을 경우 실제 기존 사용하던 페이징 쿼리를 사용하면 잘 안맞는 경우가 있다. 이 부분은 개발시에 생각보다 간과하게 쉬운데 꼼꼼히 살펴보면 페이징 되는것이 뭔가 이상함을 발견할 수 있을것이다. 무리하게 기존 페이징 코드 Copy & Paste했다간 추후 낭패를 본다 -_- 이런 쿼리들을 실제로 보여줬으면 좋을텐데 아쉽게도 제대로 정리해두질 않았고 그런 쿼리들을 지금 만들자니 끔찍하다. 그러니 그냥 '그런 경우도 있구나...' 라고만 생각해라 -_-;;;

 

이번에 다룰 페이징 함수는 위와 같은 문제점을 한방에 해결해준다. 적용하기 쉽고, 코드양 현저하게 줄어들고, 어떤 쿼리라도 정확하게 페이징 해준다.(물론 100% 장담 못한다. -_- 그냥 허위광고라 생각해라 -_-)

단! 단점이 있다. 속도는 보장 못한다. 이 함수는 속도보다는 편리함에 중점을 둔것이다. 페이징 속도를 생명처럼 여긴다면 이 함수 적용하지 않는것이 좋다. 뭐..그렇다고 속도가 기존 페이징 쿼리보다 2배이상 현저하게 떨어진다거나 그렇진 않다. 단지 속도에 중점을 둔 쿼리가 아니라는 것이다. (실제 개발에 사용했을때도 페이징 속도때문에 문제된적은 한번도 없었다 -_-)

또 한가지 단점은 레코드 수가 1000000000(10억)이 넘는 게시물은 페이징 할 수 없다. 이렇게 제한을 둔 이유는 Top 쿼리문 특성 때문인데...이 함수 만들당시에 왜 10억개로 제한했는지 기억이 안난다 -_-;; 그 당시 무슨 이유때문에 이렇게 만들어 두었는데 당췌 기억이... -_-;; 대충..기억으론 Top에서 사용할수 있는 개수 제한이 있었던것 같은데 지금 좀더 높은 수로 테스트 해봐도 된다. 문제는 그 당시는 ms-sql 2000으로 테스트 했었고 지금 로컬에 설치되어있는것은 ms-sql 2005이다. 그래서 정확하게 비교가 좀 그렇다.

혹시 Top 절의 개수 제한이 있는지 도움말과 이런 저런 책을 잠깐 봤는데..음 찾을수가 없다. 그냥 10억개로 제한해두고 사용하자! 알겠지만 10억개까지 있는 게시물은 평생 찾아보기 힘들고 만들기도 힘들다. 일전에 큰머리님의 페이징 자료 테스트 해본다고 100만개 레코드 생성하는데 하다 포기했다. 한 40만개까지인가 만든것으로 기억하는데 간단한 레코드인데도 생성시간이 대략 한시간정도 걸린것으로 기억한다 -_-;;; 그러니 10억개 레코드 테스트 해보려고 한다면 몇일간 컴퓨터 개고생 시켜야 할것이다.

음..뭔가 서론이 상당히 긴듯한데 결론은 ...다시 읽어보니 왠지 이 함수의 단점들을 변명하는것 같군 -_- 아무튼 실제 개발에 적용해도 문제없는 함수이니 필요하면 가져다 사용하자!

 

아래는 페이징 안하고 모든 레코드 가져오는 코드이다.

<% OPTION EXPLICIT %>
<%
Dim DBCon : Set DBCon = Server.CreateObject("ADODB.Connection")
Dim strConn : strConn = ""
Dim strQry
Dim rs

strConn = strConn&"Provider=SQLOLEDB.1;Persist Security Info=True;"
strConn = strConn&"Data Source=localhost;"  ' Server Name
strConn = strConn&"User ID=sa;"             ' User ID
strConn = strConn&"Password=;"              ' User Password
strConn = strConn&"Initial Catalog=NorthWind;"   ' DataBase
DBCon.Open strConn

strQry = "select CompanyName from Customers"
Set rs = DBCon.Execute(strQry)

Do While not rs.eof
    Response.Write rs(0) & "<hr>"
    rs.MoveNext()
Loop
rs.Close
Set rs = nothing
%>

이 코드에 페이징 처리 구문을 넣어보자

<% OPTION EXPLICIT %>
<!--#include file="asp_page_function.asp"-->
<%
Dim DBCon : Set DBCon = Server.CreateObject("ADODB.Connection")
Dim strConn : strConn = ""
Dim strQry
Dim rs
Dim req_curPage : req_curPage = Request("curPage")

strConn = strConn&"Provider=SQLOLEDB.1;Persist Security Info=True;"
strConn = strConn&"Data Source=localhost;"  ' Server Name
strConn = strConn&"User ID=sa;"             ' User ID
strConn = strConn&"Password=;"              ' User Password
strConn = strConn&"Initial Catalog=NorthWind;"   ' DataBase
DBCon.Open strConn

strQry = "select CompanyName from Customers"
Set rs = ExecutePage(strQry,req_curPage)

Do While not rs.eof
    Response.Write rs(0) & "<hr>"
    rs.MoveNext()
Loop
rs.Close
Set rs = nothing

' 페이지 이동 부분 뿌려준다.
Response.Write ShowPageBar(req_curPage,"","","")
%>

단지, 강조된 부분만 처리해주면 바로 페이징이 된다! 몇번을 야려봐도 정말 간단하다(나만 간단한가 -_-;;)

하나씩 살펴보면 일단 보여줄 페이지 번호를 Request로 받아온다.


Dim req_curPage : req_curPage = Request("curPage")

그래서 그 보여줄 페이지 번호와 쿼리문을 ExecutePage 함수에 인자로 던져준다.


Set rs = ExecutePage(strQry,req_curPage)

그런후 페이지 이동바 함수를 호출해서 뿌려주면 끝이다.


Response.Write ShowPageBar(req_curPage,"","","")

만약 뿌려질 목록수를 10개에서 20개 혹은 30개로 조절하고 싶다거나 페이지 이동바의 모양을 변경하고 싶다거나.. 등등 미묘한 설정을 건드리고 싶다면 asp_page_function.asp 이 파일 열어서 수정하면된다. 나름대로 주석 여기저기 달아두었으니 수정하는데 그리 큰 어려움은 없을꺼라 본다. 아! 잠시 깜빡하고 넘어갈뻔했는데 실제로 이 페이징 함수 적용하려면 asp_page_function.asp 파일 수정해 줘야한다. 거기서 DB연결 객체의 Execute 이용하는데.. 개발자마다 DB연결객체 처리 방식이 틀리기 때문에 그 부분은 수정해 줘야한다. 그러니 소스코드 다운받고 실행하고선 곧바로 '이거 왜 안되지?'라고만 생각하지 말고 자신의 설정에 맞게 코드들 좀 수정해 줘야한다.

마지막으로 asp_page_function.asp의 코드를 보자 이 페이징 처리의 핵심 로직이 들어가있다 -_-

<%
'#################################################################################
'# 페이징 관련 함수
'#################################################################################


' 페이징시 필요한 전역변수 2개
Dim G_PAGE_SIZE : G_PAGE_SIZE = 10 ' 뿌려질 레코드 개수
Dim G_TOTAL_RECORD ' 전체 레코드 수



'/* 쿼리문 + 페이징 */
Public Function ExecutePage(ByVal pSql, ByVal pPage)
    Dim rs : Set rs = Server.CreateObject("ADODB.RecordSet")
    Dim strSQL
    Dim nPage
    Dim cut, l_sql, r_sql
    
    If pPage = "" or isNull(pPage) Then pPage = 1


    pSql = UCase(pSql) ' 대문자로 변환
    pSql = Replace(pSql, vbTab, " ") ' 쿼리문의 Tab은 Space로
    pSql = Replace(pSql, vbCr, " ") ' 쿼리문의 개행은 Space로
    
    cut = InStr(1, pSql, " TOP ")
    ' top 절이 없으면 order by에서 오류 - top절 있는지 검사
    If cut = 0 Then
        cut = InStr(1, pSql, " DISTINCT ")
        If cut > 0 Then
         ' distinct 가 있을 경우
         cut = cut + 8
         l_sql = Left(pSql, cut)
         r_sql = Right(pSql, Len(pSql) - cut - 1)
        
         pSql = l_sql & " TOP 1000000000 " & r_sql
        Else
         ' distinct 가 없을 경우
         cut = InStr(1, pSql, "SELECT ")
         If cut > 0 Then
         cut = cut + 5
         l_sql = Left(pSql, cut)
         r_sql = Right(pSql, Len(pSql) - cut - 1)
        
         pSql = l_sql & " TOP 1000000000 " & r_sql
         End If
        End If
    End If
    
    nPage = pPage * CLng(G_PAGE_SIZE)
    strSQL = "Select TOP " & CStr(nPage) & " * From (" & pSql & ") AS _TEMP_PAGE_TABLE"
    
    G_TOTAL_RECORD = ExecuteCount(pSql) ' 쿼리문의 전체 레코드 수 구함
        
    rs.CursorType = 1
    rs.PageSize = G_PAGE_SIZE
    rs.Open strSQL, DBCon
    
    If Not (rs.EOF Or rs.BOF) Then rs.AbsolutePage = pPage
    
    Set ExecutePage = rs
End Function


'/* 쿼리문을 주면 그 쿼리문의 레코드 수를 반환 */
Public Function ExecuteCount(ByVal pSql)
    Dim rs : Set rs = Server.CreateObject("ADODB.RecordSet")


    pSql = "Select Count(*) From (" & pSql & ") AS _TEMP_PAGE_TABLE_CNT"
    Set rs = DBCon.Execute(pSql)
    
    ExecuteCount = CLng(rs(0))
    rs.Close
    Set rs = nothing
End Function


'/* 페이징시 전체 페이지수 계산 */
Function GetPageCount(ByVal pTotalRecord)
    Dim retVal
    
    pTotalRecord = CLng(pTotalRecord)
    retVal = Fix(pTotalRecord / G_PAGE_SIZE)
    If (pTotalRecord Mod G_PAGE_SIZE) > 0 Then
        retVal = retVal + 1
    End If
    GetPageCount = CLng(retVal)
End Function


'/* 페이지 네비게이션을 뿌려주는 함수 */
Public Function ShowPageBar(ByVal pCurPage, ByVal pPreImg, ByVal pNextImg, ByVal param)
    Dim nPREV
    Dim nCUR
    Dim nNEXT
    Dim i
    Dim nPageCount
    Dim retVal
    Dim strLink
    Dim pageKubun


    If pCurPage = "" or isNull(pCurPage) Then pCurPage = 1
    
    nPageCount = GetPageCount(G_TOTAL_RECORD)
    
    If pPreImg = "" Then
        pPreImg = "[이전]"
    Else
        pPreImg = "<img src='" & pPreImg & "' border=0 align=absmiddle>"
    End If
    
    If pNextImg = "" Then
        pNextImg = "[다음]"
    Else
        pNextImg = "<img src='" & pNextImg & "' border=0 align=absmiddle>"
    End If
    
    nPREV = (Fix((pCurPage - 1) / 10) - 1) * 10 + 1
    nCUR = (Fix((pCurPage - 1) / 10)) * 10 + 1
    nNEXT = (Fix((pCurPage - 1) / 10) + 1) * 10 + 1


    ' [이전] 페이지 조합
    If nPREV > 0 Then
        strLink = "?curPage=" & nPREV & param
        retVal = "<a href=""" & strLink & """>" & pPreImg & "</a> "
    Else
        retVal = "" & pPreImg & " "
    End If
    i = 1
    Do While i < 11 And nCUR <= nPageCount
        If nCUR = nPageCount Or i = 10 Then
         pageKubun = " "
        Else
         pageKubun = " . "
        End If
        
        If CInt(pCurPage) = CInt(nCUR) Then
         retVal = retVal & "<font color=#FF6700 size=3><b>" & nCUR & "</b></font>" & pageKubun
        Else
         strLink = "?curPage=" & nCUR & param
         retVal = retVal & "<a href=""" & strLink & """>" & nCUR & "</a>" & pageKubun
        End If
        nCUR = nCUR + 1
        i = i + 1
    Loop
    ' [다음] 페이지 조합
    If nNEXT <= nPageCount Then
        strLink = "?curPage=" & nNEXT & param
        retVal = retVal & " <a href=""" & strLink & """>" & pNextImg & "</a>"
    Else
        retVal = retVal & pNextImg & ""
    End If
    
    ShowPageBar = retVal
End Function
%>

(이상하게 asp_page_functioh.asp 의 소스코드가 star-light 적용했더니 들여쓰기가 잘 안맞는다. -_- 실제 소스코드는 제대로 되어있으니 실제 코드 다운받아 보면된다.)

함수 총 4개에 전역변수 2개다.

전역변수 2개는 "보여줄 게시물 갯수 지정", "전체 레코드 수" 이다. 함수 4개에서 이 전역변수 2개를 이용해서 데이타를 만들어 내기 때문에 없애거나 변수명 수정하면 안된다. 만일 수정하려면 함수안에 있는 전역변수 명까지 다 수정해주자. 이렇게 함수간의 통신을 위해서 전역변수를 이용하는 방법은 그다지 좋은 방법은 아닌데..내가 기존에 사용하고 있던 모듈에서 페이징 부분만 떼어오다보니 이렇게 바꿔버리게 되었다. 원랜 소스는 전역변수들이 전부 클래스의 멤버 변수로 선언되서 사용되는데 공개용으로 함수를 만들다보니 클래스를 이용하는 방법이 적당치 않은듯해서...어찌 어찌 하다보니 이렇게 되었다 -_-;;;

함수 4개는 "페이징 처리 함수","전체 레코드수 가져오는 함수","페이지 갯수 계산하는 함수","페이지 이동바 만들어주는 함수" 이렇게 4개인데 그 중에서 첫번째 함수의 마지막 함수만 주의해서 보면 된다. 첫번째 함수는 쿼리문과 페이지 번호를 받으면 그 페이지에 해당하는 게시물만 가져오는 함수다. 가장 중요한 함수다. 자세한 설명은 없다. 그냥 야리는거다. -_- 마지막 함수는 페이지이동바를 만들어주는데...역시나 보면 안다. -_-

오늘도 이렇게 날림강좌 하나 완성이다. 후훗 뿌뜻하다.

 

 

 

p/s 이번 강좌 적으면서 내 강좌에 대한 내 스스로 느낀점
1. 헛소리가 많다.
2. 지식이 얕고 귀찮아서 자세한 정보 잘 제공 안한다.
3. 소스코드 전체를 그냥 보여준다.
4. 그 소스코드 설명 안하고 무조건 야리라고만 한다.
5. 그림이 없어서 이해가 힘들다.
5. -_- <ㅡ 요게 너무 많다 -_-