VBA For 루프로 1,000행 매출 등급 분류하기
1000행을 분류할 때는 마지막 행을 놓치지 않는 것과 잘못된 금액을 정상 등급으로 만들지 않는 것을 함께 확인해야 합니다. 이 글에서는 10개 금액을 반복해 예상 등급 개수를 손으로도 계산할 수 있게 만든 뒤, 오류 입력을 별도 시트에 모읍니다. 속도 경쟁이나 실제 매출 분석이 아니라 행 범위와 입력 계약을 점검하는 실습입니다.
10행에서 1000행의 기대값을 먼저 계산하기
practice-61.xlsx의 Sample에는 95000,82000,75000,68000,92000,88000,71000,65000,99000,45000이 있습니다. 가상 등급 기준은 90000 이상 A, 80000 이상 B, 70000 이상 C, 나머지 비음수 정수 D입니다. 10행에서는 A 3개, B 2개, C 2개, D 3개입니다.
Scale1000은 이 순서를 정확히 100번 반복합니다. ID R0001부터 R1000까지 1000개이며 A 300, B 200, C 200, D 300이 독립 기대값입니다. 마지막 셀은 B1001=45000이고 마지막 등급은 D입니다. 제목의 1000행은 이 입력 크기이며 처리 시간을 측정했다는 뜻이 아닙니다.
| 시트 | 읽는 입력 | 매크로 출력 | 바꾸지 않을 기대값 |
|---|---|---|---|
| Sample | A2:B11 | C:D | E:F |
| Scale1000 | A2:B1001 | C:D | E:F |
| Boundary | A2:B15 | C:D | E:F |
G열 수식은 Check 상태가 DONE일 때만 실제 출력과 기대값을 비교합니다. 배포 파일은 C:D가 비어 있고 Check는 NOT RUN입니다. 기대값을 출력 칸에 미리 넣지 않았습니다.
마지막 행과 배열 행 번호를 나눠 읽기
For…Next에서 Step을 생략하면 증가량은 1입니다. 아래 모듈은 시트의 2행부터 마지막 입력 행까지 A:B를 배열로 읽은 후 For i = 1 To rowCount로 배열을 순회합니다. 시트의 2행은 배열의 1행이고, 시트 1001행은 배열 1000행입니다. 1000을 시트의 끝 행 번호로 쓰면 마지막 자료 한 건을 빠뜨립니다.
Range.End는 지정한 방향으로 영역 끝의 셀을 나타내는 Range를 반환합니다. 여기서는 A열과 B열의 마지막 값을 각각 조사해 더 큰 행을 사용합니다. ID가 빈 마지막 행에 금액만 남아 있어도 검사에 포함시키기 위해서입니다. E:F의 기대값이나 G열 수식을 마지막 입력으로 세지 않습니다. 데이터는 2~1001행에만 두고 그 아래 입력은 오류로 처리합니다.
전체 모듈과 실행 순서
XLSX를 새 XLSM 사본으로 저장하고 GradeRows.bas를 VBA 프로젝트에 가져옵니다. BAS 첫 줄은 가져오기용 메타데이터입니다. 회사의 매크로 정책이 허용하는 연습 환경에서 컴파일을 먼저 확인합니다.
-
Sample을 선택해 입력 10개와 E:F를 확인하고
GradeSample을 실행합니다. Check의 Sample 행에 처리 10, 유효 10, 거부 0이 나타나고 G열이 모두 일치하는지 비교합니다. -
Scale1000을 선택하고
GradeThousand를 실행합니다. Check에서 처리 1000·유효 1000을 확인한 뒤 등급 개수 300/200/200/300과 마지막 R1000의 D를 대조합니다. 첫 화면 몇 행이 맞는 것으로 전체 성공을 판단하지 않습니다. -
Boundary에서
GradeBoundary를 실행합니다. 처리 14·유효 7·거부 7과 각 행의 사유를 확인합니다. DONE은 반복 처리가 끝났다는 상태이며 모든 입력이 유효하다는 뜻은 아닙니다.
Attribute VB_Name = "GradeRows"
Option Explicit
Public Sub GradeSample()
ProcessGrades "Sample", 2
End Sub
Public Sub GradeThousand()
ProcessGrades "Scale1000", 3
End Sub
Public Sub GradeBoundary()
ProcessGrades "Boundary", 4
End Sub
Private Function AmountGrade(ByVal value As Variant, ByRef reason As String) As String
Dim amount As Double
reason = "OK"
If IsError(value) Then
reason = "CELL_ERROR"
Exit Function
End If
If IsEmpty(value) Then
reason = "EMPTY"
Exit Function
End If
If VarType(value) = vbString Then
If Len(value) = 0 Then
reason = "EMPTY"
Else
reason = "TYPE"
End If
Exit Function
End If
Select Case VarType(value)
Case vbByte, vbInteger, vbLong, vbSingle, vbDouble, vbCurrency, vbDecimal
Case Else
reason = "TYPE"
Exit Function
End Select
amount = CDbl(value)
If amount < 0 Then
reason = "NEGATIVE"
Exit Function
End If
If amount <> Fix(amount) Then
reason = "DECIMAL"
Exit Function
End If
Select Case amount
Case Is >= 90000: AmountGrade = "A"
Case Is >= 80000: AmountGrade = "B"
Case Is >= 70000: AmountGrade = "C"
Case Else: AmountGrade = "D"
End Select
End Function
Private Sub ProcessGrades(ByVal sheetName As String, ByVal reportRow As Long)
Const LAST_ALLOWED As Long = 1001
Dim ws As Worksheet, check As Worksheet
Dim lastRow As Long, candidate As Long, colNo As Long
Dim rowCount As Long, i As Long, validCount As Long
Dim data As Variant, outputs() As Variant, headers As Variant
Dim grade As String, reason As String, problem As String
Dim oldScreen As Boolean, oldEvents As Boolean
Dim checkReady As Boolean, outputReady As Boolean
oldScreen = Application.ScreenUpdating
oldEvents = Application.EnableEvents
On Error GoTo Failed
Set check = ThisWorkbook.Worksheets("Check")
If check.ProtectContents Then
Err.Raise vbObjectError + 611, , "Use an unprotected practice Check sheet."
End If
checkReady = True
Application.EnableEvents = False
Application.ScreenUpdating = False
check.Range("B" & reportRow & ":F" & reportRow).ClearContents
check.Cells(reportRow, 2).Value2 = "RUNNING"
Set ws = ThisWorkbook.Worksheets(sheetName)
If ws.ProtectContents Then
Err.Raise vbObjectError + 612, , "Use an unprotected practice input sheet."
End If
headers = Array("id", "amount", "grade", "reason", "expected_grade", "expected_reason", "match")
For colNo = 1 To 7
If IsError(ws.Cells(1, colNo).Value2) Then
Err.Raise vbObjectError + 613, , "Invalid practice header."
End If
If CStr(ws.Cells(1, colNo).Value2) <> headers(colNo - 1) Then
Err.Raise vbObjectError + 614, , "Practice headers must remain unchanged."
End If
Next colNo
outputReady = True
ws.Range("C2:D" & LAST_ALLOWED).ClearContents
ws.Calculate
lastRow = 1
For colNo = 1 To 2
candidate = ws.Cells(ws.Rows.Count, colNo).End(xlUp).Row
If candidate > lastRow Then lastRow = candidate
Next colNo
If lastRow > LAST_ALLOWED Then
Err.Raise vbObjectError + 615, , "Input is limited to rows 2 through 1001."
End If
rowCount = lastRow - 1
If rowCount = 0 Then GoTo Complete
data = ws.Range("A2:B" & lastRow).Value2
ReDim outputs(1 To rowCount, 1 To 2)
For i = 1 To rowCount
grade = ""
reason = "NAME"
If Not IsError(data(i, 1)) Then
If VarType(data(i, 1)) = vbString Then
If Len(Trim$(data(i, 1))) > 0 Then
grade = AmountGrade(data(i, 2), reason)
End If
End If
End If
outputs(i, 1) = grade
outputs(i, 2) = reason
If reason = "OK" Then validCount = validCount + 1
Next i
ws.Range("C2").Resize(rowCount, 2).Value2 = outputs
Complete:
check.Cells(reportRow, 2).Value2 = "DONE"
check.Cells(reportRow, 3).Value = Now
check.Cells(reportRow, 4).Value2 = rowCount
check.Cells(reportRow, 5).Value2 = validCount
check.Cells(reportRow, 6).Value2 = rowCount - validCount
ws.Calculate
check.Calculate
Application.EnableEvents = oldEvents
Application.ScreenUpdating = oldScreen
Exit Sub
Failed:
problem = Err.Description
On Error Resume Next
If outputReady Then ws.Range("C2:D" & LAST_ALLOWED).ClearContents
If checkReady Then
check.Range("B" & reportRow & ":F" & reportRow).ClearContents
check.Cells(reportRow, 2).Value2 = "ERROR"
check.Calculate
End If
Application.EnableEvents = oldEvents
Application.ScreenUpdating = oldScreen
On Error GoTo 0
MsgBox "Grade processing failed: " & problem & vbCrLf & _
"Do not reuse old output or an old timestamp.", vbExclamation
End Sub
14개 경계 입력에서 거부를 읽기
VarType은 변수의 하위 형식을 나타내는 정수를 반환합니다. 이 예제는 숫자 형식의 비음수 정수만 금액으로 받으며 숫자처럼 보이는 문자는 변환하지 않습니다.
| 입력 | 기대 등급 또는 사유 |
|---|---|
| 90000 / 89999 | A / B |
| 80000 / 79999 | B / C |
| 70000 / 69999 / 0 | C / D / D |
| 빈 금액 | EMPTY |
| 문자 95000, 문자 95,000원 | TYPE |
| -1 | NEGATIVE |
| 70000.5 | DECIMAL |
| =1/0 셀 오류 | CELL_ERROR |
| 빈 ID와 숫자 95000 | NAME |
문자 95000은 Boundary!B10에 문자로 저장돼 있습니다. CSV만 Excel로 열면 숫자로 자동 해석될 수 있어, 형식 거부 실험의 기준 파일은 XLSX입니다. =1/0은 의도적인 오류 입력이며 깨진 첨부가 아닙니다.
각 행의 오류는 빈 등급과 사유로 남기지만, 헤더 변경·보호·범위 초과는 전체 실행 오류입니다. 오류를 고치고 재실행할 때 C:D의 옛 결과는 지우도록 작성했습니다. 쓰기 실패가 나면 옛 시각이나 DONE을 신뢰하지 말고 새 사본에서 확인합니다. 실제 업무의 등급 규칙이나 반올림 정책을 이 가상 기준으로 대체하지 마세요.
검증 결과: 마지막 행과 처리 건수를 따로 대조하기
GradeThousand 실행 후 Scale1000!A1001=R1000, B1001=45000, C1001=D를 확인합니다. Check!D3의 처리 건수는 1000, E3의 유효 건수도 1000이어야 합니다. 마지막 셀만 맞고 중간 행이 빠지는 경우를 잡기 위해 Check의 실제 등급 개수를 독립 기대값 A300/B200/C200/D300과 비교하고 Scale1000!G2:G1001의 불일치가 0인지 함께 확인합니다. 어느 하나라도 다르면 완료 표시만으로 결과를 채택하지 않습니다.
독립 검산의 한계
Python 참조 계산으로 10행·1000행의 등급 분포, 1000번째 입력, 14개 경계 사유와 타입 보존을 확인합니다. 전체 BAS와 입력·테스트를 ZIP에 함께 넣었습니다. Microsoft Excel에서 VBA를 컴파일하거나 실행하지 않았습니다. 따라서 Excel의 실제 배열 쓰기, 오류 메시지, 이벤트 복구는 확인 대상입니다. Python 검산은 VBA 실행 증거가 아닙니다.
입력과 결과 칸을 찾는 기존 그림
그림은 실습 위치를 찾는 보조 자료입니다. 실제 실행 결과는 위 절차로 만든 사본의 값과 상태를 대조합니다.



공식 자료
VarType function (Visual Basic for Applications)
출처 원문 수집일: S1 2026-09-24; S2 2026-09-24; S3 2026-09-24.
이어서 연습할 글
반복 구조와 별도로 등급 구간의 경계값을 다루는 연습은 Select Case의 5단계·7단계 등급 분류에서 이어집니다.