1. <% @CODEPAGE="65001" language="VBScript" %>

코드 페이지는 숫자, 구두점, 기타 그림 문자 등을 포함하는 문자 집합이다.

각 언어와 로케일마다 다른 코드 페이지를 사용한다.

 

대표적인 코드 페이지 값

949   : 한국어 (EUC-KR)

65001 : 유니코드 (UTF-8)

 

ASP에서 코드 페이지를 지정하기 위해서는 Session.CodePage 프로퍼티나 @CODEPAGE 명령어를 사용한다.

결과는 같지만 적용범위의 차이가 있다.

 

Session.CodePage 프로퍼티를 이용해서 코드 페이지를 지정하면 세션 범위 내에서 실행되고 있는 모든 스크립트에 적용되지만, @CODEPAGE를 사용하면 지정한 페이지에만 영향을 미치게 된다.

 

페이지 상단에 @CODEPAGE를 지정하여 해당 페이지 전체에 적용하거나,

코드 중간에 Session.CodePage 프로퍼티를 지정하여 동적으로 설정할 수 있다.

 

2. Session.CodePage = 65001

현재 세션의 동적 코드 페이지 설정.

 

스크립트 일부분에서 외국어로 된 문장을 출력하려면 코드 페이지를 일시적으로 출력할 외국어의 코드 페이지로 변경하여 출력 후 원래의 코드 페이지 값으로 복원해주면 된다.

 

3. Response.CharSet = "UTF-8" 
문자 집합 이름을 응답 개체의 content-type 헤더에 추가한다.

 

4. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 해당 페이지의 (HTML,ASP,JSP,PHP 등) 정보를 가지고 있는 메타 태그를 설정.

 

5. 에디트플러스 등으로 저장할 때 반드시 해당 Encoding 방식으로 저장한다.

65001 일땐 필히 UTF-8로 저장해야함.

 

--------------------------------------------------------------------------------

 

1, 2, 3을 ASP 페이지 상단에 추가해주고 (config.asp 만들어 매페이지 상단에 삽입해주면 편하다)
HTML의 header 내에 4를 추가해주고 저장할땐 인코딩방식을 확인하라~~

가끔 지식인을 통하여 도움을 받다보니, 가끔 답변을 달아 주고 있다. 그러다 보면 대학의 중간고사 쯤 되거나.. 기말고사쯤 되면, 정말 어이 없는 질문들이 쏟아지는 것을 보게된다. 그런 질문을 한 사람이 그정도도 몰라서 어이없다는 것이 아니라, 대학이라는 곳의 교수라는 분들이 얼마나 고리타분하고. 수구적이며. 학습을 하지 않는지...

 

그런 대표적인 것이 ODBC 데이터 베이스 연결이다.

 ASP는 이제 구닥다리가 되어 버린 아주 오래된 서버 스크립트다. 

(오래된 것이지만.. 나는 이것 밖에는 모른다. 그냥 처음에 PERL에 짜증나서 ASP를 접했고, 예전에도 그렇고 현재도 그렇지만 직업이 개발자가 아니기 때문에, 그 이상을 알 필요가 없었다. 이는 앞으로도 그럴 것이다. 어떻게 프로그램이라는 것이 작동하며 어떻게 최신 기술들이 흘러 가는지를 예전에 얻은 지식 위에 그냥 쌓아가면 되는 그런 사람이다. 그래서 ASP라는 놈을 고집하지도 않는다. )

 

ASP는 구닥다리다. 하지만, 학생들에게 프로그램(스크립트)이라는 것의 기초 또는 작동방식을 가르쳐주기에 여전히 가장 좋은 환경에 있다. 

 

MS윈도우 / 메모장 / access의 MDB 파일...  이렇게 3가지만 있으면 누구나 ASP 프로그래밍을 체험할 수 있다. 이 3가지는 윈도에 기본 탑재되거나 용이하게 탑재 가능한 놈이기에 아주 저렴한 비용에 학생들은 교육을 받을 준비가 완료된다. 이는 학교에서 뿐만 아니라 집에도 마찬가지다.  프로그래밍과 관련하여 이렇게 저렴하게 학습자료를 준비할 수 있는 게 또 있을까? 더구나, 설치법을 일일이 학생들에게 가르쳐주지 않아도 이렇게 쉽게 준비가 완료되는 것이 또 있을까?

 

그러나, 이러한 "교수"님들의 뛰어나신 판단에도 불구하고, 그분들이 가르치고 계시는 몇가지로 인하여 나를 깜짝 놀라게 한다. 그중에 대표적인 것이 바로 ODBC 가 되겠다. 

 

하지만, 그사이 세상은 놀랄만큼 바뀌고, 발전했지요. 그러니, 그 사이에 ODBC 를 능가할만한 것이 또한 등장할만도 하다는 느낌이 사정없이 들죠? 그렇습니다. 마이크로소프트는 그 사이에 이 방법을 구체하시켜서 ODBC 보다 뛰어난 성능을 가진 OLE DB 라는 것을 등장시켰습니다. 오옷... 대단한 MS (아부성은 아닙니다.)

http://www.taeyo.pe.kr/lecture/9_Board2001/Board2001_02.htm 중에서

 

그렇다. ODBC보다 더 좋은 방법으로 OLEDB라는 놈이 존재하고 있는 것이다.

 

이론을 떠나서. 교육적 차원에서 실무적으로 ODBC보다 OLEDB를 가르치는 것이 학생들에게 왜 좋은 것일까?

  1. ODBC는 제어판에 들어서 DSN 설정을 복잡하게 해야 DB를 세팅한다. OLEDB는  소스에 OLEDB 연결 스트링을 추가하면 끝난다. 시간적으로 보면, 수백배 차이가 날 것이다.
  2. ODBC는 코딩한 소스를 다른 서버로 옮기면 바로 작동하지 않는다. 제어판에 들어가 또다시 해당 서버에 맞게 세팅해야 한다. 반면에, OLEDB의 경우라면 아무런 조건 없이 그대로 작동한다. (MDB의 경우 폴더 위치만 같으면 그대로 작동하며, 소스 자체에 server.mappath 등으로 mdb 파일명의 경로를 확인토록 하면, 위치가 달라져도 그 위치를 소스가 추적해서 적용하므로 관계없어진다)
    이러한 문제로 인하여, ODBC의 경우 학생들이 DSN세팅을 조금 다르게 하여 과제를 제출하면... 담당교수 되는 분은 그 세팅을 일일이 맞추어주어야만 제출한 과제물을 확인할 수 있다. 이때문에 학생은 학생대로, 교수는 교수대로 불필요한 작업을 해야 한다. (odbc에서는 소스를 보는 것만으로는 DB연결방식을 확인할 방법도 없다)
    반면, OLEDB의 경우에는 아무런 세팅없이도 내용을 확인할 수 있다.
  3. ODBC를 웹호스팅 환경에서 사용하는 경우,서버의 관리자가 DSN세팅을 해주어야만 사용이 가능하다. OLEDB는 관리자의 도움없이 작동시킬 수 있다. asp에는 윈도2000 등 nt기반 서비스이며, dsn 세팅을 하려면 터미널서비스를 열거나 로컬 로긴을 해야한다.  일반적으로 nt계열은 보안 문제로 일반 유저에게 이러한 권한을 주지 않기에 odbc 설정을 하려한다면 관리자의 도움이 필요하게 된다. (호스팅 환경이라면, 다른 웹호스팅 사용자의 설정과 충돌이 발생할 수도 있다. 내가 관리자라면, odbc는 지원하지 않을 것이다.)

기술적으로 odbc 또는 oledb가  뛰어나고 말고는 전문가들의 영역으로 빼두더라도, 실질적으로 OLEDB가 유연한 연결 방식이라 하겠다.  그런데도 교수님들은 왜 ODBC를 학생들에게 가르치고, 학생들은 OLEDB를 몰라 ODBC로 과제를 제출하고 있는 것일까?

 

단순하다. 그 양반들이 그 변화를 몰랐기 때문이다. classic asp 책들은 asp 가 처음 발표되던 시절에 나온것이 대부분이다. 그 책들은 asp가 수정되고, 발전된 현실의 내용을 담고 있지 않다. 그래서, ASP 초기에 나온..... 이제는 가능하면 피해야 하는 코드들이 교과서에 포함되어 있는 것이고, 바로잡아지지 않고 있는 것이다.

 

그 이야기는 여기서 끝내고.. oledb 연결 스트링에 대하여 확실하게 배워두자.

 

먼저, ACCESS SQL ORACLE 할 것 없이 모두의 연결 스티링 만드는 법이다. 정말 간단하다. 외우지 않아도 언제나 가능한 방법이다. 단 한가지만 외워라.

 

UDL

외웠으면 끝났다.

 

어느 정도 pc를 다를 줄 아는 사람이라면..

 바탕화면에 새텍스트 파일을 아무런 이름으로 만들고... UDL을 확장자를 변경한다. 그리고.. 더블클릭한다.  거기에 나온대로 하고.. 저장후에.. UDL 파일을 메모장으로 열어보라.. 그러면 연결스트링이 있을 것이다.  ORACLE, MDB, SQL ... 웬만한 연결스트링을 만들 수 있다.

 

 

무슨 말인지 잘 모르겠는 분은..

  1. 웹루트(즉 ASP 파일들이 있는 첫 페이지)에서 빈 자리에 마우스 오른쪽 버튼
  2. 새로 만들기 선택
  3. 텍스트 문서 선택
  4. CONN.UDL 로 이름 바꾸기 (확장자를 바꾸는 방법은 폴더 옵션 -> 보기 -> 고급설정 -> 알려진 파일 형식의 파일 확장명 숨기기에서 체크를 해제하면 된다. 윈도의 기본값은 숨기기이다. 폴더 옵션이 어디 있는지 모르겠다고? 내컴퓨터를 눌러 맨위 메뉴바의 파일 편집 보기 즐겨찾기 죽 나오고 그 옆의 도구를 누르면 맨 아래에 있다)
  5. CONN.UDL 파일이 만들어 졌으면 더블 클릭
  6. 데이터 연결 속성이라는 창이 뜨면 OK. (메모장 등 다른게 뜨면 확장명이 안바뀐 것이다. 위대로 해야 한다)
  7. 공급자 탭으로 이동
  8. OLEDB 공급자 선택후에... 
    - ACCESS는 MICROSOFOT JET로 시작하는 놈을 선택하고 다음 -> 파일 위치 선택 -> 연결 테스트 -> 확인
    - MS SQL이면 MICROSOFT OLE DB PROVIDER FOR SQL SERVER를 선택후 다음 -> 연결탭 선택 -> SQL 서버 IP 또는 도메인주소 입력 -> 부여된 사용자 ID와 암호 입력 -> 본인에게 부여된 DB 선택 -> 연결 테스트 -> 확인

 

자 그럼 기본이 끝났다

 

이제 ASP에 실제로 적용하기 위한 코드는 만들어 보자. 메모장으로 조금전에 만든 UDL 파일을 열어 보자.

 

sql이라면

 [oledb]
; Everything after this line is an OLE DB initstring

Provider=SQLOLEDB.1;Persist Security Info=False;User ID=입력한sql접속id;Initial Catalog=데이터베이스이름;Data Source=sql서버ip

 

access라면

 [oledb]
; Everything after this line is an OLE DB initstring

Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\1.MDB;Persist Security Info=False

 

로 파일이 되어 있을 것이다.

 

이제 이 파일을 자신에 맞에 수정하자

 

예를 들겠다.

ms sql이고, 본인이 ADODB.Connection 를 Set ConnDB=Server.CreateObject("ADODB.Connection")식을 처리하였다면

set connDB=server.createobject("ADODB.Connection")

conns= "Provider=SQLOLEDB.1;Persist Security Info=False;User ID=입력한sql접속id;Password=비밀번호;Initial Catalog=데이터베이스이름;Data Source=sql서버ip"

connDB.open conns

 

access이고 본인이 ADODB.Connection 를 Set ConnDB=Server.CreateObject("ADODB.Connection")식을 처리하였다면

set connDB=server.createobject("ADODB.Connection")

conns= "rovider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\1.MDB;Persist Security Info=False"

connDB.open conns

 

sql이면 위의 것을 access면 아래것으로 기존 odbc 연결부분만을 갈아치우면 끝이다.

 

참고1.. 위 연결 스티링을 conn.asp 파일로 저장하여 놓고, include 로 db연결이 필요한 페이지에서 사용하면 더욱 편리하다.

참고2.. access의 경우, mdb 파일의 위치를 server.mappath로 찾도록 해두면, 파일명이나 경로가 변해도 사용할 수 있어 편리하다.

accessFileName = "1.mdb"
dblocations    = Server.MapPath(accessFileName)

set connDB=server.createobject("ADODB.Connection")

conns= "rovider=Microsoft.Jet.OLEDB.4.0;Data Source="& dblocations &";Persist Security Info=False"

connDB.open conns

 

- 2009년 5월 27일 수정


■ "SiteGalaxyUpload 컴포넌트" 설치 

자료실을 구축하기 위해서 "SiteGalaxyUpload 컴포넌트"를 설치해야 합니다. 
http://www.geocities.com/sitegalaxy/ 에서 구할 수 있습니다. 
컴포넌트를 설치한 후 SiteGalaxyUpload 컴포넌트에서 제공해 주는 객체(object)들을 사용할 수 있습니다. 


■ 파일올리기 연습 

SiteGalaxyUpload 컴포넌트를 사용해서 파일을 클라이언트에서 서버로 원격 업로드 하는 방법을 알아 보도록 하자. 먼저 파일이 올라갈 폴더(C:\temp)를 만든다. 만약 NT의 경우라면(NTFS) 모든사람에게 쓰기권한까지 해 주어야 한다. 

<HTML> 
<HEAD> 

<TITLE>파일업하기 연습</TITLE> 

</HEAD> 

<BODY> 

<FORM action="upload_ok.asp" method=post encType="multipart/form-data" > 

업로드할 파일 찾아보기: <INPUT name=file type=file> 
<INPUT type="submit" value="파일올리기"> 

</form> 

</BODY> 

</html> 


먼저 위와 같이 입력 폼을 만든다. 주의해야 할 점은 <form>태그에 encType="multipart/form-data" 를 넣어야 한다는 것이다. 
enctype은 내용을 송신할 경우에 사용되는 타입으로 기본타입은 application/x-www-form-urlencodeed 고, 이것을 이용해서 CGI를 실행하는데 input 테그에 type=file 이 들어갈 경우에는 encType="multipart/form-da 로 지정해야한다 

<% set up = Server.CreateObject("SiteGalaxyUpload.Form") 
set fs = server.CreateObject("Scripting.FileSystemObject") 
fn = "c:\temp\" & fs.GetFileName(up("file").FilePath) 
up("file").SaveAs(fn) 
%> 
<html> 
<body> 
up("file").FilePath(올릴파일의 경로) : <%=up("file").FilePath%> <br> 
fn(저장될 파일의 경로) : <%= fn %> 
</body> 
</html> 
<% set fs = Nothing 
set up = Nothing %> 


SiteGalaxyUpload.Form객체의 인스턴스를 up로 만들고 FSO객체의 인스턴스를 fs로 만들었다. 
up("file")은 upload.htm에서 넘긴 파일 경로를 받아오게 된다. 
up("imagefile").FilePath 는 upload.htm에서 넘긴 파일 경로를 받아오게 된다 
GetFileName는 FSO객체의 메소드로 파일의 이름을 반환하게 된다. 
fn이라는 변수에는 파일이 저장될 서버의 절대경로가 지정이 되여지게 되며, SaveAS라는 메소드에서 파일을서버에 저장을 하게 된다. 
주의할 점은 윈도우NT 사용자라면 NTFS로 포맷이 된 드라이브는 해당폴더(c:\temp)의 사용권한을 모든 사람이 쓸수 있도록 변경해야만 한다 
upload후 c:\temp폴더를 확인하면 결과를 알수 있을 것이다. 

  

■ 자료실 테이블 작성 

Access파일에 data라는 이름으로 테이블을 추가한다. 



테이블의 구조를 보면 기존의 게시판테이블(board)에 

  

1. 자료실 쓰기 폼 만들기(write.htm) 

기존의 게시판의 글올리기 폼에서 자료를 선택할 수 있는 부분이 첨가된 것이다. 
Form태그에 ENCTYPE="MULTIPART/FORM-DATA" 를 추가해야만 파일업로드가 가능하다. 
<form action="upload.asp" method="post" ENCTYPE="MULTIPART/FORM-DATA" > 
<table width="600" border="1" cellspacing="0" cellpadding="7" align="center" bordercolor=" 
2. 자료실 쓰기 실제 수행(upload.asp) 

write.htm에서 이름, 글제목, 글내용, 파일을 입력 받을 것을 upload.asp에서 실제적으로 서버에 파일을 업로드를 하고 DB에 글을 저장하게 된다. 
(아래 프로그래밍에서는 C:\TEMP)에 저장되는 것으로 프로그래밍되어 있다 
실제 저장될 폴더를 만들고, 만약 NTFS로 포맷이 되었다면 폴더에 쓰기 권한을 주어야만 파일을 쓸수 있다. 

<% 
set Up = Server.CreateObject("SiteGalaxyUpload.Form") 
name = Up("name") ' name이라는 변수에 write.htm에서 받은 값을 저장한다. 
email = Up("email") 
homepage = Up("homepage") 
content = Up("content") 
title = Up("title") 
pwd = Up("pwd") 
file = Up("file") 

'---------------------- 첨부파일이 있을 때만 파일 업로드 작업시작---------------------------- 
if Len(file) > 0 then 


'업로드 크기 제한 - 2MByte까지 
if Up("file").Size > 2000*1024 then 
Response.Redirect "upload_fail.asp" 
End If 
Set fs = CreateObject("Scripting.FileSystemObject") 
'--------------------------------------------------------------------------- 
' 같은 파일명이 있을 경우 처리 시작(올리는 파일명 뒤에 일련번호를 넣어준다.) 
FileName = fs.GetFileName(up("file").FilePath) 
strName = Mid(FileName, 1, Instr(FileName, ".") - 1) ' 확장자를 제외한 파일명을 얻는다. 
strExt = Mid(FileName, Instr(FileName, ".") + 1) '확장자를 얻는다. 

bExist = True ' 우선 같은이름의 파일이 존재한다고 가정 
countFileName = 0 ' 파일이 존재할 경우, 이름 뒤에 붙일 숫자를 세팅함. 
FileName = "c:\temp\" & FileName 
Do While bExist ' 우선 있다고 생각함. 


If (fs.FileExists(FileName)) Then ' 같은 이름의 파일이 있을 때 

countFileName = countFileName + 1 '파일명에 숫자를 붙인 새로운 파일 이름 생성 
FileName = "c:\temp\" & strName & "_" & countFileName & "." & strExt 
Else '같은 이름의 파일이 없을 때 
bExist = False ' 파일이 존재하지 않으므로. 
End If 
Loop 
'------------------------------------------------------------------------------------ 
' 같은 파일명이 있을 경우 처리 시작 끝 
Size = Up("file").Size 
Up("file").SaveAs FileName ' 파일을 실제로 저장한다. 
FileName = fs.GetFileName(FileName) '파일명에는 경로가 들어가 있으므로 이경로를 없앤다 
'다운로드시 경로가 있으면 문제가 생김 
set FS = Nothing 

' 파일의 크기를 kb단위로 보여주기 위해 사용되는 코딩 
temp = CLng(Size) 
If temp > 1024 Then '1024byte(1Kbyte)보다 크면 
temp=temp / 1024 
Size = CStr(CInt(temp)) & "K" 
End If 



end if 
'----------------------첨부파일이 있을 경우의 작업 끝---------------------------------------------- 

Set objConn = Server.CreateObject("ADODB.Connection") 
objConn.Open "MySiteDB" 
strSQL = "Select MAX(num) from data" 
Set rs = Server.CreateObject("ADODB.RecordSet") 
rs.Open strSQL, objConn 

strSQL = "Insert into data(name,title,email,file_size,content, file_name,pwd,writeday,homepage) " 
strSQL = strSQL& " values('" & name & "','" & title & "','" & email & "','" & Size & "','" & content 
strSQL = strSQL & "','" & FileName & "','" & pwd & "','"& date & "','"&homepage&"')" 

objConn.Execute(strSQL) 
objConn.Close 
Set objConn = Nothing 
set Up = Nothing '이 객체를 이용해서 받은 값의 이용이 다 끝났을 경우에 이 객체를 버린다 
response.redirect "list.asp" ' 자바를 이용해 이동할 경우 refresh된 페이지를 보여주는 것이 
'아니라 기존의 페이지를 보여주므로 rediect문을 이용하여 페이지를 이동한다 
%> 


■ 자료실 목록(list.asp) 

목록은 게시판의 목록과 거의 비슷하며 파일첨부 처리만 해 주면 되겠다. 제목부분(content.asp)와 첨부파일(upload.asp)로 링크가 되어 있다. 둘다 마찬가지로 DB테이블의 board_idx값을 넘겨 주게 된다. 이래에 코딩된 페이지에서는 페이지를 나누는 기능이 없고 화면구성이 짜임새 있게 마무리된 상태가 아니므로 기존의 게시판 코딩을 이용해 화면구성을 다듬고 페이지를 나누는 기능을 추가하는 것이 좋다 

<% 
Set conn = Server.CreateObject ("ADODB.Connection") 
conn.Open ("MySiteDB") 
Set rs = Server.CreateObject ("ADODB.Recordset") 
strSQL = "Select * from data Order by num DESC" 
rs.Open strSQL, conn, 1 
%> 

<html> 
<head><title>자료실</title></head> 
<body bgcolor=" 

※ 문제 
1. 위의 코딩에 페이지 분할 기능을 추가해 넣자 
2. 다운로드된 수를 저장하기 위해 필드를 하나 더 추가해 보자 (이 필드를 추가 하게 되면 db가 변경되는 것이 므로 거의 모든 작업에서 이 필드에 관한 조작을 추가해 넣어야 한다) 
3. 파일의 확장자를 분석해서 각각의 확장자에 맞는 아이콘을 파일명 앞에 붙여 놓자 (zip파일은 zip파일을 의미하는 아이콘으로) 

■ 자료실 내용보기(content.asp) 


자료실의 내용보기는 게시판의 내용보기에 첨부파일부분만 처리해 주면 된다. 

<% 
Set conn = Server.CreateObject ("ADODB.Connection") 
conn.Open ("MySiteDB") 
UpdateSQL = "Update data Set readnum = readnum + 1 where board_idx =" & request("idx") 
conn.Execute UpdateSQL 

Set rs = Server.CreateObject("ADODB.Recordset") 
SQL = "SELECT * FROM data where board_idx=" & request("idx") 
rs.Open SQL,conn,1 
content = replace(rs("content"),chr(13) & chr(10),"<br>")%> 

<html> 
<body bgcolor=" 

<table width="600" border="1" cellspacing="0" cellpadding="7" align="center" bordercolor=" 

 목록과 마찬가지로 확장자별 이미지를 처리해 주고 파일정보를 게시판의 내용보기처럼 보여주고 있다. 

  

■ 자료실 내려받기(download.asp) 

URL에 선택된 파일을 붙여서 다운로드가 이루어지게 된다. 여기서 이전에 만든 서버의 파일저장 공간(C:\temp)는 가상디렉토리로 설정이 되어야만 가능하다. 그래서 아래 코드는 C:\temp폴더를 data라는 가상디렉토리로 IIS에서 등록해 놓 았다는 전제하에 작성된 것이다 

<% 
Set conn = Server.CreateObject ("ADODB.Connection") 
conn.Open ("MySiteDB") 
Set rs = Server.CreateObject ("ADODB.Recordset") 
idx = Request.QueryString ("idx") 
strSQL = "Select * from data where board_idx=" &idx 
rs.Open strSQL, conn,1 
URL = "http://210.90.238.39/data/" & rs("file_name") 
Response.Redirect URL 
rs.Close 
conn.close 
%> 
<script language=javascript> 
history.back 
</script> 




  

■ 자료 삭제하기(del.asp, del-ok.asp) 

일단 비밀번호를 입력 받아 pwd라는 변수 이름으로 del_ok.asp로 보낸다. 여기서 주의해야 할 점은 게시물의 고유번호인 idx값을 넘겨준다 
action="pds_del_ok.asp?idx=<%=request("idx")%>" 와 같은 표현 대신 <input type=hidden>을 이용해 인덱스키값을 넘겨 줘도 된다 

<html> 

<head> 

<title>자료실 삭제</title> 

</head> 

<body> 

<form action="pds_del_ok.asp?idx=<%=request("idx")%>" method="post" > 

암호:<INPUT type="password" name="pwd"> 

<input type="submit" name="submit" value=" 삭 제 "> 

<A href="list.asp">&lt;리스트로 돌아가기&gt;</A> 

</form> 

</body> 


 삭제시 num값에 대한 조작이 없기 때문에 list로 돌아와 보면 삭제된 번호가 그래로 반영되어 보이지 않는다. 
이런 부분을 수정하고자 경우에는 list.asp를 수정해주거나 이곳에서 num값을 update해 주는 것이 좋다 

<% 
set db= Server.CreateObject("ADODB.Connection") 
db.Open("MySiteDB") 
Set rs=Server.CreateObject("ADODB.RecordSet") 
SQL1 = "Select * from data Where board_idx =" &request("idx") 
rs.Open SQL1,db,1 
If Request("pwd") = rs("pwd") Then '암호가 일치하면 등록된 파일 삭제 
Set objFS = Server.CreateObject("Scripting.FileSystemObject") 
DeleteFile = "c:\temp\" & rs("File_Name") 
If objFS.FileExists (DeleteFile) then 'upload되어 있는 파일 삭제 
objFs.DeleteFile DeleteFile 
End If 

'DB에서 레코드 삭제 
strSQL = "Delete from data where board_idx = " &request("idx") 
db.Execute strSQL 
Response.Redirect "list.asp" 
Else %> 
<script language = "javascript"> 
alert("암호가 맞지 않습니다.다시 확인해보세요~!"); 
history.back(); 
</script> 
<%End If%> 

조인 방식 (Join Method)

 

MS SQL에서 지원하는 조인 메소드에 대해 알아보자.

 

 

1. 들어가며

MS SQL에서 지원하는 물리적인 조인 방식에는 크게 3가지가 있다.

 

① 중첩반복(Nested Loops)

② 정렬병합(Sort Merge)

③ 해시매치(Hash Match)

 

이중 Nested Loops와 Sort Merge는 어느 DBMS든 가장 전통적인 조인 방식이고 서로간의 단점을 보완하고자 나왔다. Hash Match의 경우는 위의 두 조인 방식의 단점을 보완하고자 나온 방식이다. 그렇다면 Nested Loops와 Sort Merge의 장점, 특징 등을 알아보고 두 조인 방식의 단점이 무엇이길래 Hash Match가 나왔느냐에 대해 알아보는 것이 이번 주제에 대한 수순임이 분명하다.

 

참고:

초보들을 위해 한마디 하자면...

- 논리적인 조인이란 INNER / LEFT / FULL OUTER / 세미조인 등을 말한다.

- 여기서 설명하는 것은 그것말고 물리적인 것을 얘기하는 것이다.

(GUI 실행계획을 보면 네모친 부분을 말한다.)

 

 

2. 방식 비교

(1) 중첩반복(Nested Loops) 조인

① 그래프 실행 계획 아이콘

그래프 실행 계획 아이콘에서 보듯이 반복처리 안의 뭔가를 또 반복처리하고 있다. 유심히 보라. 괜히 만들어진 아이콘은 아니다.

 

② 수행 방식

아래와 같은 쿼리가 있다고 하자.

 

SELECT COL1, COL2
FROM TAB1 A
INNER JOIN
TAB2 B
ON A.KEY = B.KEY
WHERE A.KEY = '111
 AND A.COL1 LIKE '222%'
 AND B.COL2 = '333'

 

Nested Loops 처리 방식을 그림으로 보면...

 

 

두가지 가정

- 옵티마이저가 통계 데이터를 보고 TAB1을 선행 테이블(먼저 읽히는 테이블)로 선택

- A.KEY, B.KEY에만 인덱스가 잡혀있다. (Non-Clustered INDEX, 클러스터드라면 인덱스 리프 페이지가 데이터 페이지라 별도의 TAB 액세스는 없을 것이다.)

 

1)  인덱스 페이지에서 A.KEY로 111을 찾는다. 이때, 인덱스 페이지가 KEY컬럼으로 정렬되어 있다면 스캔 방식으로 그대로 인덱스 페이지를 읽어나갈 것이고 없으면 스캔 방식으로 한번에 읽는 것이 아무래도 유리하기 때문에 정렬을 한다.

2) 인덱스 페이지에서 찾은 결과로 만약 A.KEY가 넌클러스터드 인덱스라면 RID를 통해 TAB1의 데이터 페이지를 접근하고 클러스터드 인덱스라면 리프(Leaf) 페이지가 데이터 페이지이기 때문에 별도의 데이터 페이지 접근 없음. 여기서 COL1 LIKE '222%'조건으로 한번 더 필터링 된다.

3) TAB1의 결과를 A.KEY = B.KEY 연결고리를 통해 INDEX2를 랜덤 액세스를 한다.

즉, B.KEY = '111'(데이터를 읽었기에 상수값으로 변경)로 랜덤 액세스를 한다. 이때 INDEX2(B.KEY)에 클러스터드 인덱스가 걸려 있으면 실행 계획에는 Clustered Index Seek가 뜬다. (당연한가?^^) 근데, 앞서 예시한 SQL문의 WHERE절에 B.KEY = [검색인자] 가 별도로 명시되어 있지 않음에도 실행 계획에 Clustered Index Seek가 있다는 점!!! 이런 조인 원리 모르는 사람에게는 헉~ 이건 어디서 나오는거지? 하면서 의문을 갖는 점이기도 하다.

4) TAB2에서도 2)와 동일한 방식으로 처리되어 최종 운반단위로 보내진다.

5) 반복

 

 

③ 특징 정리

- 순차적

일련의 어떤 흐름과 같이 첫 테이블 필터링에서부터 두 테이블간의 연결 및 최종 운반단위 산출까지 반복적이며 순차적으로 진행된다.

 

- 선행적

선행 테이블의 처리 범위가 전체 일의 양을 결정한다. 즉, 후행 테이블의 필터링 조건은 선행 테이블에서 나온 결과 ROW를 한번 더 걸러주는 체크 조건 역할을 할뿐 전체 처리량을 좌우 하는게 아니다. 다만, 부분 범위 처리를 할때 후행 테이블의 처리 범위가 넓다면 운반 단위를 빠르게 채울 수 있으므로 더 좋은 성능을 낼 수도 있다.

 

부연설명 하자면, 위의 예시 SQL문에서

... (생략) ...

WHERE A.KEY = '111-- 요놈과 ...
 AND A.COL1 LIKE '222%-- 요놈이 전체 일의 양을 결정
 AND B.COL2 = '333-- 요놈은 최종 결과를 내보내기 전에 체크만 한다. 다만, 부분 범위 처리를 하고자 할 때 요놈이 없으면 빨리 빨리 운반단위를 채울 수 있으므로 더 빠를 수 있다.

 

- 종속적

후행 테이블은 선행 테이블의 결과값을 받아 처리된다. 즉, 선행 테이블의 결과에 종속적이다. 종속적이 되어서 나쁜 점이라고 하면 후행 테이블의 인덱스를 전체 일의 양을 줄여줄 수 있는 필터링 조건으로 사용 못한다는 점(다시 한번 강조하지만, 체크조건으로만 쓰임)이고 좋은 점은 이것을 역으로 전략적으로 이용할 수 있다는 것이다.

이런 경우를 생각해보자.

 

 

수강 테이블 : 과목코드에 Clustered Index

학생 테이블 : 학번에 Clustered Index

학생과 과목 엔터티의 관계는 M:M관계이다. 따라서, 이를 풀기 위해 수강이라는 엔터티가 도입되었다. 이때, 예를 쉽게 하기 위해 과목 엔터티는 일단 무시한다. 필자의 억지스런 예를 위해서다. :)

 

문제 : 자료구조(과목코드 : 000)를 수강한 모든 학생을 찾아라.

조인을 단순화하기 위해 걍 과목코드를 부여했다. 이해하시라. 그렇다면 쿼리는

 

SELECT 학번, 학생명

FROM 수강 A

INNER JOIN

학생 B

ON A.학번 = B.학번

WHERE A.과목코드 = 000

 

와 같이 될 것이다. 주어진 문제에 따르면 학생 테이블은 WHERE절을 통해 필터링 할 인덱스 컬럼이 없다. 바로 이런 경우...수강과 학생 테이블이 M:1 관계긴 하나 한 학생이 동일한 과목을 두번 수강할리는 없기에(혹 재수강? 커헉~ 그런건 없다고 가정 -_-+) 수강 테이블의 ROW수는 전교 학생들이 그 과목을 모두 수강한다해도 학생 테이블보다 똑같으면 똑같았지 클리가 없다. 따라서, 옵티마이저는 수강 테이블을 선행 테이블로 선택하고 학생 테이블을 후행 테이블로 선택한다.(아니라면 걍 그렇다고 치자. -_-;;) 이에, 수강 테이블에서 과목코드 000인 놈을 모두 찾고 같은 ROW에 학번이 있으니 그 학번으로 학생 테이블을 액세스 (즉, B.학번 = A.학번에서 수강 테이블은 읽혀졌으니 상수값으로 변경되어 B.학번 = '111', B.학번 = '222', B.학번 = '333', ... B.학번 = 'NNN' 이런 식으로)하여 Clustered Index Seek를 한다. 멋지군....짝짝짝...

 

 

- 랜덤 액세스

A.KEY = B.KEY로 선행 테이블의 결과를 통해 후행 테이블을 액세스 할때 랜덤 I/O가 발생한다. 선행 테이블은 최초 ROW만 랜덤 액세스가 발생하고 이후에는 스캔 방식으로 진행한다.

 

- 연결고리 중요성

A.KEY = B.KEY에서 보듯이 TAB1의 처리 ROW를 가지고 TAB2의 인덱스 페이지를 액세스하기 때문에 TAB2의 인덱스 유무가 굉장히 중요하다. 만약, TAB2에 인덱스가 없다면 옵티마이저는 TAB2를 후행 테이블로 선택하지 않는다. 다시 말해 TAB2를 선행 테이블로 선택한다. TAB2를 테이블 스캔하여 나온 결과 ROW를 가지고 인덱스가 있는 TAB1를 액세스하는게 성능면에서 유리하기 때문이다. 

 

 

④ 언제 쓰면 좋은가?

- 부분 범위 처리

다른 조인 방법은 부분 범위 처리가 원천적으로 불가능하나 이 조인 방식에서는 부분 범위 처리가 가능하다. 단, MS-SQL에선 정렬을 하면 처리가 유리해진다라는 판단이 서면 서버에서 자동으로 정렬을 수행하여 사용자가 이를 제어하지 못하므로 부분범위 처리가 불가능해 질 수도 있다.

 

- 처리의 방향성이 필요

다른 테이블의 처리 결과를 받아야만 처리 범위를 확 줄여줄 수 있을 때 사용하면 좋다.

 

- 처리량이 적다.

위에서도 계속 언급한 랜덤 I/O 때문에 선행 테이블의 카디널리티를 획기적으로 줄일 수 있다면 나머지는 수학적인 반복 연결이기에 메모리를 가장 적게 사용하는 좋은 조인 방식이 된다.

 

이 조인 방식의 최대 단점은

두 테이블을 연결할 때의 랜덤 I/O가 가장 큰 부담이다.

 

 

 

(2)  정렬병합(Sort Merge) 조인

① 그래프 실행 계획 아이콘

두개의 테이블이 있고 양쪽을 합치는 듯한 표현...잘보면 재미있다.

 

② 수행 방식

Nested Loops에서 써먹은 쿼리를 그대로 사용하자.

 

SELECT COL1, COL2
FROM TAB1 A
INNER JOIN
TAB2 B
ON A.KEY = B.KEY
WHERE A.KEY = '111'

 AND A.COL1 LIKE '222%'
 AND B.COL2 = '333'

 

Sort Merge 처리 방식을 그림으로 보면...

 

가정

- A.KEY, B.KEY에만 인덱스가 잡혀있다.

 

1) INDEX1에서 A.KEY가 111인 것들을 찾은 후 A.COL1 LIKE '222%'조건으로 최종 필터링된 결과행을 연결고리인 A.KEY의 값으로 정렬해둔다.

 

2) TAB2는 인덱스를 사용 할 수 없으므로(B.KEY로 검색하는게 없음.) TAB2를 테이블 스캔하여 B.COL2 = '333'가 만족하는 행을 연결고리인 B.KEY의 값으로 정렬해둔다. 여기서 알아둘 사항은 1)이 일어난 후 2)가 일어나는게 아니라 1)2)는 동시에 일어난다.

 

3) 두 개의 정렬된 결과를 가지고 A.KEY = B.KEY를 만족하는 결과를 병합(Merge)을 하면서 운반단위로 보내진다. 여기서 병합을 한다는건 양쪽 값을 스캔 방식으로 비교를 해나가다가 어느 한쪽의 값이 커지면 멈추고 커진 값을 다른 쪽 값을 비교하면서 다시 내려가는 것을 말한다. 그러다가 어느 한쪽이라도 EOF(ROW의 끝?)를 만나면 전체 처리 과정이 종료된다.

 

 

③ 특징 정리

- 동시적

Nested Loops 조인에서는 한쪽 테이블이 읽혀져야(선행 테이블) 후행 테이블을 액세스 할 수 있었다. 즉, 순차적으로 액세스 되었다.  하지만, 이 조인 방식은 양쪽 테이블을 동시에 읽고 양쪽 테이블이 조인할 준비가 되었을 때 조인을 시작한다. 즉, 어느 한쪽의 테이블의 처리가 늦어지면 다른 한쪽은 대기해야 한다.

 

- 독립적

Nested Loops 조인에서는 선행 테이블의 처리 결과 ROW가 후행 테이블의 연결고리의 상수값으로 사용되었다. (즉, B.KEY = '111'<- A.KEY가 읽혀져 상수값으로 변경됨) 하지만, 이 조인에서는 처리범위를 줄일 수 있는 거의 유일한 수단은 각자가 가지고 있는 필터링 조건이다. 서로의 테이블이 어떻게 필터링 됐는가는 별 관심이 없다.

 

- 전체 범위 처리

정렬 작업 완료 후에 조인이 일어나므로 부분 범위 처리가 되지 않는다.

 

- 연결고리

정렬된 양쪽 결과를 스캔하는 방식으로 조인이 일어나므로 연결고리는 크게 중요하지 않다. 그냥 사용하지 않는다고 보면 된다.

 

- 체크 조건의 의미

Nested Loops의 후행 테이블의 필터링 조건은 단지 선행 테이블에서 처리된 내용을 최종 운반 단위로 보낼때 그 양을 줄여주는 역할만 할뿐 전체 처리량을 줄이지는 못한다. 즉, 선행 테이블의 처리 결과 ROW수가 10건이었다하면 이 10건을 운반단위로 보내기 전에 후행 테이블의 필터링 조건을 확인하여 조건을 만족하는지 체크만 할 뿐이다. 모두가 다 만족하면 10건이 보내지겠고 만족하지 못하는 ROW가 있으면 제외가 된다. 근데, Sort Merge의 경우 인덱스를 사용하지 않는 단순 체크 조건이라도 머지할 범위를 줄여주기 때문에 상당한 의미가 있다.

 

 

④ 언제 쓰면 좋은가?

- 처리량이 많을때

Nested Loops는 처리량이 많아지면 랜덤 액세스에 대한 부담이 극심해진다. 이때 스캔방식으로 조인되는 Sort Merge를 사용하면 성능상의 이점이 있다.

 

- 연결고리 상태 이상

Nested Loops는 연결고리의 상태가 굉장히 중요하다고 했다. 따라서, 한쪽의 연결고리에 이상이 발생하면 Nested Loops는 심히 고려해봐야 한다. 이때, 연결고리에 영향을 받지 않는 Sort Merge를 쓰면 좋다.

 

 

이 방식의 최대 단점은

정렬에 따른 부담이다.

정렬은 tempdb를 사용한다는 사실은 누구나 알고 있다. 정렬할 양이 극도로 많아 tempdb의 임계치를 넘어버린 경우에는 tempdb에 페이지 할당이 발생하여 순간 전체 데이터베이스에 페이지 잠금이 발생하는 등 DB성능에 심각한 영향을 줄 수도 있다.

물론, 가공없이 Clustered Index를 그대로 사용하게 되면 정렬은 안해도 되니 이때만큼은 정렬의 부담에서 해방된다. 근데, Group By니 Distinct 라든지 이미 정렬된 ROW를 2차 가공하게 되면 이땐 방법이 없다. 무조건 정렬이다.

 

참고 :

MS-SQL에서는 양쪽 테이블에서 필터링되어 나온 값이 각 테이블에서 유니크(Unique) 할때만 이 조인 방식을 사용하려는 경향이 있다는 것을 기억하자. 연결고리로 사용할 키값에 중복이 심하면 잘 선택하지 않으려 한다.

 

 

두 조인의 단점

- Nested Loops : 랜덤 액세스

- Sort Merge : 정렬 (메모리 사용 증가)

 

이렇다는걸 확인 할 수 있다. 그렇다면 Hash Metch가 나온 배경이 어느 정도 짐작이 가지 싶다.

 

 

 

(3) 해시매치(Hash Match) 조인

① 그래프 실행 계획 아이콘

아이콘에서 보듯이 어떤 특정한 값으로 액세스하는 모양이다.

 

Nested Loops가 랜덤 액세스에 대한 부담이 있더라도 적은 양(대용량이라도 선행 테이블의 카디널리티를 획기적으로 줄일수만 있다면)의 데이터를 처리하기에는 이만한 조인은 없다. Sort Merge는 정렬의 부담은 있지만 연결고리에 이상이 있는 경우의 대용량을 처리하기에는 괜찮은 조인 방식이다. 문제는 정렬 자체에 있다기보다 정렬해야 할 양이 너무 많아져 tempdb의 용량을 넘어서 별도의 페이지 할당이 일어나는데 있다. 페이지 할당이 일어나면 할당 잠금이 발생하여 경합의 정도에 따라 서버가 응답하지 않을 수도 있다.

 

이에 해시 조인은 이 둘의 단점에 대한 대안이 될 수 있다.

 

② 개념 및 수행 방식

이 글을 쓰면서도 해시 조인에 대한 내용을 온라인 북과 웹(테크넷, 구글 등), 국내/국외 서적에서 찾아보았는데 정말 내용이 추상적이기만 하고 잘 설명된 서적이나 싸이트가 없었다. 물론, 위에 Nested Loops나 Sort Merge도 내용이 잘 나와 있는건 아니다. 단지, Nested Loops나 Sort Merge는 전통적인 방식이라 어느 DBMS든 비슷한 방식으로 동작 하기에 잘 설명된 내용을 기초로 적긴 했는데 Hash Match는 좀 다르단다. 요즘에도 그런지 모르나 DB2는 이 Hash Join의 개념이 없다고 하니 Hash와 관련된 상세한 원리는 Re-command를 하질 않는다나? 이건 뭔지...(이거 루머일수도 있다 주의)

 

일단 걍 MSSQL 온라인 북에 있는 내용을 살펴보도록 하자. 물론, 봐도 이해는 안가지 싶다.

 

해시 조인에는 빌드 입력과 검색 입력 등 두 가지 입력이 있습니다. 쿼리 최적화 프로그램은 두 가지 입력 중 작은 쪽이 빌드 입력이 될 수 있도록 이러한 역할을 할당합니다.

해시 조인은 여러 가지 유형의 집합 일치 연산, 즉 내부 조인, 왼쪽, 오른쪽, 완전 외부 조인, 왼쪽 및 오른쪽 세미 조인, 교집합, 합집합, 차집합 등에 사용합니다. 또한, 해시 조인의 변형은 중복 요소 제거 및 그룹화(예: SUM(salary) GROUP BY department)를 수행할 수 있습니다. 이러한 수정에서는 빌드 및 검색 역할 모두에 대해 한 개의 입력만 사용합니다.

다음 섹션에서는 인-메모리 해시 조인, 유예 해시 조인 및 재귀 해시 조인 등 여러 해시 조인 유형을 설명합니다.

해시 조인은 먼저 전체 빌드 입력을 스캔하거나 계산한 다음 해시 테이블을 메모리에 작성합니다. 해시 키에 대해 계산된 해시 값에 따라 각 행이 해시 버킷에 삽입됩니다. 전체 빌드 입력이 사용 가능한 메모리보다 작으면 모든 행을 해시 테이블에 삽입할 수 있습니다. 이 빌드 단계 다음으로는 검색 단계가 이어집니다. 전체 검색 입력은 한 번에 한 행씩 스캔 또는 계산되며, 각 검색 행에 대해 해시 키 값이 계산되고 해당 해시 버킷이 스캔되며 일치하는 항목이 생성됩니다.

빌드 입력이 메모리 크기에 맞지 않으면 해시 조인은 몇 개의 단계로 진행됩니다. 이것을 유예 해시 조인이라고 합니다. 각 단계마다 빌드 단계와 검색 단계가 있습니다. 처음에는 전체 빌드 및 검색 입력이 사용되며 해시 키에 대한 해시 함수를 사용하여 여러 파일로 분할됩니다. 해시 키에 대한 해시 함수를 사용하면 2개의 조인 레코드가 모두 동일한 파일 쌍에 있는 것이 보장됩니다. 따라서 2개의 큰 입력을 조인하는 작업이 동일한 작업의 여러 개의 작은 인스턴스로 축소되었습니다. 그런 다음 해시 조인은 분할된 파일의 각 쌍에 적용됩니다.

빌드 입력이 너무 커서 표준 외부 병합에 대한 입력에 여러 개의 병합 수준이 필요한 경우에는 여러 개의 분할 단계와 여러 개의 분할 수준이 요구됩니다. 일부 파티션만 큰 경우에는 해당 파티션에서만 추가 분할 단계가 사용됩니다. 모든 분할 단계를 가능한 한 빠르게 유지하기 위해서는 단일 스레드가 여러 개의 디스크 드라이브를 사용 중인 상태로 유지할 수 있도록 대형의 비동기 I/O 작업이 사용됩니다.

참고:
빌드 입력이 사용 가능한 메모리보다 조금밖에 크지 않다면 인-메모리 해시 조인과 유예 해시 조인의 요소가 단일 단계에서 결합되어 하이브리드 해시 조인이 생성됩니다.

최적화 중에 사용될 해시 조인을 확인하는 것이 항상 가능한 것은 아닙니다. 따라서 SQL Server 는 빌드 입력의 크기에 따라 인-메모리 해시 조인을 사용하여 시작된 후 유예 해시 조인, 재귀 해시 조인으로 점차 전환됩니다.

2개의 입력 중 빌드 입력이 되어야 하는 작은 쪽을 최적화 프로그램이 잘못 예측하는 경우에는 빌드 및 검색 역할이 동적으로 바뀝니다. 해시 조인은 작은 쪽의 오버플로 파일을 빌드 입력으로 사용하게 합니다. 이 기술을 역할 반전이라고 합니다. 역할 반전은 하나 이상의 해시 조인이 디스크에 "spill"된 경우 해시 조인 내에서 발생합니다.

③ 특징 정리 

- 연결고리

각 테이블의 연결고리에 있는 인덱스는 사용하지 않는다. 대신 실시간(?) 인덱스가 생성되어 그것을 통해 조인을 한다. 물론, 조인이 종료되면 삭제된다.

 

- 조인 결과

조인의 결과는 정렬되지 않은 상태로 출력된다. 그래서, 특정 컬럼으로 정렬을 하고 싶다면 ORDER BY절을 이용해야 한다.

 

- 랜덤 액세스

랜덤 액세스가 있으나 Nested Loops와는 달리 빠른 랜덤 액세스란다. (뭔 말인지...)

 

- 메모리 사용

해시 버켓을 만들기 위해 메모리를 꽤 사용한다.

 

④ 언제 쓰면 좋은가?

- 연결고리에 이상이 있거나 연결고리의 인덱스를 사용하지 못할때

Sort Merge처럼 연결고리에 인덱스가 없어도 조인을 하는데 문제가 없다.

 

- 소량과 대용량 테이블을 조인 할때

이 경우가 가장 좋은 성능을 낸다. 소량과 소량을 연결하는데는 차라리 Nested Loops를 쓰는게 좋다. 토깽이를 잡는데 소잡는 칼 쓸 수 없지 않은가...

 

 

 

3. 마치며...

Hash는 상세하게 나온게 없어서 마지막이 흐지부지 됐다. (내가 원래 그럼...-_-;;) 혹시, Hash Join에 관해 상세하게 나와 있는 곳 아시는 분 링크 좀....그리고, 잘못된 점 있으면 지적 바랍니다.

 

스크롤 압박에도 여까지 읽어주시니 감사할 따름이지요.

+ Recent posts