【ExcelVBA】AccessのデータベースファイルにあるクエリのSQL文をすべて取得するには

この記事では、AccessのデータベースファイルにあるクエリのSQL文をすべて取得する方法についてご説明します。

【動画】AccessのデータベースファイルにあるクエリのSQL文をすべて取得する実際の動き

本題に入る前に、まずは次の動画をご覧ください。


Accessのデータベースファイルを開き、クエリ情報を取得します。

取得したクエリ情報の中からクエリのSQL文を取得してExcelのシートに出力しています。

今回のサンプルのマクロでは、指定したフォルダ配下全てのAccessのデータベースファイルに対してクエリの取得・Excelのシートへの出力を行っています。

マクロ作成の流れ

STEP.1
トップのフォルダ配下全てのフォルダからフォルダオブジェクトを取得する
トップのフォルダ配下全てのフォルダからフォルダオブジェクトを取得します。
STEP.2
フォルダ配下全てのAccessのデータベースファイル名を取得するため、再帰処理を使って繰り返し(For Eachステートメントを使用)行う
フォルダ配下全てのAccessのデータベースファイル名を取得するため、再帰処理を使って繰り返し(For Eachステートメントを使用)行います。
Accessのデータベースファイルは拡張子が「mdb」と「accdb」のファイルです。
STEP.3
STEP.2で取得したAccessのデータベースファイルを開く
STEP.2で取得したAccessのデータベースファイルを開きます。
STEP.4
開いたAccessのデータベースファイルからクエリ情報を取得する
開いたAccessのデータベースファイルからクエリ情報を取得します。
STEP.5
クエリ情報からクエリのSQL文を取得する
クエリ情報からクエリのSQL文を取得します。
STEP.5
取得したクエリのSQL文をExcelファイルのシートに出力する
取得したクエリのSQL文をExcelファイルのシートに出力します。
STEP.6
Accessのデータベースファイルの数だけSTEP.3からSTEP.5を繰り返す
Accessのデータベースファイルの数だけSTEP.3からSTEP.5を繰り返します。

ExcelファイルとAccessのデータベースファイルの例

今回は次のExcelファイルを用意しました。

今回Accessデータベースファイルの検索先は「C:¥work¥10_勉強¥10_VBA関連¥0270¥file」配下とし、フォルダは下のとおりです

フォルダ内にあるAccessのデータベースファイルは下の5つが存在します。

  1. 0270_1.accdb
  2. 0270_2.mdb
  3. 0270_3.mdb
  4. 0270_4.accdb
  5. 0270_5.mdb
0270_1.accdb:Q_Delete_明細

DELETE *
FROM T_明細;
0270_1.accdb:Q_商品S

SELECT T_商品S.商品区分コード, T_商品S.商品区分
FROM T_商品S;
0270_1.accdb:Q_伝票

SELECT T_伝票.伝票番号, T_伝票.売上日, T_伝票.得意先コード
FROM T_伝票;
0270_1.accdb:Q_得意先

SELECT T_得意先.得意先コード, T_得意先.得意先名, T_得意先.[フリガナ], T_得意先.[〒], T_得意先.住所1, T_得意先.住所2, T_得意先.TEL
FROM T_得意先;
0270_2.mdb:Q_社員

SELECT T_社員.項番, T_社員.名前, T_社員.社員番号, T_社員.年齢, T_社員.出身, T_社員.入社年, T_社員.所属部署, T_社員.役職
FROM T_社員;
0270_3.mdb:(クエリなし)

0270_4.accdb:Q_成績表

SELECT T_成績表.名前, T_成績表.国語, T_成績表.数学, T_成績表.物理, T_成績表.化学, T_成績表.世界史, T_成績表.英語
FROM T_成績表;
0270_5.mdb:(クエリなし)

マクロを実行すると、クエリのSQL文がExcelのシートに出力されます。

なお、今回のマクロではクエリのSQL文だけでなく、どのAccessのデータベースファイルのクエリなのか、どこにあるAccessのデータベースファイルなのかといった情報も合わせて出力させています。

コードの例

Option Explicit

Private Sub btn_exec_Click()

    Dim ws              As Worksheet    'ワークシート変数
    Dim fso             As Object       'FileSystemObjectのインスタンス用変数
    Dim cellPosCnt      As Long         'セルの位置用カウンタ
    Dim FolderPath      As String       'フォルダとファイルを検索したいフォルダのトップ階層のパス
    Dim dbFPathAry()    As String       '(Accessのデータベース)ファイルのフルパスを格納する配列
    Dim dbFPathAryCnt   As Long         '配列dbFPathAryの要素数カウント用カウンタ
    Dim db              As Object       'Accessデータベースファイル用インスタンス
    Dim qdf             As DAO.QueryDef 'クエリの情報用変数
    
    'データを出力する最初の行位置
    Const bgnRowPos As Long = 8
    
    'データを出力する最初の行位置を取得する
    cellPosCnt = bgnRowPos
    
    'シートを取得する
    Set ws = Worksheets("work")
            
    'フォルダとファイルを検索したいフォルダのトップ階層のパスを取得する
    FolderPath = Worksheets("work").Range("dirPath").Value
    
    'シートをクリアする
    ws.Range("A" & bgnRowPos & ":E1000").ClearContents
    
    'FileSystemObjectのインスタンスを生成する
    Set fso = CreateObject("Scripting.FileSystemObject")
        
    'フォルダとファイルを検索するサブルーチンを呼び出す
    Call fileSearch(FolderPath, dbFPathAry(), dbFPathAryCnt)

    'Accessのデータベースファイルを開く
    Set db = CreateObject("Access.Application")

    'Accessのデータベースファイルの数分処理を繰り返すFor文
    For dbFPathAryCnt = 0 To UBound(dbFPathAry)
    
        'Accessのデータベースファイルを開く
        db.OpenCurrentDatabase dbFPathAry(dbFPathAryCnt)
        
        'クエリの数分繰り返すFor文
        For Each qdf In db.CurrentDb.QueryDefs
            
            If InStr(qdf.Name, "~") = 0 Then
            
                'クエリ名に「~」が含まれていない場合
                
                'A列のセルに項番の値を出力する
                ws.Range("A" & cellPosCnt).Value = cellPosCnt - bgnRowPos + 1
                
                'B列のセルにAccessのデータベースファイルのフォルダパスを出力する
                ws.Range("B" & cellPosCnt).Value = fso.GetFile(dbFPathAry(dbFPathAryCnt)).ParentFolder.path
                
                'C列のセルにAccessのデータベースファイル名を出力する
                ws.Range("C" & cellPosCnt).Value = fso.GetFile(dbFPathAry(dbFPathAryCnt)).Name
                
                'D列のセルにクエリ名を出力する
                ws.Range("D" & cellPosCnt).Value = qdf.Name
                
                'E列のセルにクエリのSQL文を出力する
                ws.Range("E" & cellPosCnt).Value = qdf.sql
                
                cellPosCnt = cellPosCnt + 1
            
            End If
            
            DoEvents
            
        Next qdf

        'Accessのデータベースファイルを閉じる
        db.CloseCurrentDatabase
        
    Next dbFPathAryCnt
    
    Set db = Nothing
    
End Sub

Sub fileSearch(FolderPath As String, dbFPathAry() As String, dbFPathAryCnt As Long)

    Dim fso         As Object       'FileSystemObjectのインスタンス用変数
    Dim Folder      As Object       'フォルダ用変数
    Dim FileItem    As Object       '取得したファイル用変数
    Dim SubFolder   As Object       'サブフォルダ用変数
        
    'FileSystemObjectのインスタンスを生成する
    Set fso = CreateObject("Scripting.FileSystemObject")
    
    'GetFolderメソッドを使用して、指定したパスのフォルダを取得する
    Set Folder = fso.GetFolder(FolderPath)
    
    'フォルダ内にあるファイルの数分処理を繰り返す
    For Each FileItem In Folder.Files
    
        Select Case fso.GetExtensionName(FileItem.path)
        
            Case "mdb", "accdb"
            
                'ファイル名の拡張子に「mdb」または「accdb」が含まれる場合
                '⇒(Accessのデータベースファイルの場合)
                
                '配列dbFPathAryを再定義する
                ReDim Preserve dbFPathAry(dbFPathAryCnt)
                
                '(Accessのデータベース)ファイルのフルパスを取得して配列dbFPathAryに格納する
                dbFPathAry(dbFPathAryCnt) = FileItem.path
        
                dbFPathAryCnt = dbFPathAryCnt + 1
            
        End Select
        
        DoEvents
        
    Next FileItem
    
    'パスのサブフォルダを対象にループする
    For Each SubFolder In Folder.SubFolders
    
        '本サブルーチンを再帰呼び出しする
        Call fileSearch(SubFolder.path, dbFPathAry(), dbFPathAryCnt)
        
    Next SubFolder
    
End Sub

注目すべきコード①

最初に見て頂きたいのは24行目です。

    'フォルダとファイルを検索したいフォルダのトップ階層のパスを取得する
    FolderPath = Worksheets("work").Range("dirPath").Value

コードの説明

以上のコードは、Accessのデータベースファイルの置き場を取得しているコードです。

Accessのデータベースファイルを検索する対象のトップフォルダのパスの取得は、A5の黄色のセルに入力されたパスから取得します。
※A5の黄色のセルには「dirPath」という名前を付けています。

注目すべきコード②

次に見て頂きたいのは33行目です。

    'フォルダとファイルを検索するサブルーチンを呼び出す
    Call fileSearch(FolderPath, dbFPathAry(), dbFPathAryCnt)

コードの説明

以上のコードは、サブフォルダ含めて全てのフォルダ内のファイルを検索するサブルーチン「fileSearch」を呼び出す処理です。

「fileSearch」には以下の引数を渡して呼び出します。

  1. FolderPath:ファイル名の検索先フォルダ
  2. dbFPathAry():Accessのデータベースファイル名のフルパスを格納する配列
  3. dbFPathAryCnt:配列dbFPathAryの要素数用のカウンタ変数

FolderPath

FolderPathにはファイル名の検索先フォルダを指定します。

FolderPathで指定されたフォルダを「fileSearch」に渡し、「fileSearch」内でファイルが存在するか検索します。

dbFPathAry()

dbFPathAryは、サブルーチン「fileSearch」内で取得するAccessのデータベースファイルのフルパスを格納する配列です。

dbFPathAryCnt

dbFPathAryCntは、配列dbFPathAryの要素数用のカウンタ変数です。

注目すべきコード③

次に見て頂きたいのは91行目から94行目です。

    'FileSystemObjectのインスタンスを生成する
    Set fso = CreateObject("Scripting.FileSystemObject")
    
    'GetFolderメソッドを使用して、指定したパスのフォルダを取得する
    Set Folder = fso.GetFolder(FolderPath)

コードの説明

以上のコードは、FileSystemObjectのインスタンスを生成し、フォルダとファイルを検索したいフォルダのトップ階層のパスのフォルダを取得する処理のコードです。

FileSystemObjectのインスタンスのGetFolderメソッドに、フォルダとファイルを検索したいフォルダのトップ階層のパスを指定して実行することで、パス内のフォルダ名やファイル名を取得できます。

注目すべきコード④

次に見て頂きたいのは97行目から118行目です。

    'フォルダ内にあるファイルの数分処理を繰り返す
    For Each FileItem In Folder.Files
    
        Select Case fso.GetExtensionName(FileItem.path)
        
            Case "mdb", "accdb"
            
                'ファイル名の拡張子に「mdb」または「accdb」が含まれる場合
                '⇒(Accessのデータベースファイルの場合)
                
                '配列dbFPathAryを再定義する
                ReDim Preserve dbFPathAry(dbFPathAryCnt)
                
                '(Accessのデータベース)ファイルのフルパスを取得して配列dbFPathAryに格納する
                dbFPathAry(dbFPathAryCnt) = FileItem.path
        
                dbFPathAryCnt = dbFPathAryCnt + 1
            
        End Select
        
        DoEvents
        
    Next FileItem

コードの説明

以上のコードは、フォルダ内にあるファイルがAccessのデータベースファイルかどうかを判定し、Accessのデータベースファイルの場合はそのファイルのフルパスを配列dbFPathAryに格納する処理のコードです。

フォルダ内にあるファイルがAccessのデータベースファイルかどうかはGetExtensionNameを使い、ファイルの拡張子に「mdb」または「accdb」が含まれるかどうかで判定しています。

コードの詳細

97行目のコードは、フォルダ内にあるファイルの数分処理を繰り返すFor文で、もしファイルの拡張子が「mdb」または「accdb」かどうかを101行目で判定します。

ファイルの拡張子が「mdb」または「accdb」の場合は106行目で配列dbFPathAryを再定義し、110行目でAccessのデータベースファイルのフルパスを取得して配列dbFPathAryに格納します。

注目すべきコード⑤

次に見て頂きたいのは121行目から126行目です。

    'パスのサブフォルダを対象にループする
    For Each SubFolder In Folder.SubFolders
    
        '本サブルーチンを再帰呼び出しする
        Call fileSearch(SubFolder.path, dbFPathAry(), dbFPathAryCnt)
        
    Next SubFolder

コードの説明

以上のコードは、サブフォルダが存在する間は自分自身のサブルーチンを繰り返し呼び出す再帰処理を行っているコードです。

再帰処理とは、サブルーチンの処理が行われている途中で、強制的に自分自身のサブルーチンを呼び出して再度サブルーチンの最初から処理を行わせることを言います。

強制的に自分自身のサブルーチンを呼び出しているのは124行目です。

124行目でfileSearchが呼び出されると、fileSearchの処理の途中で83行目(fileSearchの最初)から強制的に開始されます。

この再帰処理(fileSearch処理中の再呼び出し)は、121行目のFor文内で繰り返し行われますが、このFor文が終わらないかぎりfileSearchが再度呼び出されます。

なお、For文から抜ける条件は、すべてのサブフォルダの参照が終わることです。(引数にサブフォルダを指定してfileSearchを呼び出す)

すべてのサブフォルダを参照すればFor文を抜けます。

For文から抜ければfileSearchが呼び出されることがなくなるので(再帰処理が行われない)、fileSearchのEnd Subまで処理が進みfileSearchの処理を抜けて、fileSearchの呼び出し元の33行目に遷移します。

そのまま33行目の次の行に処理が遷移します。

注目すべきコード⑥

次に見て頂きたいのは36行目から42行目です。

    'Accessのデータベースファイルを開く
    Set db = CreateObject("Access.Application")

コードの説明

以上のコードは、Accessデータベースファイル用インスタンスを生成するコードです。

ExcelのマクロがAccessのデータベースファイルを開くのにこのインスタンスを使用します。(閉じる時も使います)

注目すべきコード⑦

次に見て頂きたいのは39行目から77行目です。

    'Accessのデータベースファイルの数分処理を繰り返すFor文
    For dbFPathAryCnt = 0 To UBound(dbFPathAry)
    
        'Accessのデータベースファイルを開く
        db.OpenCurrentDatabase dbFPathAry(dbFPathAryCnt)
        
        'クエリの数分繰り返すFor文
        For Each qdf In db.CurrentDb.QueryDefs
            
            If InStr(qdf.Name, "~") = 0 Then
            
                'クエリ名に「~」が含まれていない場合
                
                'A列のセルに項番の値を出力する
                ws.Range("A" & cellPosCnt).Value = cellPosCnt - bgnRowPos + 1
                
                'B列のセルにAccessのデータベースファイルのフォルダパスを出力する
                ws.Range("B" & cellPosCnt).Value = fso.GetFile(dbFPathAry(dbFPathAryCnt)).ParentFolder.path
                
                'C列のセルにAccessのデータベースファイル名を出力する
                ws.Range("C" & cellPosCnt).Value = fso.GetFile(dbFPathAry(dbFPathAryCnt)).Name
                
                'D列のセルにクエリ名を出力する
                ws.Range("D" & cellPosCnt).Value = qdf.Name
                
                'E列のセルにクエリのSQL文を出力する
                ws.Range("E" & cellPosCnt).Value = qdf.sql
                
                cellPosCnt = cellPosCnt + 1
            
            End If
            
            DoEvents
            
        Next qdf
        'Accessのデータベースファイルを閉じる
        db.CloseCurrentDatabase
        
    Next dbFPathAryCnt

コードの説明

以上のコードは、Accessのデータベースファイルを開いてクエリ情報を取得し、クエリの名称やクエリのSQL文などをシートの出力する処理のコードです。

コードの詳細

39行目のコードは、Accessのデータベースファイルの数分処理を繰り返すFor文です。

42行目のコードでは、OpenCurrentDatabaseメソッドの引数にAccessのデータベースファイルのフルパスを指定して実行すると、Accessのデータベースファイルを開きます。

Accessのデータベースファイルにあるクエリの情報はOpenCurrentDatabaseメソッドを使ってAccessのデータベースファイルを開くことで取得することができます。

45行目のコードは、クエリの数分繰り返すFor文です。

47行目のコードは、クエリの名前に「~」の文字が含まれるかを判定するIFで、もし含まれていなかったら52行目以降の処理を行います。

名前に「~」の文字が含まれているクエリの例は下の通りです。

ここでなぜ「~」の文字の有無を判定しているのかについては説明が長くなるので、後述する「qdfのNameプロパティの値に対して「~」の文字の有無を判定している理由」で詳しくご説明します。

52行目のコードでは、A列のセルに項番の値を出力します。

55行目のコードでは、B列のセルにAccessのデータベースファイルのフォルダパスを出力します。

58行目のコードでは、C列のセルにAccessのデータベースファイル名を出力します。

61行目のコードでは、D列のセルにクエリ名を出力します。

64行目のコードでは、E列のセルにクエリのSQL文を出力します。

74行目のコードでは、開いていたAccessのデータベースファイルを閉じます。

qdfのNameプロパティの値に対して「~」の文字の有無を判定している理由

47行目のコードでqdfのNameプロパティの値に対して「~」の文字の有無を判定していることについては「注目すべきコード⑦」で触れました。

            If InStr(qdf.Name, "~") = 0 Then

47行目で、なぜqdfのNameプロパティの値に対して「~」の文字の有無を判定しているのかというと、手動で追加したクエリのSQL文だけを取得したいからです。

というのも、qdfのNameプロパティは手動で追加したクエリのSQL文だけでなく、手動で追加したクエリ以外のSQL文も取得してしまうんです。

そこで、「~」の文字の有無を判定することで手動で追加したクエリのSQL文だけを取得するようにしています。

ただしこの判定では不十分で現在の課題となっています。課題については後述します。

【課題】qdfのNameプロパティの値に対して「~」の文字の有無の判定では不十分

実はqdfのNameプロパティの値に対して「~」の文字の有無の判定では不十分です。

ではなぜ「~」の文字の有無の判定では不十分かというと、手動で追加するクエリの名前には「~」を使うことができるからです。

そもそも、手動で追加したクエリか手動で追加したクエリ以外かを判定する情報がないため、代替案として「~」の文字の有無を判定する方法で仮決めしました。
(正確には、情報がないのではなく、見つけられなかったが正しいかもしれません)

例えば「~Q_社員」のようにクエリに「~」の文字を名前に付けられるため、「~Q_社員」と名前を付けられたクエリは47行目の条件判定でスルーされてしまいます。

なので、クエリの名前には「~Q_社員」のような「~」の文字を使わないようするといったルールを決めることが必要となってきます。

動作確認

ExcelファイルとAccessのデータベースファイルの例」をご覧ください。

【注意】参照設定が必要です

一つ注意点があるのですが、先ほどのコードを動かすには参照設定が必要です。

参照設定の一覧(下の画像を参考)から次の項目(ライブラリ)にチェックを付けて「OK」ボタンをクリックします。

  1. Microsoft Office 15.0 Access databese engine Object Library(ACEDAO.DLL)

なぜ必要かというと、Excelのマクロのコードの12行目の「DAO.QueryDef」というオブジェクトが「ACEDAO.DLL」というファイルを参照するからです。

    Dim qdf             As DAO.QueryDef 'クエリの情報用変数

この参照設定をしないと下の画像のエラーが出ますので必ず行う必要があります。

ここでは「ACEDAO.DLL」は何者かについては記事の本題から逸れてしまうので詳細は割愛しますが、マクロで「DAO.QueryDef」というオブジェクトを使う場合は参照設定しないと動かない、程度に思って頂ければと思います。

最後に

この記事では、AccessのデータベースファイルにあるクエリのSQL文をすべて取得する方法についてご説明しました。

AccessのデータベースファイルにあるクエリのSQL文をすべて取得して確認したいときは本記事を参考にしてみてくださいね。

※今回のマクロは課題が一つ残っています。詳しくは「【課題】qdfのNameプロパティの値に対して「~」の文字の有無の判定では不十分」をご覧頂き、マクロのコードをご参照ください。

プログラミングのスキルを習得するなら

プログラミングのスキルを習得したい、今のスキルをもっと高めたい、そう考えているなら「プログラミングスクール」がおすすめです。

プログラミングのスキルの基礎を身につけるなら「TechAcademy」で1週間の無料体験があるので、これで「プログラミングの基礎」を学ぶのにおすすめですよ。

→ TechAcademyの「1週間 無料体験」はこちら