マクロの入り口

1.マクロでできる事

Excelの画面から操作することはほとんどマクロで実行可能です。

画面から操作した方が簡単な場合も有りますが、データ量が多い場合、扱うシートが多い場合、定期的に同じような作業をする場合、作業時間の短縮化、正確性の向上に寄与できると思います。

このサイトではデータの集計や報告書の作成を仕事にしている方が実際に使えるモジュール(サブプロシージャ)を紹介していきます。

 


2.マクロの登録準備

(1) マクロの登録準備

マクロを登録したり呼び出したりするのには、マクロのリボンが表示状態にしておく必要があります。

バージョンによって異なるかもしれませんが『ホーム』リボンを右クリックして、『リボンのユーザー設定』で開発ツールにチェックを入れて下さい

3.マクロの登録

マクロの登録

 マクロの登録方法は2種類あります

 (1)マクロの記録

マクロの記録ボタンを押してから、マクロ名を入力し、シートに対する操作(列の削除とか、ソートなど)をします。終わったならば『記録終了』を押します。定型的な作業には使えますが、条件にあったデータのみを処理する場合などには使いにくい思います。

 (2)モジュールの登録

  • 開発リボンの『Visual Basic』を左クリックし、下記画面が出てきたところでThis Workbook を右クリック、表示された項目を 挿入⇒標準モジュールの順に選択すると『Module1』が登録されます。
  • これがモジュール(マクロのプログラム)が登録される箱(入れ物)となります
  • 次回からは開発リボンの『Visual Basic』を左クリックした時に『Module1』が表示されるのでこれを左クリックすれば『モジュール』が入力可能な状態になります。



これで登録の準備はできました。

次にマクロを使う上で最低限必要な用語を説明します。

 

4.最小限必要な用語と考え方

 

(1)オブジェクト

  • セルまたは特定の範囲のセル、行、列、シート、ブックなどを示すことばです。
  • VBAでは 算術式、関数、メソッドなどデータを操作する(書き換える)手段があり、その対象となるのがオブジェクトです。
  • 文章で言うと ~を XX する。と書く場合、~を に相当するのがオブジェクトです。
  • オブジェクトは修飾できます。というより修飾できなければVBAの運用はかなり制約を受けてしまいます。
  • 不便な事にVBAの命令(メソッド)は1行ずつしか実行できない場合が多々あります。各行に対して1行目はこうしなさい、2行目はこうしなさい、3行目はこうしなさいとデータの行数分だけプログラミングをするのは無理があります。そこで 『n 行目はこうしなさい』という文を1行だけ記述して、あとから n に2,3,4・・・と順次数字(主に行数)を代入して実行していくのが一般的なのです。このあと具体例をお見せします。

(2)Dim ステーメント(変数の宣言)

  • VBAの中ではセルに含まれている数字・文字以外の項目を使います。その項目(変数)のタイプを宣言するのがDimステートメントです。変数を使う目的は上記のようにオブジェクトの修飾の他に、長い文字列を短く表現する、モジュールの記述を定型化して使い回しをするなどがあります。
  • 一般的には次の3つを覚えておけばモジュールの作成は可能です
    Dim i      as long (数字-小数なし)
    Dim range1 as string (文字)
    Dim su1    as double(数字-小数あり)

(3)代入式(右辺左辺同一変数の考え方)

  • Excelの関数で C2 = A2 + B2 はありますが、 C2 = C2 + 1 はありません 循環関数の警告が発生するからです。警告を無視したとしても、何の計算もしてくれません。
  • しかしVBAでは命令は一度に1回しか実行しないので、循環関数の警告は出ません。もしC2に最初3という値だった場合C2=C2+1を実行後 C2には4の値が入ります。この『=』 は気持ちの上では『⇐』と思ってみたほうがよいかもしれません。
  • i =i + 1という形式はVBAでは繰り返し処理をする場合は(見えない場合もありますが)必ず使われる式です。

概念的な説明だけでは実感して頂けないと思いますので、実際のマクロを作成して実施前と実施後を比較していきます。

マクロはシート単位でおこなうものと、行やセル単位で行うものがあります。

後者の方が応用範囲が広く複雑な処理もできるのですが、少し難しいです。ここではシート単位でのマクロの使用例を見ていきましょう。これから下の表をマクロを使って加工していきます。元の表は何れも下記の表となります。

(全て実在していないデータです)

5.マクロの使用例(シート)

 

(1)クリアー

  • Sub test01()
    Range("A2:G15").Clear

    End Sub

  • あるいは
    Sub test02()
    Range("A1").CurrentRegion.Offset(1, 0).Clear
    End Sub
  • どちらも実行結果は同じで2行目以下がクリアーされるのですが、上ののモジュールではデータの範囲を把握して、範囲を設定してコードを作成する必要性が発生します。また下のモジュールでは空白行があると、そこから下はクリアーする事が出来ません。この文は 先頭1行を残して(Offset(1, 0)、表の最後までクリアーしなさいという文なのですが、空白行があるとそこを『表の最後』と見なされてしまいます。回避する方法は最終行を見つけてそこまでを範囲指定して消去することです。

クリアーの重要性について

  • VBA では操作を元に戻すボタンが使用できません。ですから元データを消さないように、違うエリアにコピーしてから実行します。コピー先に別のデータが残っていないように事前にクリアーしておく事が必要になります。

(2)ソート

  • 下記モジュールは簡単にソートを実行できるモジュールです
  • Sub test03()
        Range("A1").Sort Key1:=Range("F1"), Order1:=xlAscending, Header:=xlYes
    End Sub
  • 説明 A1からシートの最後(空白行)までをソートする。ソート対象はF列。昇順ソート。ヘッダー(A1を含む行)はソートの対象としない。もし空白行を含んだシートなら

 Range("A1") ⇒ Range("A1:G15") とします

  • もしヘッダーが2行目ならRange("A2")とします
  • 実行結果

(3)列の削除

  • D列を切り取ってF列の後ろに挿入するモジュールを紹介します。
  • Sub test04() '
        Columns("D:D").Delete 
    End Sub
  • 実行結果

(4)列の切り取りと挿入

  • D列を削除するモジュールを紹介します
  •  Sub test05()

     Columns("D:D").Cut

         Columns("G:G").Insert Shift:=xlToRight
    End Sub
  • 実行結果  挿入先はD列を切り取る前の位置を指定します

(5)シート内容を全てコピーする

  • 同じファイル(Book)にある『Sheet3』の全てのセルを『Sheet4』にコピーするモジュールです。直接Sheets("Sheet3").Copyとすると、新たなBookを作成してそこにコピーしてしまうため、『Cells.Copy』を使います。最後にクリップボードをクリアします。(メモリの使用効率を上げるため)小さい表ならあえて記述する必要はありません。
  • Sub test06()
        Sheets("Sheet3").Select
        Cells.Copy
        Sheets("Sheet4").Select
        Cells.Select
        ActiveSheet.Paste
          Application.CutCopyMode = False  'クリップボードのクリア
    End Sub
  • 実行結果  実行結果は省略します。

6.マクロの使用例(行及びセル単位の処理)

 

ここまでの処理は画面から操作しても可能な処理でした。

また、マクロの記録でも簡単にモジュールを作成することができます。

ここから先が本格的なマクロ処理となります。

この処理をするためには『For・・・ Next』ステートメントまたは『Do・・・ Loop』ステートメントを使います。

(1)行ごとに判断結果をシートに書き込む

 

IF文は関数でも簡単にできるのですが、マクロを使うと複雑な分岐をするIF文を比較的簡単に作ることができます。

  • 年齢が20歳未満の時にF列に1を書き込みそれ以外では0を書き込む。
  • Sub test07()
      Dim MaxRow As Long
      Dim i      As Long
         Sheets("Sheet4").Select         '処理対象を Sheet4 とする
        MaxRow = Cells(Rows.Count, 1).End(xlUp).Row  '1列目(A列)を参照し                                                                                               'て最大  行を求める
        For i = 2 To MaxRow                  '2からMaxRow(最大行)まで繰り返す
        If Cells(i, "F") < 20 Then       'F列i行目の値が20未満かどうか判断し
           Cells(i, "H") = 1       '20未満ならH列 I 行目に 1 をセットする
           Else
           Cells(i, "H") = 0      'そうでなければH列 I 行目に 0 をセットする
           End If
        Next           'Iに 1を加算して "For" の位置まで戻る
    End Sub
  • 計算結果

解説

For で始まりNext で終わる部分

  • iが2から15になるまで 14回繰り返し実施されます。それに合わせてデータを1行ずつ判断し、書き込みがされます。

If で始まり End If で終わる部分

  •   If 条件式 Then
              条件が成立した場合の処理
       Else
              条件が成立しなかった場合の処理
      End If

他に罫線やタイトルなど追加したい部分があります。またどちらの文もバリエーションが多くあります。しかしこの『For・・・Next』や『If ・・・End if』が自在に使えるようになることはプログラミング上非常に重要なことです。他の言語でも類似表現はよく使われます。

(2)行を削除する及び高速化

  • ここでは行削除、逆順のNext、そして高速化の例を紹介します。
  • 行の削除をすると削除前と行の内容が変わってしまいます。3行目を削除すると先ほどまで4行目だった行が3行目になります。For文では行数を示す I ずつカウントアップされるので、1行分処理が漏れてしまいます。これを防ぐために15行目から i を1ずつ減らす方法を使います。メインの表現は下記の通りになります。Step - 1 と EntireRow.Deleteがポイントになります。この削除命令の前のオブジェクトはそのセルが属するどのセルでもかまいません。(下記"C"は"A"でも"1"でもかまわない)
  •    For i = MaxRow To 2 Step -1
       If Cells(i, "C") = "男" Then
         Cells(i, "C").EntireRow.Delete
       End If
       Next

削除文を実行すると処理速度が遅くなります。削除した瞬間に表示される表を1行減らしたものに再表示するからです。この画面表示をやめさせる制御と、シート上の関数の計算を一時的に停止する制御文をセットで使う習慣を身につけると何かと便利です。

    Application.ScreenUpdating = False ’画面表示の抑止
    Application.Calculation = xlCalculationManual ’計算の抑止


これはプログラムが終了する前に元に戻しておく必要があります

     Application.ScreenUpdating = True ’画面表示の再開
     Application.Calculation = xlCalculationAutomatic ’計算の実施

 

通しで実行すると下記のようになります

 

 

Public Sub test07()
   Dim MaxRow As Long
  Dim i As Long
    Application.ScreenUpdating = False
    Application.Calculation = xlCalculationManual
   MaxRow = Cells(Rows.Count, 1).End(xlUp).Row
   For i = MaxRow To 2 Step -1
   If Cells(i, "C") = "男" Then
     Cells(i, "C").EntireRow.Delete
   End If
   Next
     Application.ScreenUpdating = True
     Application.Calculation = xlCalculationAutomatic
  • End Sub
実行結果

 

ではこの高速処理をした場合としなかった場合の時間差はどのくらいかというと1000件のデータを処理した場合

高速化しなかった場合   13.84秒

高速化した場合        0.234秒

 

 

と圧倒低な差がつきました。ちなみにF列には =DATEDIF(E17,TODAY(),"Y")という生年月日から年齢を算出する関数が入っています。

 

(3)別なシートへのデータ追加(同一Book)

 

日次データを月次データに追加する、複数名で入力したデータを一つにまとめる時の方法です。

少し長くなるので、先に手順をリストアップします(あくまで一例です)

①追加される側のシートを選択する。

②データの行数をカウントする

③行数 + 1の値を変数に記録する。

④追加するシートを選択する。

⑤データの行数をカウントする。

⑥コピー範囲を定める

⑦コピー先のシートに貼り付ける

上記手順に従ってコードを作成します。事例ではSheet7のデータ部分(2行目以降)に追加します。追加する側、される側も10列とします。

 

Public Sub test08()
  Dim MaxRow As Long
  Dim i As Long
  Dim Max1 As Long
  Dim RNG As String
  
  Sheets("Sheet4").Select    '追加される側
    MaxRow = Cells(Rows.Count, 1).End(xlUp).Row   '最大行の把握
    Max1 = MaxRow + 1          '追加先の行位置を決める
 Sheets("Sheet7").Select      '追加する側
    MaxRow = Cells(Rows.Count, 1).End(xlUp).Row   '最大行の把握
    RNG = "A2:J" & MaxRow      '追加する範囲を決める
    Range(RNG).Copy
     Sheets("Sheet4").Range("A" & Max1).PasteSpecial Paste:=xlAll
   Application.CutCopyMode = False
End Sub

 

  • 解説
  • RNG = "A2:J" & MaxRow  追加するシートの範囲を示します。タイトルを含め5行のシートだとするとMaxRow の値は 5、実行時の式は"A2:J5"となります(範囲の対角線右上・左下のセル)。
  • Sheets("Sheet4").Range("A" & Max1).PasteSpecial Paste:=xlAll Sheet4の行数は15、Max1は MaxRow +1なので値は 11、実行時はRange("A11")となります。
  • 複数のシートを扱う時は本来オブジェクトにはシート名を含めるのが正しい書き方ですが、普通は直前でselectされたシート名が暗黙のうちに選択されています。
  • Range(RNG).Copy
         Sheets("Sheet4").Range("A" & Max1).PasteSpecial Paste:=xlAll
    についてコピー元と先のシート関係が非対称的に見えるのは、実は下記の赤字部分が省略されていたためです。
  • Sheets("Sheet7").Range(RNG).Copy
         Sheets("Sheet4").Range("A" & Max1).PasteSpecial Paste:=xlAll

7.マクロの使用例(シート間の処理)

 

(1)別なシートへのデータ追加(異なる一Book)

 

前述のSheet7をSheet4に追加する処理ですが、Sheet4が異なるBookにある場合の処理方法です。

Bookが異なるとすべきことが急に増えます。

作業は下記の通りです。

①(追加されるシート)selectする

②現在のBook名を記録する

③データの行数をカウントする

④行数 + 1の値を変数に記録する。

⑤別のシート(『動作環境』から)追加する(インポートする)Bookの所在と名称を参照する

⑥上記Bookが存在するか調べる(存在しなかった場合はエラーメッセージを出して終了する。

⑦Bookを開けなかった場合の処理を作成する

⑧追加すべきデータの存在するBookを開き、Book名を記録する(xlBookbB)

⑨(xlBookbB)のSheet7をselectする。

⑩追加するデータの行数を求め、コピーする範囲を決める。

コピー先のシートに貼り付ける

⑪(xlBookbB)をクローズする

  この時確認メッセージを抑止するには

  クローズ文の前後に Application.DisplayAlerts = False、 ' Application.DisplayAlerts = Trueを記述する。

 

今回は①のBook名を現在のBookの"動作環境"というシートの"C2"のセルに記述するという形を取ります。モジュールの中にBook名を記入することも可能ですが、対象のBookが変わるたびにモジュールを書き換えるのは不便だなので、シート上に記述することにします。

前回と同じように手順を書いてみましょう。

 

 

上記手順に従ってコードを作成します。事例ではSheet7のデータ部分(2行目以降)に追加します。追加する側、される側も10列とします。

 

Sub test09()
 
Dim i   As Long
Dim MaxRow As Long
Dim FNAME1 As String
Dim xlBookA As Workbook
Dim xlBookB As Workbook
 Dim Max1 As Long
  Dim RNG As String
  
'Sheet4シートの範囲確認
Sheets("Sheet4").Select
 
    Set xlBookA = ThisWorkbook
 
'    最終行の取得
   MaxRow = Cells(Rows.Count, 1).End(xlUp).Row
'   貼付先の行数を算出する
  Max1 = MaxRow + 1
 ' インポートファイルの情報取得
 Sheets("動作環境").Select       '開くファイル名が書かれているシートを開く

FNAME1 = Range("C2")                                    '開くファイル名を取得する

        'C2には具体的に『D:¥2020¥FILE_B.xlsx』などの名前が入ります

    If Dir(FNAME1) <> "" Then                                     'ファイルが存在するか確認する
          Set xlBookB = Workbooks.Open(FNAME1)    'ファイルを開いてxlBookBとする
   Else
          MsgBox FNAME1 & "   ファイルが存在しません。プログラムを終了します"
          Exit Sub
 End If
   '  インポート処理
  Sheets("Sheet7").Select
  MaxRow = Cells(Rows.Count, 2).End(xlUp).Row
    RNG = "A2:J" & MaxRow
   Range(RNG).Copy
    xlBookA.Worksheets("Sheet4").Range("A" & Max1).PasteSpecial Paste:=xlAll
      Application.DisplayAlerts = False
    xlBookB.Close
   xlBookA.Worksheets("Sheet4").Activate
   Application.DisplayAlerts = True
End Sub
  • 解説
  • RNG = "A2:J" & MaxRow  追加するシートの範囲を示します。タイトルを含め5行のシートだとするとMaxRow の値は 5、実行時の式は"A2:J5"となります。
  • Sheets("Sheet4").Range("A" & Max1).PasteSpecial Paste:=xlAll Sheet4の行数は15、Max1は MaxRow +1なので値は 11、実行時はRange("A11")となります。
  • 複数のシートを扱う時は本来オブジェクトにはシート名を含めるのが正しい書き方ですが、普通は直前でselectされたシート名が暗黙のうちに選択されています。
  • Range(RNG).Copy
         Sheets("Sheet4").Range("A" & Max1).PasteSpecial Paste:=xlAll
    についてコピー元と先のシート関係が非対称的に見えるのは、実は下記の赤字部分が省略されていたためです。
  • Sheets("Sheet7").Range(RNG).Copy
         Sheets("Sheet4").Range("A" & Max1).PasteSpecial Paste:=xlAll

8.関数の設定(表計算式を組み込む)

 

関数をシートに組み込むと処理が遅くなるのであまり好ましいことではありません。しかしながら関数を使った方が便利な場合があります。それは下記のようなケースです。

1.VBAの関数に相当するメソッドが使いにくい

マクロのメソッドは項目(変数)の定義に反する処理にが厳しいのでエラー処理の記述が大変

またそのエラー処理が正しくされているか確認のデバッグに時間が掛かる

文が長くなる

 

2.Sheetの値を確認する時どのような処理をしているか分かりにくい(特にIF文やVlookup文)

 

関数は手作業で追加しても良いのですがデータの行数に合わせてCopyしたり、関数の入力範囲を消さないような配慮も必要になるので『マクロを使ってExcel関数を』データの数に合わせて書き込んだ方が便利かと思います。

 

文例は以下の通りになります

 

Sub test10()
 
Dim i   As Long
Dim MaxRow As Long
  
Sheets("Sheet4").Select
'    最終行の取得
   MaxRow = Cells(Rows.Count, 1).End(xlUp).Row
'関数を入れる行をSelectして相対表記で関数をセットする
 Range("F2").Select                                                                                  '関数を設定する最初の行
    ActiveCell.FormulaR1C1 = "=DATEDIF(RC[-1],TODAY(),""Y"")"  '生年月日と当日で年齢を算出
    Selection.AutoFill Destination:=Range("F2:F" & i), Type:=xlFillDefault   'F2のセルを最終行までコピーする
   Application.CutCopyMode = False                 'クリップボードのクリアー    
End Sub

 

解説  関数は必ず相対表記で記述する必要があります。
VBAでCopyすると関数を行に合わせて書き換えることをしてくれないからです。相対表記がなれない方は一度シート上で関数を打ち込み、その後『マクロの記録』でその関数の"="を削除し、更に"="を挿入すれば、相対表記された関数を入手することができます。