ジャグ配列の中身をすべてを、イミディエイトウィンドウに書き出す。

ジャグ配列とは…

素数の異なる配列のことです。

(例)
配列(2)
 配列(0)(0),(0)(1)
 配列(1)(0),(1)(1),(1)(2)
 配列(2)(0),(2)(1),(2)(2),(2)(3)


配列(0)は2つの要素を持つ。
配列(1)は3つの要素を持つ。
配列(2)は4つの要素を持つ。

このように、要素数の異なる配列のことをジャグ配列と呼びます。

VBAでジャグ配列の作り方

Sub jag配列作成()

Dim jagarr() As Variant
jagarr = Array(Array(1, 2), Array(3, 4, 5, 6), Array(7, 8, 9, 10, 11))

End Sub


下の画像の通りジャグ配列ができる。

f:id:bimori466:20201227014336p:plain

ジャグ配列の中身すべてをイミディエイトウィンドウに書き出す。

Sub jag配列_要素全てを書出し()

Dim jagarr: jagarr = Array(Array(1, 2), Array(3, 4, 5, 6), Array(7, 8, 9, 10, 11))

    jagValue = jagarr(0)(0)
    
    'jag配列の最小値、最大値をイミディエイトウィンドウに書き出す
    Debug.Print "jag配列数 = " & LBound(jagarr)
    Debug.Print "jag配列数 = " & UBound(jagarr)
    
    
    'jag配列の要素をイミディエイトウィンドウに書き出す
    For jaghi = LBound(jagarr) To UBound(jagarr)
        For jaghihi = LBound(jagarr(jaghi)) To UBound(jagarr(jaghi))
            Debug.Print jagarr(jaghi)(jaghihi)
        Next
    Next
End Sub


下の画像の通りジャグ配列の要素をイミディエイトウィンドウに書き出す。

f:id:bimori466:20201227014639p:plain


LBound、UBoundを使って配列のそれぞれの要素数を調べ、それをループ数とすることですべての要素をイミディエイトウィンドウに書き出すことができます。

なお、ジャグ配列の活用方法はわかりません(;^ω^)


備忘録でした(^^)/~~~

複数の改行するスマートなコード

結論

Sub 改行()

'普通の書き方
MsgBox "かず" & vbCrLf & vbCrLf & "やん"


'スマートな書き方
MsgBox "かず" & String(2, vbCrLf) & "やん"

End Sub

処理結果は同じ

f:id:bimori466:20201226234105p:plain


「& vbCrLf & vbCrLf & 」ではなく、「& String(2, vbCrLf) &」と記述することにより、2回改行します。

なぜスマートかというと、5回改行したい場合、「& String(5, vbCrLf) &」と書けば済むからです。


備忘録でした(^^)/~~~

VBA100本ノック 39本目:数値リストの統合(マージ)

この記事から得るもの

Collectionオブジェクトを使って、重複しないデータを作成する。


1 今回のお題

A列とB列の数値を統合(マージ)しユニーク化してC列に出力します。
・A列、B列ともに行数は不定
・列内では数値は昇順になっています。
・列内では重複していないが、A列とB列では重複します。
・C列へは重複しない数値として昇順に出力してください。
※画像を参考に。

f:id:bimori466:20201220205726p:plain

excel-ubara.com

2 今回のお題の意図

同じ列に重複はない。別の列には重複する数字がある。複数の列から重複しない数字を列Cに書き出す。

3 回答

私の最初の回答

Sub ノック39本目_1()

Dim hi() As Variant
Dim myCollection As New Collection
Dim ws As Worksheet: Set ws = Worksheets("sheet1")

'最終行の取得
rowLastLineA = ws.Cells(Rows.Count, 1).End(xlUp).Row
rowLastLineB = ws.Cells(Rows.Count, 2).End(xlUp).Row

ReDim hi(1 To rowLastLineA)

For k = 1 To rowLastLineA
    hi(k) = ws.Cells(k, 1)
Next

ReDim Preserve hi(1 To rowLastLineA + rowLastLineB)

For i = 1 To rowLastLineB
    hi(rowLastLineA + i) = ws.Cells(i, 2)
Next

On Error Resume Next    '重複の代入エラーを無視

For j = 1 To rowLastLineA + rowLastLineB
    myCollection.Add hi(j), CStr(hi(j))
Next

On Error GoTo 0     'エラー無視の解除

'列Cへ転記
For k = 1 To myCollection.Count
    ws.Cells(k, 3) = myCollection(k)
Next

'列Cを並べ替え
Range("C1:C11").Sort Key1:=Range("C1"), Order1:=xlAscending, Header:=xlNo

End Sub


処理手順
1 A、B列の数字を配列に入れる。
2 Collectionに重複しない数字を取得する。
3 列Cに転記する。
4 並べ替える。


ポイントとしてはCollection.Add処理の、Keyは文字列で代入しないと重複してItemを追加(Add)してしまいます。
「myCollection.Add hi(j), CStr(hi(j))」(Add Item,Key)のKeyを、Cstr関数で文字列に変換する必要があります。
これは、パラメータがItemがObjectに対し、KeyはStringのためであると思われます。

4 感想

CollectionはItemが重複するObjectを代入することができないと思っていたのですが、実際はKeyが重複不可でした。
かなり重要な勘違いをしていました(;^ω^)。数字のユニーク化でも、KeyをCstr関数で文字列として扱えば可能となります。


ではでは、この辺で(^^)/~~~

VBA100本ノック 38本目:1シートを複数シートに振り分け

この記事から得るもの

日付データから、祝日、平日、休日の判断ができるようになる。
書込み処理のサブルーチン化のやり方が分かる。


1 今回のお題

「売上」シートのA列に日付が昇順で入っています。
土日祝と平日に分けて別シートに出力してください。
・「売上」シートの列数は不定
・「土日祝」「平日」シートは既存です。
・祝日は「祝日」シートのA列にあります。
※セルの書式の扱いは任意

f:id:bimori466:20201219043111p:plain

f:id:bimori466:20201219043145p:plain

excel-ubara.com

2 今回のお題の意図

Weekday関数を使って曜日を取得し、条件分岐する。
Countif関数を使って祝日の、条件分岐をする。

3 回答

私の最初の回答

Sub ノック38本目_1()

Dim wirteWeekdays_IX As Long: wirteWeekdays_IX = 1  '平日書込み用
Dim writeHolidays_IX As Long: writeHolidays_IX = 1  '休日書込み用

Dim wsEarnings As Worksheet: Set wsEarnings = Worksheets("売上")
Dim wswriteHolidays As Worksheet: Set wswriteHolidays = Worksheets("土日祝")
Dim wswriteWeekdays As Worksheet: Set wswriteWeekdays = Worksheets("平日")
Dim wsPublicHoliday As Worksheet: Set wsPublicHoliday = Worksheets("祝日")

Dim endFlg As Boolean


For i = 2 To wsEarnings.Range("A1").CurrentRegion.Rows.Count
    mydate = wsEarnings.Cells(i, 1): endFlg = False
    
    '祝日の処理
    For j = 1 To wsPublicHoliday.Range("A1").CurrentRegion.Rows.Count
        If mydate = wsPublicHoliday.Cells(j, 1) Then
            
            '祝日書込み処理
            If writeHolidays_IX = 1 Then
                For k = 1 To wsEarnings.Range("A1").CurrentRegion.Columns.Count
                    wswriteHolidays.Cells(writeHolidays_IX, k) = wsEarnings.Cells(1, k)
                Next
            End If
            
            writeHolidays_IX = writeHolidays_IX + 1 '書込み変数インクリメント
            
            For k = 1 To wsEarnings.Range("A1").CurrentRegion.Columns.Count
                wswriteHolidays.Cells(writeHolidays_IX, k) = wsEarnings.Cells(i, k)
            Next
            
            endFlg = True
            
        End If
    Next
    
    
    '平日、土日の処理
    If endFlg = False Then
        If Weekday(mydate) = 7 Or Weekday(mydate) = 1 Then
            '休日書込み処理
            If writeHolidays_IX = 1 Then
                For k = 1 To wsEarnings.Range("A1").CurrentRegion.Columns.Count
                    wswriteHolidays.Cells(writeHolidays_IX, k) = wsEarnings.Cells(1, k)
                Next
            End If
            
            writeHolidays_IX = writeHolidays_IX + 1 '書込み変数インクリメント
            
            For k = 1 To wsEarnings.Range("A1").CurrentRegion.Columns.Count
                wswriteHolidays.Cells(writeHolidays_IX, k) = wsEarnings.Cells(i, k)
            Next
            
        Else
            '平日書込み処理
            If wirteWeekdays_IX = 1 Then
                For k = 1 To wsEarnings.Range("A1").CurrentRegion.Columns.Count
                    wswriteWeekdays.Cells(wirteWeekdays_IX, k) = wsEarnings.Cells(1, k)
                Next
            End If
            
            wirteWeekdays_IX = wirteWeekdays_IX + 1 '書込み変数インクリメント
            
            For k = 1 To wsEarnings.Range("A1").CurrentRegion.Columns.Count
                wswriteWeekdays.Cells(wirteWeekdays_IX, k) = wsEarnings.Cells(i, k)
            Next
            
        End If
    End If
    
Next

End Sub


まずは、平日Sheet、土日祝日Sheet書込み用変数を1として宣言します。
また、各Sheetをオブジェクト変数にSetします。

メイン処理は、まず祝日がどうかを変数mydateに売上Sheetの日付を代入し、祝日Sheetのデータをループ処理して探す。
次に、祝日で無い場合は、Weekday関数を使って土日か判断をする。土日であれば、土日祝日Sheet書込みし、それ以外(平日)なら平日Sheetに書込みます。


これをTwitterに投稿したところ、サイト管理者から以下のツッコミがありました。

「祝日の判定をループせずに出来るとより良いと思います。」

ループ処理せずに判定できる方法あるの??と思いました。しかし、サイトの回答を見てみるとCountifを使っていることに気づきました。
そうか、処理対象の日付が祝日Sheetないにあれば、Countifが戻り値が0以上になる!そこで判断すればいいのかということに気づきました。

また、書込み処理部分が長くなってしまうので、書込み処理はサブルーチン化しました。

私の最終回答

'モジュールレベルの変数宣言
Dim wirteWeekdays_IX As Long: Dim writeHolidays_IX As Long

Dim wsEarnings As Worksheet: Dim wswriteHolidays As Worksheet
Dim wswriteWeekdays As Worksheet: Dim wsPublicHoliday As Worksheet

Dim i As Long: Dim endFlg As Boolean: Dim mydate As Date

Sub ノック38本目_2()

wirteWeekdays_IX = 1  '平日書込み用
writeHolidays_IX = 1  '休日書込み用

Set wsEarnings = Worksheets("売上"): Set wswriteHolidays = Worksheets("土日祝")
Set wswriteWeekdays = Worksheets("平日"): Set wsPublicHoliday = Worksheets("祝日")

For i = 2 To wsEarnings.Range("A1").CurrentRegion.Rows.Count
    mydate = wsEarnings.Cells(i, 1): endFlg = False
    
    '祝日の処理
    If WorksheetFunction.CountIf(wsPublicHoliday.Columns(1), wsEarnings.Cells(i, 1)) > 0 Then
        '祝日書込み処理
        If writeHolidays_IX = 1 Then
            For k = 1 To wsEarnings.Range("A1").CurrentRegion.Columns.Count
                wswriteHolidays.Cells(writeHolidays_IX, k) = wsEarnings.Cells(1, k)
            Next
        End If
        
        writeHolidays_IX = writeHolidays_IX + 1 '書込み変数インクリメント
        
        For k = 1 To wsEarnings.Range("A1").CurrentRegion.Columns.Count
            wswriteHolidays.Cells(writeHolidays_IX, k) = wsEarnings.Cells(i, k)
        Next
        
        endFlg = True
    End If
    
    
    '平日、土日の処理
    If endFlg = False Then
        Call 書込み処理
    End If
Next


End Sub

サブルーチン、書込み処理

Private Sub 書込み処理()

If Weekday(mydate) = 7 Or Weekday(mydate) = 1 Then
    '休日書込み処理
    If writeHolidays_IX = 1 Then
        For k = 1 To wsEarnings.Range("A1").CurrentRegion.Columns.Count
            wswriteHolidays.Cells(writeHolidays_IX, k) = wsEarnings.Cells(1, k)
        Next
    End If
    
    writeHolidays_IX = writeHolidays_IX + 1 '書込み変数インクリメント
    
    For k = 1 To wsEarnings.Range("A1").CurrentRegion.Columns.Count
        wswriteHolidays.Cells(writeHolidays_IX, k) = wsEarnings.Cells(i, k)
    Next
    
Else
    '平日書込み処理
    If wirteWeekdays_IX = 1 Then
        For k = 1 To wsEarnings.Range("A1").CurrentRegion.Columns.Count
            wswriteWeekdays.Cells(wirteWeekdays_IX, k) = wsEarnings.Cells(1, k)
        Next
    End If
    
    wirteWeekdays_IX = wirteWeekdays_IX + 1 '書込み変数インクリメント
    
    For k = 1 To wsEarnings.Range("A1").CurrentRegion.Columns.Count
        wswriteWeekdays.Cells(wirteWeekdays_IX, k) = wsEarnings.Cells(i, k)
    Next
    
End If

End Sub


修正点は2点です。
1点目は、祝日の判定処理をCountifに変更しました。
2点目は、休日、平日の書き込み処理をサブルーチン化しました。サブルーチン化すると、処理がサブルーチンのコードに移った時に、プロシージャレベルの変数の値は破棄されるため、書込み用の変数、Sheet用の変数、endFlgはモジュールレベルの変数としました。

ここまでやるなら、祝日の処理も書込み処理のサブルーチンの中に入れれば??と思いましたが、時間がある時に修正したいと思います。
やはり、サブルーチン化するとメイン処理がすっきりと見えます。モジュールレベルの変数をどれにするかが悩ましいところではありますが(;^ω^)。

4 感想

コードの部品化(サブルーチン化)は、可読性をあげる、保守しやすくするために必要だと思っていますが、なかなか手を出せずにいたので今回で少しレベルが上がりました。もっと部品化して、可読性の高いコードを書いていこうと思います。


ではでは、この辺で(^^)/~~~

5 英語の勉強

売上:Earnings
平日:Weekdays
休日:Holiday
祝日:Public Holiday

(土日祝Sheetは「Holidays」と造語を使っております。)

VBA100本ノック 32本目:Excel終了とテキストファイル出力

この記事から得るもの

1 今回のお題

開かれているブック全てを上書き保存して、Excelアプリも終了させてください。
履歴として、保存したブックのパスをテキストファィルに出力してください。
テキストファイルはマクロブックと同一フォルダに以下の名称で出力(SJIS)してください。
log_yyyymmddhhmmss.txt(フルパス出力してください。)

f:id:bimori466:20201212062948p:plain

excel-ubara.com

2 今回のお題の意図

エクセルで開かれている全ブックを上書き保存して、テキストにエクセルのフルパスを書き出すことにより、どのエクセルファイルを保存したかが分かるようにする。

3 回答

私の最初の回答

Sub ノック32本目_1()

'FSO宣言
Dim fso As Object: Set fso = CreateObject("scripting.filesystemobject")
Dim fsoText As Object
Set fsoText = fso.CreateTextFile(ThisWorkbook.Path & "\32本目\log_" & Format(Now, "yyyymmddhhmmss") & ".txt", True)


'ファイルPathを取得
Set myfiles = fso.getfolder(ThisWorkbook.Path & "\32本目").Files

For Each file In myfiles
    'ファイルかどうか判断してlog保存先Pathを取得する
    If file.Name Like "log_*.txt" Then
        logText = file.Path
        Exit For
    End If
Next

fsoText.Close   'テキストファイルを閉じる

'ファイルを開いて書き込む
Open logText For Output As #1
    myBookPath = ThisWorkbook.Path & "\" & ThisWorkbook.Name
    
    '自Book以外を閉じる処理
    For i = Workbooks.Count To 1 Step -1
        otherBookPath = Workbooks(i).Path & "\" & Workbooks(i).Name
        If myBookPath <> otherBookPath Then
            Print #1, otherBookPath: Workbooks(i).Close 'テキストに書込み、ファイルを閉じる
        End If
    Next
    
    'テキストに書込み、自Bookを保存
    Print #1, myBookPath: ThisWorkbook.Save
    
Close #1


'エクセルを終了する
Application.DisplayAlerts = False
Application.Quit

End Sub


一応お題通りの処理はできていますが、fso.CreateTextFileでログ書き込み先のテキストファイルを作って、いったん閉じて「Open For Output As」で開きなおしているのがナンセンスです。

このお題での重要な気づきは、「Open ファイル名 For Output As」の時に、ファイル名が存在しなければ新規に作ってくれます。
つまりfso.CreateTextFileは必要ないのです。

サイト管理者の回答

Sub VBA100_32_01()
    ThisWorkbook.Save
  
    Dim ymdhms As String
    ymdhms = Format(Now(), "yyyymmddhhmmss")
    Open ThisWorkbook.Path & "\log_" & ymdhms & ".txt" For Output As #1
  
    Dim wb As Workbook
    For Each wb In Workbooks
        wb.Save
        Print #1, wb.FullName
    Next
  
    Close #1
    Application.Quit
End Sub


実際動かしてみるとわかるのですが、「Open ThisWorkbook.Path & "\log_" & ymdhms & ".txt" For Output As #1」を実行したときに、Pathフォルダにテキストファイルが作成されます。これは大きな気づきでした。FSOいらんやん!!

4 感想

「Open ファイル名 For Output As」の時に、ファイル名が存在しなければ新規に作ってくれる。
大きな収穫でした。


ではでは、この辺で(^^)/~~~

VBA100本ノック 33本目:マクロ記録の改修

この記事から得るもの

1 今回のお題

「このVBAはマクロの記録から作ったのですが、件数の数値を変更してから実行しなければならず、データ件数も多くて何分も時間がかかりとても困っています。なんとかしてもらえないでしょうか?」
こう頼まれました。VBAを書いて対応してあげましょう。

f:id:bimori466:20201210213414p:plain

Sub ノック33_ソース()

Sheets("データ").Select
For i = 2 To 3500

DoEvents

Range("D" & i).Select
ActiveCell.FormulaR1C1 = _
    "=IFERROR(VLOOKUP(RC[-2],マスタ!C[-3]:C[-1],2,FALSE),"""")"
Range("E" & i).Select
ActiveCell.FormulaR1C1 = _
    "=IFERROR(VLOOKUP(RC[-3],マスタ!C[-4]:C[-2],3,FALSE),"""")"
Range("F" & i).Select
ActiveCell.FormulaR1C1 = _
    "=RC[-1]*RC[-3]"
Range("D" & i & ":F" & i).Select
Selection.Copy
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
    :=False, Transpose:=False
Application.CutCopyMode = False
Next
Range("A1").Select

End Sub

2 今回のお題の意図

処理速度をあげる処理を追加。必要な分だけ処理する。

3 回答

記録マクロのソースコードを動かしてみるとわかるのですが、「For i = 2 To 3500」とループ回数を決めてしまっています。そのため、データが入力されている分だけ処理をする。加えて、処理速度をあげるコードを追加してあげます。

私の回答

Sub ノック33_1()

Dim ws As Worksheet: Set ws = Worksheets("データ")

'処理速度を上げるやつ設定
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual


Sheets("データ").Select
For i = 2 To ws.Cells(Rows.Count, 1).End(xlUp).Row  '最終行の取得
Range("D" & i).Select
ActiveCell.FormulaR1C1 = _
    "=IFERROR(VLOOKUP(RC[-2],マスタ!C[-3]:C[-1],2,FALSE),"""")"
Range("E" & i).Select
ActiveCell.FormulaR1C1 = _
    "=IFERROR(VLOOKUP(RC[-3],マスタ!C[-4]:C[-2],3,FALSE),"""")"
Range("F" & i).Select
ActiveCell.FormulaR1C1 = _
    "=RC[-1]*RC[-3]"
Range("D" & i & ":F" & i).Select
Selection.Copy
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
    :=False, Transpose:=False
Application.CutCopyMode = False
Next
Range("A1").Select


'処理速度を上げるやつ設定終了
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic


End Sub

1 処理速度を上げる処理
  画像描写更新を止める。再計算処理を止める。

2 データの最終行を取得する。


これだけですね。ざっくりした質問には、ざっくりした対応です。


ではでは、この辺で(^^)/~~~

SQL文で他のBookのデータを更新する。

1 閲覧対象者

データを更新するエクセルBookと、データを蓄積するBookを別けたい方(いわゆるDBファーストです)。
更新用Bookからベタ打ちのSQL文を発行するだけなので、ADO接続、SQL文の書き方がよくわからない初心者の方。

2 得られる効果

1) 更新用Bookとデータを蓄積するBookを別けることでデータの管理が容易となる。
2) ADO接続の方法が分かる。
3) 簡単なSQL文(Update)の使用方法がわかる。

3 設計

以下の画像のように、Bookを2つ準備します。

f:id:bimori466:20201204055043p:plain

DB.xlsmの中身

f:id:bimori466:20201204060525p:plain

「氏名」、「カード番号」の2行だけです。クレジットカードの番号管理的なイメージで!

使い方

参照設定

更新用BookにADO接続を使用するために、以下の画像の蛍光線部の参照設定をします。

f:id:bimori466:20201204060329p:plain


DB更新用.xlsmを開きます。このBookの中に以下のコードを記述しています。

Const cnsProvider = "Microsoft.ACE.OLEDB.12.0"
Const cnsExprop = "Extended properties"
Const cnsExcel = "Excel 12.0"
Const cnsDBname = "DB.xlsm"
Const cnsYen = "/"

Sub Update_SQL()

Dim upSQL As String


'Connecionの作成
Set DBcon = New ADODB.Connection
With DBcon
    .Provider = cnsProvider
    .Properties(cnsExprop) = cnsExcel
    .Open ThisWorkbook.Path & "\" & cnsDBname
End With


'SQL文作成 (セルに付けた名前がテーブルとなる)
upSQL = "update [name$] SET 氏名 = '神田 えの' where カード番号 = '2'"


'DBの更新
DBcon.Execute (upSQL)


'終了。オブジェクトの参照破棄
DBcon.Close: Set DBcon = Nothing

End Sub


内容としては、カード番号「2」の氏名を「神田 えの」に変更します。
Update_SQLの実行結果は以下の画像の通り↓↓

f:id:bimori466:20201204061452p:plain

更新したいDB.xlsmのPathは「ThisWorkbook.Path & "\" & cnsDBname」で指定しています。
ファイル名の変更をしたい場合は、「Const cnsDBname = "DB.xlsm"」を別のBook名に変更すればOKです。
ほかのフォルダを指定したい場合は、「ThisWorkbook.Path」ではなく、指定のファルダPATHを直指定してください。

最初のConstの部分でADO接続の設定をしていますのでそのままコピペでOKです。ここはあまり深く考えなくてよいです。

更新用SQL文は変数「upSQL 」に作っています。
upSQL = "update [name$] SET 氏名 = '神田 えの' where カード番号 = '2'"
このSQL文を動作させるためには、DB.xlsmのSheet名を「name」にしておく必要があります。

「DBcon.Execute (upSQL)」でSQL文を実行します。これで「神田 えの」に変更されます。

4 感想

Update文を作成でエラーが出ていて2時間も格闘していました(;^ω^)。原因は、「氏名 =」この空白部分が全角空白でエラーになってました。
今回は、SQL文を直指定していますので実用性はありません。SQL文を条件で作成して更新すると実用的になりますね。
その辺は、次回の記事を作ります。


1つわからなかったことがありました。私はExcel2019で作成しています。なので、Versionは「Excel 16.0」になるのですが、「Const cnsExcel = "Excel 12.0"」の12.0を16.0にするとエラーになります。ここだけなぜかわかりませんでした。わかる方は教えていただけると喜びますm(_ _"m)
Twitterで教えていただいた方によれば、エクセルのversionではなく検索エンジンのversionらしいです。自分でも調べてみますm(_ _"m)}

ではでは、この辺で(^^)/~~~