数式の取得(高速版)

'--------------------------------------------------
'プロシージャの概要:
'処理名: exec全数式取込
'概要: 対象ブックの全シートから数式を取得してAccessテーブルに格納
'--------------------------------------------------
Public Sub exec全数式取込()
    Dim strTargetPath As String
    Dim wb As Workbook
    Dim ws As Worksheet
    Dim varFormulas As Variant
    Dim varValues As Variant
    Dim lngRow As Long
    Dim lngCol As Long
    Dim db As DAO.Database
    Dim rsFormulas As DAO.Recordset
    Dim lngRecordCount As Long
    Dim strSQL As String
    
    'ファイル選択ダイアログ
    With Application.FileDialog(msoFileDialogFilePicker)
        .Title = "解析対象Excelファイルを選択"
        .Filters.Clear
        .Filters.Add "Excelファイル", "*.xls;*.xlsx;*.xlsm"
        
        If .Show <> -1 Then
            Exit Sub
        End If
        strTargetPath = .SelectedItems(1)
    End With
    
    'Excelの設定を最適化
    With Application
        .ScreenUpdating = False
        .EnableEvents = False
        .Calculation = xlCalculationManual
    End With
    
    'データベース準備とテーブル初期化
    Set db = CurrentDb
    strSQL = "DELETE FROM t数式テーブル"
    db.Execute strSQL, dbFailOnError
    
    'レコードセット準備
    Set rsFormulas = db.OpenRecordset("t数式テーブル", dbOpenDynaset)
    
    '対象ブックを開く
    Set wb = Workbooks.Open(strTargetPath, ReadOnly:=True)
    
    'トランザクション開始(高速化)
    DBEngine.BeginTrans
    
    'シート数カウント用変数
    Dim lngSheetCount As Long
    Dim lngCurrentSheet As Long
    Dim dblProgress As Double
    
    '総シート数を取得
    lngSheetCount = wb.Worksheets.Count
    lngCurrentSheet = 0
    
    '全シートをループ
    For Each ws In wb.Worksheets
        'シートカウンタを増やす
        lngCurrentSheet = lngCurrentSheet + 1
        
        '進捗率計算
        dblProgress = (lngCurrentSheet / lngSheetCount) * 100
        
        'ステータスバー更新
        SysCmd acSysCmdSetStatus, "数式取得中・・・" & ws.Name & " " & _
                                  lngCurrentSheet & "/" & lngSheetCount & _
                                  " (" & Format(dblProgress, "0") & "%)"
        DoEvents  '強制描画
        
        With ws.UsedRange
            '空のシートはスキップ
            If .Cells.Count = 1 And .Cells(1, 1).Formula = "" Then
                GoTo NextSheet
            End If
            
            '========================================
            '配列による高速取得処理(ここが最重要)
            '========================================
            '通常、セル単位でFormulaを取得すると遅いが
            '範囲全体のFormulaを配列に一括取得すると激速
            varFormulas = .Formula  '全セルの数式を2次元配列に一括取得
            varValues = .Value      '全セルの値も2次元配列に一括取得(比較用)
            
            '単一セルの場合の処理
            '(単一セルの場合、配列ではなく単一値が返るため配列化)
            If Not IsArray(varFormulas) Then
                ReDim varFormulas(1 To 1, 1 To 1)
                ReDim varValues(1 To 1, 1 To 1)
                varFormulas(1, 1) = .Formula
                varValues(1, 1) = .Value
            End If
            
            '========================================
            '配列内をループして数式を判定・抽出
            '========================================
            'この時点でExcelとの通信は終了しており
            'メモリ上の配列を処理するため高速
            For lngRow = 1 To UBound(varFormulas, 1)    '行方向のループ
                For lngCol = 1 To UBound(varFormulas, 2) '列方向のループ
                    
                    '数式判定:"="で始まる文字列は数式
                    '(varFormulas配列には数式なら"=SUM(A1:A10)"のような文字列、
                    '  値なら"100"のような値が格納されている)
                    If Left$(varFormulas(lngRow, lngCol), 1) = "=" Then
                        'Accessテーブルにレコード追加
                        rsFormulas.AddNew
                        rsFormulas!シート名 = ws.Name
                        '配列のインデックスから実際のセル番地を算出
                        rsFormulas!セル番地 = .Cells(lngRow, lngCol).Address(False, False)
                        rsFormulas!数式 = varFormulas(lngRow, lngCol)
                        rsFormulas.Update
                        
                        lngRecordCount = lngRecordCount + 1
                    End If
                Next lngCol
            Next lngRow
        End With
NextSheet:
    Next ws
    
    'トランザクションコミット
    DBEngine.CommitTrans
    
    'クリーンアップ
    rsFormulas.Close
    Set rsFormulas = Nothing
    wb.Close SaveChanges:=False
    Set wb = Nothing
    Set db = Nothing
    
    'Excelの設定を戻す
    With Application
        .ScreenUpdating = True
        .EnableEvents = True
        .Calculation = xlCalculationAutomatic
    End With
    
    '処理完了メッセージ
    MsgBox "数式取込完了" & vbCrLf & _
           "取込件数: " & Format(lngRecordCount, "#,##0") & " 件", _
           vbInformation
    
    'ステータスバーをクリア
    SysCmd acSysCmdClearStatus
    
End Sub