VBA Select Case로 5단계·7단계 등급 분류하기
점수 구간을 80 To 89.999처럼 닫힌 숫자로 적으면 89.9999가 어디에도 들어가지 않는 틈이 생깁니다. 이 글에서는 0~100의 소수 점수를 허용하는 연습 계약을 정하고, 큰 하한부터 비교하는 5단계·7단계 등급을 나란히 계산합니다. 문자 라벨은 별도 분류로 다뤄 숫자로 억지 변환하지 않습니다.
점수와 문자 라벨은 다른 입력 계약이다
practice-62.xlsx의 Scores에는 23개 가상 입력이 있습니다. 0~100 범위의 실제 숫자는 소수를 포함해 유효합니다. 빈 셀, 문자 숫자, 음수, 100 초과, 셀 오류, Boolean과 빈 ID는 거부합니다. TextMap은 ID와 문자열 라벨만 받습니다. CSV는 형식이 자동 변환될 수 있으므로 타입 실험은 XLSX를 기준으로 합니다.
| 점수 구간 | 5단계 | 7단계 |
|---|---|---|
| 95~100 | A | S |
| 90 이상 95 미만 | A | A |
| 80 이상 90 미만 | B | B |
| 70 이상 80 미만 | C | C |
| 60 이상 70 미만 | D | D |
| 50 이상 60 미만 | F | E |
| 0 이상 50 미만 | F | F |
이 표는 가상 정책입니다. 소수를 정수로 반올림하지 않습니다. 94.9999는 A/A, 89.9999는 B/B, 85.5도 B/B입니다. 문자로 저장된 85.5는 TYPE이고 숫자 85.5와 구별됩니다.
조건 순서로 구간 틈을 없애기
Select Case에서 여러 조건이 일치하면 첫 번째 일치 조건의 문장만 실행됩니다. 아래 Grade5는 90,80,70,60의 하한을 큰 순서로 비교합니다. 89.9999는 90 이상이 아니고 80 이상이므로 B입니다. 반대로 Case Is >= 60을 맨 앞에 쓰면 95도 D가 되므로, 짧은 조건만 보고 순서를 바꾸면 안 됩니다.
잘못된 반례 Case 80 To 89.999는 89.9999를 포함하지 않습니다. 5단계 분류에서 이를 유일한 B 조건으로 쓰고 마지막 Case Else를 F로 두면 이 점수가 F로 잘못 내려갑니다. 아래 코드는 그 반례를 사용하지 않습니다. 실패를 재현할 때는 별도 연습 함수에서만 비교하고 완성 모듈을 덮어쓰지 마세요.
완전한 분류 모듈과 확인 순서
매크로를 보관할 사본은 Excel 매크로 사용 통합 문서(.xlsm)로 저장합니다. ZIP의 SelectCasePractice.bas를 그 VBA 프로젝트로 가져옵니다. 헤더와 기대값 열은 수정하지 않고 실제 출력 열만 매크로가 쓰게 둡니다.
-
Scores의 A:B와 기대값 F:H를 선택해 확인한 뒤
GradeScoreExample을 실행합니다. Check에서 처리 23, 유효 15, 거부 8을 확인하고 I열의 행별 대조도 확인합니다. -
5단계 분포 A4/B3/C2/D2/F4, 7단계 분포 S2/A2/B3/C2/D2/E2/F2를 독립 기대값과 비교합니다. 89.9999·85.5·문자 85.5 행을 각각 찾아 등급과 사유를 따로 확인합니다.
-
TextMap을 선택해
GradeTextExample을 실행합니다. 처리 12, 유효 5, 거부 7이 기대값입니다. VIP와 공백이 있는gold는 A, Silver는 B, BRONZE와 basic은 C입니다. 낯선 Goldx는 UNKNOWN이며 빈 값·숫자·Boolean·오류·빈 ID는 각각 사유를 확인합니다.
Attribute VB_Name = "SelectCasePractice"
Option Explicit
Option Compare Binary
Public Sub GradeScoreExample()
ProcessCases "Scores", 2, True
End Sub
Public Sub GradeTextExample()
ProcessCases "TextMap", 3, False
End Sub
Private Function ReadScore(ByVal value As Variant, ByRef score As Double) As String
If IsError(value) Then
ReadScore = "CELL_ERROR"
Exit Function
End If
If IsEmpty(value) Then
ReadScore = "EMPTY"
Exit Function
End If
If VarType(value) = vbString Then
If Len(value) = 0 Then
ReadScore = "EMPTY"
Else
ReadScore = "TYPE"
End If
Exit Function
End If
Select Case VarType(value)
Case vbByte, vbInteger, vbLong, vbSingle, vbDouble, vbCurrency, vbDecimal
Case Else
ReadScore = "TYPE"
Exit Function
End Select
score = CDbl(value)
If score < 0 Or score > 100 Then
ReadScore = "RANGE"
Else
ReadScore = "OK"
End If
End Function
Private Function Grade5(ByVal score As Double) As String
Select Case score
Case Is >= 90: Grade5 = "A"
Case Is >= 80: Grade5 = "B"
Case Is >= 70: Grade5 = "C"
Case Is >= 60: Grade5 = "D"
Case Else: Grade5 = "F"
End Select
End Function
Private Function Grade7(ByVal score As Double) As String
Select Case score
Case Is >= 95: Grade7 = "S"
Case Is >= 90: Grade7 = "A"
Case Is >= 80: Grade7 = "B"
Case Is >= 70: Grade7 = "C"
Case Is >= 60: Grade7 = "D"
Case Is >= 50: Grade7 = "E"
Case Else: Grade7 = "F"
End Select
End Function
Private Function LabelGrade(ByVal value As Variant, ByRef reason As String) As String
Dim label As String
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
reason = "TYPE"
Exit Function
End If
label = Trim$(UCase$(value))
reason = "OK"
Select Case label
Case "VIP", "GOLD": LabelGrade = "A"
Case "SILVER": LabelGrade = "B"
Case "BRONZE", "BASIC": LabelGrade = "C"
Case "": reason = "EMPTY"
Case Else: reason = "UNKNOWN"
End Select
End Function
Private Sub ProcessCases(ByVal sheetName As String, ByVal reportRow As Long, ByVal numericMode As Boolean)
Const LAST_ALLOWED As Long = 1001
Dim ws As Worksheet, check As Worksheet
Dim headers As Variant, data As Variant, results() As Variant
Dim lastRow As Long, candidate As Long, colNo As Long
Dim rowCount As Long, outCols As Long, i As Long, validCount As Long
Dim score As Double, reason As String, grade 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 + 621, , "Practice Check sheet must be writable."
End If
checkReady = True
Application.ScreenUpdating = False
Application.EnableEvents = 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 + 622, , "Practice input sheet must be writable."
End If
If numericMode Then
headers = Array("id", "score", "grade5", "grade7", "reason", "expected5", "expected7", "expected_reason", "match")
outCols = 3
Else
headers = Array("id", "label", "grade", "reason", "expected_grade", "expected_reason", "match")
outCols = 2
End If
For colNo = 1 To UBound(headers) + 1
If IsError(ws.Cells(1, colNo).Value2) Then
Err.Raise vbObjectError + 623, , "Invalid practice header."
End If
If CStr(ws.Cells(1, colNo).Value2) <> headers(colNo - 1) Then
Err.Raise vbObjectError + 624, , "Keep the practice headers unchanged."
End If
Next colNo
outputReady = True
ws.Range("C2").Resize(LAST_ALLOWED - 1, outCols).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 + 625, , "Practice 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 results(1 To rowCount, 1 To outCols)
For i = 1 To rowCount
For colNo = 1 To outCols
results(i, colNo) = ""
Next colNo
reason = "NAME"
If Not IsError(data(i, 1)) Then
If VarType(data(i, 1)) = vbString Then
If Len(Trim$(data(i, 1))) > 0 Then
If numericMode Then
reason = ReadScore(data(i, 2), score)
If reason = "OK" Then
results(i, 1) = Grade5(score)
results(i, 2) = Grade7(score)
End If
Else
grade = LabelGrade(data(i, 2), reason)
results(i, 1) = grade
End If
End If
End If
End If
results(i, outCols) = reason
If reason = "OK" Then validCount = validCount + 1
Next i
ws.Range("C2").Resize(rowCount, outCols).Value2 = results
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").Resize(LAST_ALLOWED - 1, outCols).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 "Classification failed: " & problem & vbCrLf & _
"Do not reuse old output or an old timestamp.", vbExclamation
End Sub
오류를 등급으로 숨기지 않기
VarType은 변수의 하위 형식을 나타내는 정수를 반환합니다. 점수 검사에서는 Boolean도 숫자로 받지 않으며, 문자 숫자를 CDbl로 곧장 바꾸지 않습니다. 유효한 점수만 Grade5·Grade7에 전달하므로 Case Else의 F는 '검사에서 누락된 값 전부'가 아니라 유효한 낮은 점수입니다. TextMap은 ASCII 라벨의 앞뒤 공백을 제거하고 대문자로 맞춘 뒤 정해진 다섯 라벨만 분류합니다. 모든 언어의 공백·대소문자 정규화를 다루는 코드는 아닙니다.
Scores!B22와 TextMap!B12의 =1/0은 CELL_ERROR를 확인하는 의도적 입력입니다. 지워서 조용히 통과시키지 마세요. 배포 파일의 실제 출력은 비어 있고 Check 상태는 NOT RUN입니다. 실행 후 DONE이어도 거부 행이 0이라는 뜻은 아닙니다.
헤더를 바꾸거나 1001행 아래에 입력하면 전체 ERROR로 처리합니다. 틀린 헤더와 입력을 원복하고 새 사본에서 재실행합니다. 예약 출력은 Scores C:E, TextMap C:D의 2~1001행이며 재실행 때 지워집니다. 그 칸에 개인 메모를 넣지 않습니다. 보호된 시트에서는 오류 표시를 쓰는 것 자체가 실패할 수 있으므로 이전 시각이나 등급을 재사용하지 마세요.
검산 결과의 한계
Python 참조 테스트는 23개 점수·12개 라벨의 기대값과 분포, 89.9999의 잘못된 닫힌 구간 반례를 독립 계산합니다. 문자 숫자·Boolean·셀 오류가 XLSX에서 구별되는지도 확인합니다. Excel에서 VBA를 컴파일하거나 실행하지 않았습니다. 실제 Excel에서 가져오기, 컴파일, 쓰기 범위와 Check 갱신을 확인해야 하며 Python의 통과 기록을 VBA 실행 결과로 읽으면 안 됩니다.
입력과 결과 칸을 찾는 기존 그림
그림은 실습 위치를 찾는 보조 자료입니다. 실제 실행 결과는 위 절차로 만든 사본의 값과 상태를 대조합니다.



공식 자료
VarType function (Visual Basic for Applications)
Save a macro | Microsoft Support
출처 원문 수집일: S1 2026-09-24; S2 2026-09-24; S3 2026-09-24.
이어서 연습할 글
같은 등급 규칙을 많은 행에 적용할 때 마지막 행과 처리 개수까지 확인하는 방법은 For 루프로 1,000행 분류하기에서 이어집니다.