はもちくわ

コードについて自分なりの解釈を書いてます。

【Excel】を使った書類管理(その④:ExcelでCSVを扱う方法(前編)〜疑似データベースサンプル〜

f:id:hamochikuwa440:20211008220136p:plain

 

その③でExcelファイル(ブック形式)の操作について書きました。

今回はCSVファイルを疑似データベースとして扱うコードサンプルを使って、CSVファイルの操作方法を説明していきます。

前回は、ただやり方をダラダラ書いてしまったので、今回は少し書き方を変えて、みなさんのPC上に動くサンプルを説明前に準備していただき、実際に動かしながら、説明を読んでいただくようにします。その方が理解しやすいのかな?と思いますし、そのままコードサンプルをベースに改造して使えるかな?!と思いました。もし、そのように使っていただければ嬉しく思います!

また全部いっぺんに書くととんでもなく長くなるので、前編、後編にわけて書きます。前編でサンプルを動かす準備と使い方、後編でコードの説明をしていきます。

さて、いつも前置きが長くなるので、先に準備から始めようと思いますが、この記事を読み進めて「なんだCSV使えないじゃないか!」となってしまっては、記事を読んだ時間が無駄になってしまうので、一つだけ先に書いておきます。
MicrosoftのOneDriveでファイル共有便利ですよね。Officeがサブスクになって、導入する会社も多くなったのではないでしょうか?でも、残念ながらOneDrive上ではCSVファイル操作ができません。Windowsもデータベースの仕組み持っていますから、そっち使えってことでしょうか(笑)フリーソフトかませると使えるみたいですが、私やったことないので紹介できません。。。申し訳ない。。。CSVファイルを疑似データベースのように使えるなら、OneDrive上でファイルを共有すれば、簡易社内システムが作れるかも?!と考えちゃいますよね。実は、私、このことを知らずに、一度作成に取り組んでしまい、結局、使えずに途方にくれた経験があります。費やした時間は戻らないので、トライする前に事前調査。。。大切ですね。気をつけてください。

 

それではみなさんのPCでこのサンプルコードを使う準備です。

1.フォルダとファイルの準備

次の画層のようにご準備ください。

f:id:hamochikuwa440:20210927214531p:plain

  • PCの任意の場所に任意の名前でフォルダを作成
  • フォルダ内にマクロ有効ブック形式で画像のようなシートを作成
    ブック名、シート名は任意で構いません。始めはシートを1枚にしておいてください。
  • フォルダ内に「DataBase」という名前のフォルダを作成

できましたでしょうか?
もし、シートにボタンの配置方法がわからない方は過去の記事にあります。先にそちらを見て、戻ってきてください。

hamo440.hatenablog.com

「サンプルファイルをダウンロードさせてよ!!」と聞こえてきそうです(笑)
でも、自分で言うのもなんですが、わけのわからない人(私^^;)が作ったものをダウンロードして使うって怖くないですか?
まあ、VBAの扱いになれる為だと思ってお付き合いください。案外このやり方のほうがいいかもですよ!

 

2.VBAコードの配置

次にコードです。先に作成してもらったマクロ有効ブックに入れます。
入れる場所は下の3つになります。

  1. Workbookに配置するもの
  2. Worksheetに配置するもの
  3. Moduleに配置するもの

それではそれぞれ配置していきます。

 

1.Workbookに配置するもの

f:id:hamochikuwa440:20211004225926p:plain

 

コピペは下のコードです。

Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)

    ThisWorkbook.Worksheets(1).Range("B2").Validation.Delete

End Sub

説明は後編で詳しく。

 

2.Worksheetに配置するもの

f:id:hamochikuwa440:20210927225225p:plain

コピペは下のコードです。

Private Sub Worksheet_Change(ByVal Target As Range)

Dim f_name As String, buf() As Variant, cnt As Long, d_up As Long, i As Long
Dim ck As Variant

With Target
   If .Address = "$B$2" Or .Address = "$B$2:$D$2" Then
        ck = ActiveSheet.Range("B2")
        If .Validation.Value = False Or ck = "" Then
            Application.EnableEvents = False
                Call sheet_clear(2)
            Application.EnableEvents = True
        Else
            f_name = Dir(ThisWorkbook.Path & "¥DataBase¥*.csv")
            cnt = 0

            Do While f_name <> ""
                ReDim Preserve buf(cnt)
                buf(cnt) = f_name
                f_name = Dir()
                cnt = cnt + 1
            Loop

            If cnt > 0 Then
                d_up = UBound(buf)
                For i = 0 To d_up
                    If buf(i) = ck Then
                        Application.EnableEvents = False
                        Call staff_Search
                        Call sheet_clear(1)
                        Application.EnableEvents = True
                        Exit For
                    End If
                Next i
            End If
        
        End If
    End If
End With

End Sub

上の画像で、Sheet1に続くカッコ内が(Staff_REG)となってますが、これは私がつけたシート名になっているだけです。任意のシート名をつけている場合はその名前が出てくると思います。何もつけてない方は(Sheet1)です。

ここも説明は後編で詳しく書きます。

 

3.Moduleに配置するもの

f:id:hamochikuwa440:20210927231909p:plain

コピペは下のコードです。ちょっと長いですが、そのまま貼り付けてください。

上の図でも書いてますが、コードの境目ラインはコピペすると勝手に引かれます。

Option Explicit
Dim staff_data() As Variant
Dim s_sheet As Worksheet
Sub sheet_setting()
    Set s_sheet = ThisWorkbook.Worksheets(1)
End Sub

Function file_check() As Variant()

Dim result_arr(3) As Variant

Call sheet_setting

result_arr(0) = False 'ファイルの有無
result_arr(3) = False 'ファイルが開かれているか


If Dir(ThisWorkbook.Path & "¥DataBase¥" & s_sheet.Range("B2")) = s_sheet.Range("B2") Then
    result_arr(0) = True
    
    On Error Resume Next
        Open ThisWorkbook.Path & "¥DataBase¥" & s_sheet.Range("B2") For Append As #1
        Close #1
        If Err.Number > 0 Then result_arr(3) = True
    On Error GoTo 0
    
    result_arr(2) = ThisWorkbook.Path & "¥DataBase¥" & s_sheet.Range("B2")
End If

result_arr(1) = FreeFile

file_check = result_arr

End Function

Function data_input(f_name As Variant, n As Integer)

Dim buf As Variant, buf_s As Variant, d_up As Integer, cnt As Long, i As Long

cnt = 0

Open f_name For Input As n
    Do Until EOF(n)
        Line Input #n, buf
        buf_s = Split(buf, ",")
        d_up = UBound(buf_s)
        
        If cnt = 0 Then
            ReDim staff_data(d_up, cnt)
        Else
            ReDim Preserve staff_data(d_up, cnt)
        End If
        
        For i = 0 To d_up
            staff_data(i, cnt) = buf_s(i)
        Next i
        cnt = cnt + 1
    Loop
Close n

End Function
Function data_output(f_name As Variant, n As Integer)

Dim buf() As Variant, r_up As Integer, c_up As Long
Dim i As Long, j As Long

r_up = UBound(staff_data, 2)
c_up = UBound(staff_data, 1)

ReDim buf(c_up)

Open f_name For Output As n
    For i = 0 To r_up
        For j = 0 To c_up
            buf(j) = staff_data(j, i)
        Next j
        Print #n, Join(buf, ",")
    Next i
Close n

End Function
Function data_add(f_name As Variant, n As Integer, add_data As String)

Dim buf() As Variant, r_up As Integer, c_up As Long
Dim i As Long, j As Long

r_up = UBound(staff_data, 2)
c_up = UBound(staff_data, 1)

ReDim buf(c_up)

Open f_name For Append As n
        Print #n, add_data
Close n

End Function
Function data_sort()

Dim r_up As Integer, c_up As Long
Dim i As Long, j As Long

r_up = UBound(staff_data, 2)
c_up = UBound(staff_data, 1)

Dim no_min As Long, no_max As Long, cnt As Long, staff_data2() As Variant
Dim d_result As Long

staff_data2 = staff_data  'コピーを作る

no_min = 9999999
no_max = 0
cnt = 1
d_result = 0

'番号が小さい順位に並び替え
Do While r_up + 1 <> cnt

For i = 1 To r_up
    If Val(staff_data2(0, i)) < no_min And Val(staff_data2(0, i)) > no_max Then
        no_min = Val(staff_data2(0, i))
        d_result = i
    End If
Next i

For j = 0 To c_up
    staff_data(j, cnt) = staff_data2(j, d_result)
Next j
    no_max = no_min
    no_min = 99999999
    cnt = cnt + 1
Loop

End Function
Function not_file()
 MsgBox "データファイルが見当たりません" & vbCrLf & "DataBaseフォルダを確認してください", vbOKOnly + vbExclamation, "ファイルの確認"
End Function
Function open_file()
 MsgBox "データファイルが開かれているようです" & vbCrLf & "データファイルを閉じるか、少し時間をあけて再度操作してください", vbOKOnly + vbExclamation, "ファイルの確認"
End Function
Function sheet_clear(n As Integer)

Dim r As Integer

Call sheet_setting

With s_sheet
    
    If n = 1 Then
        r = 4
    Else
        r = 3
    End If
    
    If n = 0 Then
        With .Range("B2")
            .Validation.Delete
            .Value = ""
        End With
    End If
    
    Dim r_max As Long, c_max As Long

    r_max = Rows.Count
    c_max = Columns.Count
    r_max = .Cells(r_max, 1).End(xlUp).Row
    c_max = .Cells(3, c_max).End(xlToLeft).Column
    
    If r_max >= r Then
        .Range(.Cells(r, 1), .Cells(r_max, c_max)) = ""
    End If

End With

End Function
Sub file_list()

Call sheet_clear(0)

Dim f_name As String, buf() As Variant, cnt As Long

f_name = Dir(ThisWorkbook.Path & "¥DataBase¥*.csv")
cnt = 0

Do While f_name <> ""
    ReDim Preserve buf(cnt)
    buf(cnt) = f_name
    f_name = Dir()
    cnt = cnt + 1
Loop

If cnt > 0 Then
With s_sheet.Range("B2").Validation
    .Delete
    .add Type:=xlValidateList, Formula1:=Join(buf, ",")
    .ShowError = False
End With
Else
    Call not_file
End If

End Sub
Sub staff_Register()

Dim re_arr As Variant
Dim r_up As Long, c_up As Long
Dim r As Long, c As Long, i As Long, j As Long
Dim add_data As String, cnt As Long
Dim result As String, err_msg As String

re_arr = file_check()

If re_arr(0) = True And re_arr(3) = False Then

    Call data_input(re_arr(2), Val(re_arr(1)))
    
    r_up = UBound(staff_data, 2)
    c_up = UBound(staff_data, 1)
    
    r = 4
    err_msg = ""
    Do While s_sheet.Cells(r, 1) <> ""
    
        If IsNumeric(s_sheet.Cells(r, 1)) = False Then
             err_msg = err_msg & "・ " & s_sheet.Cells(r, 1) & "は数字ではありません。" & vbCr
        End If
        
        For i = 0 To r_up
            If s_sheet.Cells(r, 1) = Val(staff_data(0, i)) Then
                 err_msg = err_msg & "・ " & s_sheet.Cells(r, 1) & "番はすでに使われています。" & vbCr
            End If
        Next i
        r = r + 1
    Loop
    
    If err_msg <> "" Then
        MsgBox err_msg & vbCr & "データの登録をすべて中止しました。" & vbCr & "上記を修正して再度登録してください。" & _
               vbCr & "現在使われている末尾の番号は" & staff_data(0, r_up) & "番です", vbOKOnly + vbExclamation, "番号エラー"
        Exit Sub
    End If
    
    result = ""
    
    r = 4
    Do While s_sheet.Cells(r, 1) <> ""
            
        add_data = ""
        For j = 0 To c_up
            If j = c_up Then
             add_data = add_data & s_sheet.Cells(r, j + 1)
            Else
             add_data = add_data & s_sheet.Cells(r, j + 1) & ","
            End If
        Next j
        
        result = result & s_sheet.Cells(r, 1) & vbCr

        Call data_add(re_arr(2), Val(re_arr(1)), add_data)
        
        ReDim staff_data(c_up, 0) '一度配列を空っぽにする
        
        Call data_input(re_arr(2), Val(re_arr(1)))
        Call data_sort
        Call data_output(re_arr(2), Val(re_arr(1)))
        
        r = r + 1
    Loop
    
    If result <> "" Then
            MsgBox result & "のデータを追加登録しました。", vbOKOnly + vbInformation, "追加登録結果"
            Call sheet_clear(1)
    End If
    
    
Else
    If re_arr(3) = True Then
        Call open_file
    Else
        If s_sheet.Range("B2") Like "*.csv" Then
            If s_sheet.Range("A3") <> "" Then
                r = 3
                c = s_sheet.Range("A3").End(xlToRight).Column
                cnt = 0
                ReDim staff_data(c - 1, cnt)

                Do While s_sheet.Cells(r, 1) <> ""
                    ReDim Preserve staff_data(c - 1, cnt)
                    For i = 0 To c - 1
                        staff_data(i, cnt) = s_sheet.Cells(r, i + 1)
                    Next i
                    r = r + 1
                    cnt = cnt + 1
                Loop
                Call data_output(ThisWorkbook.Path & "¥DataBase¥" & s_sheet.Range("B2"), Val(re_arr(1)))
                MsgBox "新規データの登録ができました", vbOKOnly + vbInformation, "ファイル確認"
                result = s_sheet.Range("B2")
                Call file_list
                s_sheet.Range("B2") = result
            Else
                MsgBox "登録データがありません", vbOKOnly + vbExclamation, "データの確認"
            End If
        Else
            MsgBox "ファイル名に.csvがついていません。", vbOKOnly + vbExclamation, "ファイル確認"
        End If
    End If
End If

End Sub
Sub staff_Search()

Dim re_arr As Variant
Dim r_up As Long, c_up As Long
Dim r As Long, c As Long, i As Long, j As Long

re_arr = file_check()

If re_arr(0) = True Then

    Call data_input(re_arr(2), Val(re_arr(1)))
    
    r = 4
    r_up = UBound(staff_data, 2)
    c_up = UBound(staff_data, 1)
    
    For i = 1 To c_up + 1
        s_sheet.Cells(3, i) = staff_data(i - 1, 0)
    Next i
    
    Do While s_sheet.Cells(r, 1) <> ""
        For i = 0 To r_up
            If s_sheet.Cells(r, 1) = Val(staff_data(0, i)) Then
                For j = 1 To c_up
                    s_sheet.Cells(r, j + 1) = staff_data(j, i)
                Next j
                Exit For
            End If
        Next i
        r = r + 1
    Loop
Else
    Call not_file
End If

End Sub
Sub staff_Delete()

Dim re_arr As Variant
Dim r_up As Long, c_up As Long
Dim r As Long, c As Long, i As Long, j As Long, k As Long
Dim r_max As Long, result As String

re_arr = file_check()

result = ""

If re_arr(0) = True And re_arr(3) = False Then

    Call data_input(re_arr(2), Val(re_arr(1)))
    
    r = 4
    
    Do While s_sheet.Cells(r, 1) <> ""
        r_up = UBound(staff_data, 2)
        c_up = UBound(staff_data, 1)

        For i = 0 To r_up
            If s_sheet.Cells(r, 1) = Val(staff_data(0, i)) Then
                result = result & s_sheet.Cells(r, 1) & vbCr
                For j = i + 1 To r_up
                    For k = 0 To c_up
                        staff_data(k, j - 1) = staff_data(k, j)
                    Next k
                Next j
                
                ReDim Preserve staff_data(c_up, r_up - 1)
                
                Call data_sort
                Call data_output(re_arr(2), Val(re_arr(1)))
                
                Exit For
            
            End If
        Next i
        r = r + 1
    Loop
    
    If result <> "" Then
        MsgBox result & "のデータを削除しました。", vbOKOnly + vbInformation, "削除結果"
        Call sheet_clear(1)
    End If
Else
    If re_arr(0) = False Then Call not_file
    If re_arr(3) = True Then Call open_file
End If

End Sub
Sub clear_button()

Call sheet_clear(1)

End Sub

 

長いですね。あと少しで完成です。

コードの説明は後編でします。

 

3.ボタンにマクロを登録する

次にシートに配置した5つのボタンにマクロを登録して、コピペしたコードを動かせるようにします。

上のコードをそのまま添付すると、ウインドウに表示されるマクロ名は下の表と一緒のものが表示されるはずなので、それぞれのボタンに登録してください。一気に登録できませんので、ボタン1つ1つ操作して登録します。

f:id:hamochikuwa440:20211001225428p:plain

 

ボタン名 マクロ名
ファイル一覧 file_list
登録 staff_Register
検索 staff_Search
削除 staff_Delete
クリア clear_button

これで準備完了です。できましたでしょうか?

 

それではコードの説明前に実際に動かしてみます。

現状確認ですが、作成したフォルダの中には、サンプルのExcelファイルとDataBaseフォルダだけあって、データが全くない状態でしょうか?もし、DataBaseフォルダに他のCSVファイルが入っている状態でしたら最初は移動させておいてください。後で戻してもらって結構です。

 

まず、ファイル一覧ボタンを押してみてください。
CSVファイルはDataBaseフォルダに無い状態ですので、下のような表示が出ると思います。

f:id:hamochikuwa440:20211001232100p:plain

 

次にデータファイルを作ってみます。

B2セルに「staff.csv」と入力し、画像のように項目と一緒にデータを入れてみてください。

f:id:hamochikuwa440:20211001232545p:plain

 

そして登録ボタンを押します。

f:id:hamochikuwa440:20211001232848p:plain

このような画面になったかと思います。
OKボタンを押すと、入力したデータ部分だけ消えると思います。
さて、本当にファイルが作成されいるのでしょうか?確認してみましょう。エクスプローラーやFinderからDataBaseフォルダを開いてみてください。

f:id:hamochikuwa440:20211002000002p:plain

しっかりファイルが作成されていると思います。

 

次に最初に押したファイル一覧ボタンを押します。
ファイル名も項目も消えてしまいました。そのまま、セルB2にカーソルを合わせてみてください。ドロップダウンリストが設定されていると思います。

f:id:hamochikuwa440:20211001233319p:plain

先程作成したファイル名がリストに出てきました。
ファイル名を選択してみてください。

f:id:hamochikuwa440:20211001233648p:plain

先程登録した項目だけが表示されました。
そのまま画像のように別のデータを1行入力します。

f:id:hamochikuwa440:20211001234327p:plain

登録ボタンを押してください。

f:id:hamochikuwa440:20211003003016p:plain

 

idの番号とともに追加登録されたメッセージが出ました。OKボタンを押すと、先程と一緒の動きでデータ部分だけ消えました。
それでは、登録されたかデータを確認します。
idのところに160と入力してください。

f:id:hamochikuwa440:20211003003255p:plain

 

それでは、検索ボタンを押してください。

f:id:hamochikuwa440:20211003003332p:plain

 

先程のデータが表示されました。
そのまま登録ボタンを押してみてください。

f:id:hamochikuwa440:20211003003444p:plain

 

同じ番号では登録できないとガードが入ります。
それでは削除ボタンを押してみてください。

f:id:hamochikuwa440:20211003003540p:plain

 

削除しましたたとメッセージが出ました。
試しにもう一度160と入れて検索すると何も動かないと思います。
それでは、一番最初に入力したデータから3つを検索してみます。
100、130、150と入れて検索しましょう。

f:id:hamochikuwa440:20211003005203p:plain

 

でました。
ではそのまま削除してみてください。

f:id:hamochikuwa440:20211003005347p:plain

 

一気に3行消せました。
次にidをデータの間に入る番号で入れてみます。
125を使ってデータを入れ、登録してみます。

f:id:hamochikuwa440:20211003005530p:plain

 

入りました。データの位置はどうなっているのでしょうか?追加なら最後でしょうか?
CSVファイルを直接開いて確認してみてください。

f:id:hamochikuwa440:20211003005643p:plain

 

データが並び替えられ入ってます。これは並び替えのコードが働いているからです。
CSVファイルを閉じてください。

それではidが文字列だったらどうでしょうか?
文字で入力して登録ボタンを押してみてください。

f:id:hamochikuwa440:20211003005826p:plain

 

このように数字以外は受け付けない仕様にしてますので、文字を発見したら処理を止める命令が入ってます。
次は、操作によって変わるシートの動きをみます。

ファイル一覧ボタンをおしてください。すべてが消えると思います。
その状態でファイル名にoffice.csvといれてみてください。
Excelの入力規制でプルダウンリストを設定してますが、リストを保持したままリスト以外の値も入力できる設定になってます。入力によりデータを消したり、表示したりするのはworksheetに入れたコードのおかげです。
では、このまま別ファイルを作成してみます。

入力したファイル名のまま、次のようなデータを入れてください。項目もしっかり入れてください。

f:id:hamochikuwa440:20211003010301p:plain

 

登録ボタンを押すと

f:id:hamochikuwa440:20211003010337p:plain

 

新規扱いで登録されました。
ファイル名のリストを見てみます。

f:id:hamochikuwa440:20211003010452p:plain

 

ちゃんとファイルが2つになっています。

しっかりCSVファイルになっているかエクスプローラー(もしくはFinder) から直接開いてみてください。
できていましたでしょうか?
それでは、CSVファイルを開いたまま操作してみます。

ファイル一覧を押して、office.csvをリストから選んでください。
そのままidに10を入力して検索してみてください。

f:id:hamochikuwa440:20211008175603p:plain

ファイルが開いたままでもファイル一覧の取得と検索は可能でした。

では、そのまま削除ボタンを押してみてください。

f:id:hamochikuwa440:20211008175715p:plain

ファイルが開いていると書き込みができないのでエラーになります。エラーにならないように先に開いているかチェックする機能を入れてます。登録も書き込みになるので同様の表示が出ます。

このサンプルは、データの項目をCSVのレコードの先頭に入れて、それを表示するようにしています。そうすることでデータごとに項目が設定できるようにしているわけです。
この仕組みにしておけば、あと少し手を加えることで、CSVファイルをテーブルと見立てて、それぞれのカラム名で検索したりできます。実際のデータベースと似た動きができるというわけです。
また、最初に私がサンプルで提示したシートの表が、4列10行なのでデータに制限があるように見えますが、、、実はデータ数は制限つけてません。試しに別のデータファイルで列、行を多くして作成してみてください。動くはずです 。
さらに、他にCSVファイルが有る方はDataBaseフォルダに入れてみてください。ファイル一覧でヒットして表示もできると思います。ただ、最初のレコードは項目、idをレコードの最初(一番左端)に固定していますので、形が違うデータはうまく動かないと思います。また、一度作成したデータファイルの項目数を増やすことはできないようになってます。増やすとエラーが出てプログラムが止まるでしょう。

このように、フォーマットやフォームの形というのは、実はとても大切です。表を作って色をつけただけなのに、入力できない錯覚に陥るわけです。プログラミングのガード無しでも、フォームである程度は行動制限できてしまうんですね。

また、実際に運用することを考えてみると、このサンプルで未対応になっていることは、入力するデータの間に空白があるとデータがそこで終わりと判断してしまうことと、idと項目でデータ数を判断していますので、id無しではうまく動きません。

このサンプルはCSVファイルの操作方法を説明する目的なので、データベースと同じように扱うには、少し足りません。idのオートインクリメント機能やデータの上書き、列の挿入、、、などなど運用を考えるとまだまだ足りませんね。そのあたりは前編後編とは別に付録として追加コードを書こうと思います。しばしお待ち下さい。

さて、、、、現在、後編を作成中です。
後編を作成しながら、無駄なコードを発見して修正したり、機能追加したくなったりと、この記事自体を変更しちゃっています。
もし、最初に見たコードと変わっていたら、「なんか間違ってたんだな・・・」と思ってください。
最初にアップしてから、今回で別コードになるぐらい変わってます。【21年10月8日】
(このあとから追記、変更履歴は記事の最後にいれることにします。)