엑셀 & VBA/엑셀 VBA 코딩패턴

VBA 코딩 패턴: Range Loop-읽기(Read)

ProDA 2021. 5. 30.

이 글은 새로운 블로그로 옮겼습니다. 5초후 자동으로 이동합니다.

▶ 새로운 블로그 주소: https://prodskill.com/

▶ 새로운 글 주소: https://prodskill.com/excel-vba-coding-pattern-range-loop-read/

목차

     

    요약

    Range Loop는 엑셀 시트에 입력되어 있는 데이터를 VBA 코드로 읽어서 처리할 때 자주 사용하는 코딩 패턴이다. 시작 cell부터 마지막 cell까지 순차적으로 읽는 기본적인 패턴과, 순차적으로 읽지 않고 한번에 읽는 성능 개선 패턴을 아래에 소개하고 성능을 비교한다.

     

    Test data, Test VBA Code

    설명을 위해 예시 데이터를 10만개 행, 10개 열, 총 100만개 cell로 생성하고, 각 cell의 값은 (행, 열)의 좌표값으로 설정하였다.

    아래 github 파일 Link에서 예시 데이터, "B2" cell을 시작점으로 10행(100 cell) 부터 10만행(100만 cell)까지 Range를 탐색하는 코딩 패턴과 실행결과를 확인할 수 있다.

    파일 Link: github DAToolset/VBACode/VBA 코딩 패턴-Range Loop(Read).xlsm

    표1. Test Data
    표1. Test Data

     

    기본 패턴-Nested Loop

    기본 패턴은 시작 Cell을 기준으로 행과 열의 Offset을 증가시키면서 행에 대한 Loop(반복) 구문 내부에 열에 대한 Loop(반복) 구문을 사용한다. 상세 코드는 다음과 같다.

    '기본 패턴
    Public Sub NestedLoop(aTargetRowCnt As Long)
        Dim lRowOffset As Long, lColOffset As Long, oBaseRange As Range, sVal As String
        Set oBaseRange = Range("B2")
        lRowOffset = 0
        For lRowOffset = 0 To aTargetRowCnt '읽는 행의 수(충분히 큰 수로 지정하고 Loop 내부에서 종료 조건 설정)
            If Trim(oBaseRange.Offset(lRowOffset, 0).Value) = "" Then Exit For 'Loop 종료 조건
            For lColOffset = 0 To 9 '읽는 열의 수
                'cell 단위로 읽고 처리하는 코드 예시
                'Debug.Print oBaseRange.Offset(lRowOffset, lColOffset).Value
                sVal = oBaseRange.Offset(lRowOffset, lColOffset).Text
            Next lColOffset
        Next lRowOffset
    End Sub

    내부 For Loop의 다음 코드는 Cell 값을 읽는 가장 간단한 코드를 예시로 작성하였다.

    sVal = oBaseRange.Offset(lRowOffset, lColOffset).Text

     

    단일 Loop를 사용하는 패턴-Single Loop(1), Text  사용

    열에 대한 내부 반복문을 열 개수만큼의 문장으로 대체하는 구문으로 상세 코드는 다음과 같다. Range의 값을 꺼낼 때 Text 를 사용하였다.

    '단일 Loop를 사용하는 패턴: Single Loop(1), Text  사용
    Public Sub SingleLoop1(aTargetRowCnt As Long)
        Dim lRowOffset As Long, lColOffset As Long, oBaseRange As Range, sVal As String
        Set oBaseRange = Range("B2")
        lRowOffset = 0
        Dim lRowCount As Long, lColCount As Long, oHeaderRange As Range
        Set oHeaderRange = oBaseRange.Offset(-1, 0)
        'lRowCount = Range(oBaseRange, oBaseRange.End(xlDown)).Rows.Count
        lRowCount = aTargetRowCnt
        lColCount = Range(oHeaderRange, oHeaderRange.End(xlToRight)).Columns.Count
        For lRowOffset = 0 To lRowCount - 1 '읽는 행의 수 (Offset은 0부터 시작하므로 Count -1 까지 Loop 실행해야 함)
            sVal = oBaseRange.Offset(lRowOffset, 0).Text
            sVal = oBaseRange.Offset(lRowOffset, 1).Text
            sVal = oBaseRange.Offset(lRowOffset, 2).Text
            sVal = oBaseRange.Offset(lRowOffset, 3).Text
            sVal = oBaseRange.Offset(lRowOffset, 4).Text
            sVal = oBaseRange.Offset(lRowOffset, 5).Text
            sVal = oBaseRange.Offset(lRowOffset, 6).Text
            sVal = oBaseRange.Offset(lRowOffset, 7).Text
            sVal = oBaseRange.Offset(lRowOffset, 8).Text
            sVal = oBaseRange.Offset(lRowOffset, 9).Text
        Next lRowOffset
    End Sub

     

    단일 Loop를 사용하는 패턴-Single Loop(2), Text 대신 Value2 사용

    Single Loop 구문을 유지하고 Range의 값을 꺼낼 때 Text 대신 Value2를 사용하였다. 성능 비교는 가장 아래쪽에 작성해 두었다.

    ' 행만 Loop, Text 대신 Value2 사용
    Public Sub SingleLoop(aTargetRowCnt As Long)
        Dim lRowOffset As Long, lColOffset As Long, oBaseRange As Range, sVal As String
        Set oBaseRange = Range("B2")
        lRowOffset = 0
        Dim lRowCount As Long, lColCount As Long, oHeaderRange As Range
        Set oHeaderRange = oBaseRange.Offset(-1, 0)
        'lRowCount = Range(oBaseRange, oBaseRange.End(xlDown)).Rows.Count
        lRowCount = aTargetRowCnt
        lColCount = Range(oHeaderRange, oHeaderRange.End(xlToRight)).Columns.Count
        For lRowOffset = 0 To lRowCount - 1 '읽는 행의 수 (Offset은 0부터 시작하므로 Count -1 까지 Loop 실행해야 함)
            sVal = oBaseRange.Offset(lRowOffset, 0).Value2
            sVal = oBaseRange.Offset(lRowOffset, 1).Value2
            sVal = oBaseRange.Offset(lRowOffset, 2).Value2
            sVal = oBaseRange.Offset(lRowOffset, 3).Value2
            sVal = oBaseRange.Offset(lRowOffset, 4).Value2
            sVal = oBaseRange.Offset(lRowOffset, 5).Value2
            sVal = oBaseRange.Offset(lRowOffset, 6).Value2
            sVal = oBaseRange.Offset(lRowOffset, 7).Value2
            sVal = oBaseRange.Offset(lRowOffset, 8).Value2
            sVal = oBaseRange.Offset(lRowOffset, 9).Value2
        Next lRowOffset
    End Sub

     

    데이터를 한번에 memory로 읽는 방법-Variant Array 사용

    데이터를 읽을 범위를 정하고 2차원 Variant Array로 한번에 읽는 방법으로 상세 코드는 다음과 같다.

    ' 한번에 읽는 패턴
    Public Sub VariantArray(aTargetRowCnt As Long)
        Dim vRngArr As Variant, oBaseRange As Range, oHeaderRange As Range, lColCount As Long, sVal As String
        Set oBaseRange = Range("B2")
        Set oHeaderRange = oBaseRange.Offset(-1, 0)
        lColCount = Range(oHeaderRange, oHeaderRange.End(xlToRight)).Columns.Count
        vRngArr = oBaseRange.Resize(aTargetRowCnt, lColCount).Value2
        Dim lCol As Long, lRow As Long
        For lRow = LBound(vRngArr, 1) To UBound(vRngArr, 1)
            For lCol = LBound(vRngArr, 2) To UBound(vRngArr, 2)
                'Debug.Print vRngArr(lRow, lCol)
                sVal = vRngArr(lRow, lCol)
            Next lCol
        Next lRow
    End Sub

    여기에서 핵심은 다음 코드이다.

    vRngArr = oBaseRange.Resize(aTargetRowCnt, lColCount).Value2

    Resize는 시작 범위(Range)로부터 지정한 행, 열의 수만큼 범위를 확장하여 값을 읽을 대상을 설정한다. Value2는 Range의 값을 형식없이 원래의 값으로 꺼내는 속성이다. 값을 꺼낼 Range에 여러 행과 열을 포함하고 있다면 그 값은 2차원 Variant Array로 꺼내진다. 그래서 위 코드는 읽을 범위 전체를 한번에 memory로 읽게 된다.

     

    성능비교

    10부터 10만까지 Row Count를 증가시키면서 각 방법의 실행시간을 정리하면 다음과 같다.
    (실행환경: 2019 LG Gram, CPU i5, Ram 16GB, 256GB SSD)

    표2. 입력 Count와 소요시간(초)
    표2. 입력 Count와 소요시간(초)

     

    그림1. 입력 Count와 소요시간(초)
    그림1. 입력 Count와 소요시간(초)

    실행 성능은 다음과 같이 요약할 수 있다.

    • Nested Loop, Single Loop 반복 구문 자체의 실행 성능은 거의 차이가 없다.
    • Range에서 값을 꺼낼 때 Text를 사용하면 느리고, Value2를 사용하면 빠르다.
    • Variant Array를 이용하면 데이터가 많아도 매우 빠르게 읽을수 있다.

    참고로 Range에서 값을 꺼낼때 사용하는 Text, Value, Value2 속성에 대한 비교는 나중에 시간이 되면 정리하겠다. 일단은 google에서 검색하여 찾은 stackoverflow.com의 글을 링크해 둔다.
    https://stackoverflow.com/questions/17359835/what-is-the-difference-between-text-value-and-value2

     

    What is the difference between .text, .value, and .value2?

    What is the difference between .text, .value, and .value2? Such as when should target.text, target.value, and target.value2 be used?

    stackoverflow.com

    댓글

    💲 추천 글