본문 바로가기

IT for my Life/Database Construction

수백 배 빠른 쿼리, Access DAO 레코드셋

들어가며

레코드셋을 한 번만 불러온다고? 이게 무슨 뜻일까요? 서울에서 부산까지 컨테이너 한대 분량의 화물을 옮긴다고 가정해봅시다. 커다란 컨테이터 트럭을 한대 고용해서 한 번만 옮기면 되겠죠? 이 방법이 DAO로 레코드셋을 불러오는 방법입니다. 반면 퀵 오토바이를 사용하여 백 번을 왔다 갔다 하는 비효율적인 방법도 있을 거예요. 이것이 DAO 또는 ADAO를 사용하지 않고 데이터를 매번 불러오는 방법입니다. 엄청나게 비효율적이죠. 컴퓨터 프로그래밍 환경에서는 이게 더 심해집니다, 왜냐하면 현실과 다르게 코딩에서는 컨테이너 트럭이나 퀵 오토바이나 고용하는 데 드는 비용이 같기 때문이에요. 어떻게 이렇게 효율적으로 데이터를 가공할 수 있을까요?


이번 프로그래밍 예제에서 사용할 내용은 언어의 당일 번역량을 뽑아내는 것입니다. 이를 응용하면 전일, 금주, 금월, 금년 등의 다양한 단위로 데이터를 간편하게 추출할 수 있죠. 이론부터 말하자면 실제 번역을 한 기간오늘을 비교하여 교집합처럼 2개 기간이 겹칠 때만 해당 번역량을 가져오면 됩니다. 제가 쿼리 디자인에 적은 아랍어의 당일 번역량을 구하는 식은 다음과 같아요.

arabicToday: IIf([Assigned To]="Arabic",[dailyAverage]*[dteWorkingDiff],0)

하루 평균 변역량 구하기

여기서 중요한 것이 [dailyAverage]와 [dteWorkingDiff]죠. 먼저 [dailyAverage]부터 살펴봅시다. [dailyAverage]를 사람의 언어로 설명하자면 특정 과제(Assignment)의 하루 평균 번역량이랍니다. 굳이 왜 하루 평균 변역량을 구해야 할까요? 사전에 평균 번역량을 구해놓으면, 거기에 검색 기간만 곱해서 손쉽게 총 번역량을 산출할 수 있기 때문이에요. [dailyAverage]를 구하는 방법은 아래와 같습니다.

dailyAverage: [Volume]/fWorkingDays([Assigned Date],[Completion Date],"1,7")

[Volume]은 번역량을 단어량으로 기록해놓은 필드이며 이를 fWorkingDays 함수의 결과값으로 나눕니다. fWorkingDays() 전역 함수 설정 방법은 Access VBA 주말과 공휴일 제외하고 날짜 구하기 포스팅을 참고해주세요. 이렇게 함으로써 주말과 사용자 지정 공휴일을 제외한 정확한 일 평균 번역량을 구할 수 있습니다.

예를 들어 월요일에서 금요일에 걸쳐 번역을 진행했고, 분량은 총 500단어이며 중간에 공휴일이 안 끼여 있었다면, "500/(5-0)=100단어/일"이 됩니다. 그러나 동일한 조건에 수요일이 법정 공휴일이었다면 "500/(5-1)=125단어/일"이 됩니다.

교집합에 해당하는 기간 구하기

이제 남은 부분은 [dteWorkingDiff] 부분이죠? 이는 실제 번역 기간과 오늘 날짜를 비교하여 겹치는 일수를 구한 것입니다. 식은 아래와 같습니다.

dteWorkingDiff: IIf([dteRealEnd]>=[dteRealStart],fWorkingDays([dteRealEnd],[dteRealStart],"1,7"),0)

dteRealEnd와 dteRealStart 사이의 값을 구하는 거라 보시면 됩니다. 무슨 뜻이냐? 시작일(dteRealEnd)일 경우 업무가 할당된 날과 사용자가 검색 시작한 날 (이 경우는 당일 하루) 중 더 큰 날짜, 종료일(dteRealStart)일 경우 업무가 종료된 날과 사용자가 검색 종료한 날 (이 경우는 당일 하루) 중 더 작은 날짜를 구해서 첫 번째가 두 번째보다 같거나 크다면 두 날짜의 차이를 가져와서 쓰면 됩니다. 언제나 그렇듯 단순히 DateDiff 명령어 대신 fWorkingDays 함수를 사용하여 휴일을 고려해줘야 합니다.

좌측에 Assigned Date와 Completion Date가 있고 우측에 dteRealStart와 dteRealEnd가 있습니다. 검색일(이 경우는 당일)은 2020년 1월 2일입니다. 위의 식을 적용하면 해당되는 레코드만 dteWorkingDiff 값이 1로 뜹니다.

이렇게 구한 값을 위의

arabicToday: IIf([Assigned To]="Arabic",[dailyAverage]*[dteWorkingDiff],0)

에 넣기만 하면 아랍어 오늘 번역량이 튀어나오고 이를 DAO 레코드셋으로 받기 전에 Sum 쿼리로 한 번 더 묶어주는 작업이 필요합니다.

qryRealWork 쿼리를 qryRealWorkSum으로 한 번 더 불러오고 Sum으로 그룹화합니다. 이렇게 하는 것은 VBA에서 DAO 레코드셋으로 불러오기 쉽게 만들기 위함입니다.

VBA DAO로 불러와서 폼에 값 할당하기

이제 다 끝났습니다. 임의의 폼에 언바운드 컨트롤을 하나 생성하고 레코드셋으로 불러온 내용을 넣습니다. 폼 디자인에서 Form_Load 이벤트에 다음 내용을 넣어줍니다.

Private Sub Form_Load()

DoCmd.Close acForm, "WelcomeForm", acSaveYes

Dim rst As DAO.Recordset

Set rst = CurrentDb.OpenRecordset("SELECT Sum(SumOfarabicToday) as SumArabicToday, Sum(SumofJapaneseToday) as SumJapaneseToday, Sum(SumOfKoreanToday) as SumKoreanToday, Sum(SumOfSChineseToday) as SumSChineseToday FROM qryRealWorkSum")

With rst

Me.TxtArabicToday = !SumArabicToday
Me.txtJapaneseToday = !SumJapaneseToday
Me.txtKoreanToday = !SumKoreanToday
Me.txtSChineseToday = !SumSChineseToday

End With

End Sub

아랍어 외에도 한국어, 일본어, 중국어 간체가 들어가 있습니다.

마치며

짐작하실지 모르겠으나 이거 다 제가 직접 삽질하고서 찾아낸 방법이에요. DAO를 안 쓸 때엔 무식하게 도메인 함수(DSum)를 써서 여러 번 불러오는 바람에 폼 로딩 속도가 무척 느렸습니다. 머리가 나쁘면 몸이 고생한다는 말이 괜한 말이 아니에요. DAO 레코드셋으로 마치 흰 도화지에 그림을 그리듯, 나만의 폼 위에 마음껏 데이터를 흩뿌려봅시다!