データベースのTable情報の取得

カテゴリー名: [ADOによるデータベースの中身の把握

2016/11/23

 データベース操作のうち、Tableに関する情報の取得を取り上げます。

 Accessデータベース(mdbファイル)を素材にします。

 サンプルではエンジンとして JET4.0 でなく ACE12.0 を使っているので、
Office2007以降の環境が前提です。

    


《このページの目次》


    

1. 準備

 素材とするmdbファイルの用意と、
 Excel VBA でADOを利用するための準備について述べます。

(1) Accessデータベースの用意

 データベースの作成方法は「ADOによるデータベースの新規作成」に記載しました。

 今回素材とするデータベースは、それを参考に自作してもらってもいいですが、
面倒ならzip圧縮ファイルに同梱の MakeTestDB.vbs を実行してみて下さい。
TestDB.mdb が作成されます。

 あるいは、下のサイトから Northwind.mdb をダウンロードできます。それを素材にするのもいいとおもいます。

コンピュータ演習 Excel2000 & Access2000

 以下のサンプルプログラムでは、処理するデータベース名を TestDB.mdb にしていますが、別のデータベースを扱う場合はデータベース名を変更してください。

    

  TestDB.mdb には二つの標準テーブル(TableA, TableB)と、
一つの仮装テーブル(TableMerge)が含まれています。

  1. TableA: ID、性別、身長、体重からなるデータベース
  2. TableB: ID、職業、免許の有無、生年月日からなるデータベース

 どちらも100人分が記録されており、ID番号が同じだと同一人物です。

 TableMerge は、TableA, TableB の二つを結合したものです。つまり、
 ID、性別、身長、体重、職業、免許の有無、生年月日からなるデータベースです。

目次に戻る


    

(2) VBAでADOを利用するときの準備

 今回、VBScriptプログラムの他に VBAマクロも掲げます。

 Excelにマクロを組み込んでAccessデータベースを操作します。

 その場合、Excelのワークブックにおいて ADO, ADOX に参照設定しておくと何かと便利です。

 参照設定用のファイルは次の二つです。

 上は、Windowsのいろいろなバージョンで共通だとおもいますが、
見つからないときは DOS窓で下の2行を入力すれば確認できるとおもいます。

  CD /D "C:\Program Files\Common Files"
  DIR /b /s msado*.dll

    

 zip圧縮ファイルに同梱の SetExcelMacro.vbs を使えば、参照設定を施した Book1.xls が生成されます。

 SetExcelMacro.vbs を実行すると、macro01.txt などのマクロ記述ファイルを選択するダイアログが開きます。選択したものがマクロとして登録されます。

 SetExcelMacro.vbs を利用する場合、あらかじめマクロのセキュリティの「Visual Basic プロジェクトへのアクセスを信頼する」をonにしておく必要がありますが、よかったらお試しください。

目次に戻る


    

2. Tableの名前と種類

 Accessデータベースのファイルを受け取ったとき、まずはそれにどんなTableが含まれているかを把握するのが先決です。

 含まれているTableの名前が分かれば、ExcelのQueryTableの機能を利用して、Accessデータベースの中身をExcelファイルに書き出すことができます。

 フィールド情報のことを知らなくても書き出せるので便利です。

(1) テーブル名の取得

 今、C:\work\TestDB.mdb の中身を調べようとしているとします。

 この場合、mdbファイルに含まれているTableの名前を出力するためのVBScriptは下のとおりです。

    

Option Explicit
Dim DbPath, CN, ConnStr, CAT, Tbl
DbPath = "C:\work\TestDB.mdb"
ConnStr = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & DbPath & ";"
Set CN = CreateObject("ADODB.Connection")
CN.Open ConnStr
Set CAT = CreateObject("ADOX.Catalog")
CAT.ActiveConnection = CN
For Each Tbl In CAT.Tables
    MsgBox Tbl.Name
Next
CN.Close

    

 データベースの Tableオブジェクトを取り出すのにADOXを利用します。手順は上のプログラムのとおりです。

 テーブル名として次ぎのものが出力されるはずです。

  1. MSysACEs
  2. MSysObjects
  3. MSysQueries
  4. MSysRelationships
  5. TableA
  6. TableB
  7. TableMerge

 MSysACEs などの MS で始まるテーブル名は、システムテーブルといわれるものです。ユーザーが意図して作らなくても自動的に設けられます。

 システムテーブルの中身に注意を払う必要は、普通はないとおもいます。

    

 上のプログラムでは CAT.Tables からTableオブジェクトを取り出すのに
For Each を使っています。

 これを番号で取り出す場合は CAT.Tables(0) とか CAT.Tables(1) のようにします。番号は0から始まります。

目次に戻る


    

(2) テーブルの種類

 上記のプログラム中、Tblという変数にTableオブジェクトを代入していますが、
Tbl.Name でテーブル名を把握しています。

 その他、プログラムには出てきませんが、
Tbl.Type をみるとテーブルの種類を知ることができます。
Tbl.Type は、TABLE などの文字列を返します。

 TestDB.mdb の場合のテーブルの種類は次の3種類です。

    

 「標準テーブル」は、実態としてデータをきちんと保持している通常のテーブルです。

 「仮装テーブル」は、標準テーブルに一定の条件を適用して抽出した結果を便宜的に保持したものです。実態としてのデータは持っておらず、抽出条件が保持されているだけですが、あたかも標準テーブルであるかのように扱うことができます。

 TestDB.mdb の場合、TableMergeが仮装テーブルです。TableA, TableB を合併したものですが、実態としてのデータは持っていません。下のSQL命令によって生成しました。

create view TableMerge as
    select TableA.*, TableB.職業, TableB.免許の有無, TableB.生年月日
    from TableA, TableB
    where TableA.ID = TableB.ID;

目次に戻る


    

3. ExcelのQueryTable機能によるAccessのデータ抽出

 Accessデータベースのテーブルの名前と種類を取得して、標準テーブルをExcelファイルに書き出します。ExcelのQueryTable機能を用います。

(1) VBAマクロ

 まずは VBAマクロを掲げます。ADO,ADOX の参照設定ができているとの前提です。

△ macro01.txt

 1' AccessデータベースのテーブルをExcelに取り込む(QueryTable利用)
 2Sub Macro1()
 3    Dim DbName As String, DbPath As String, ConnStr As String, sql As String
 4    Dim FSO As Object, Tbl As Variant, TblAry As Variant
 5    Dim WSobj As Worksheet, SheetCount As Integer, i As Integer
 6
 7    Set FSO = CreateObject("Scripting.FileSystemObject")
 8    DbName = InputBox("Accessファイルの名前: ", _
 9        "Access→Excel", "TestDB.mdb")
10    If DbName = "" Then Exit Sub
11    CreateObject("WScript.Shell").CurrentDirectory = ThisWorkbook.Path
12    DbPath = FSO.GetAbsolutePathName(DbName)
13    If FSO.FileExists(DbPath) = False Then
14        MsgBox "ファイルがみつかりません: " & DbPath
15        Exit Sub
16    End If
17    TblAry = GetTableInfo(DbPath)
18
19    ConnStr = "ODBC;DSN=MS Access Database;DBQ=" & DbPath
20    SheetCount = 0
21    For Each Tbl In TblAry  ' AccessのTable情報を一つずつ取り出す
22        If (Tbl(1) = "TABLE") Or (Tbl(1) = "VIEW") Then
23            SheetCount = SheetCount + 1
24            If Worksheets.Count < SheetCount Then  ' シート枚数不足
25                Worksheets.Add After:=Worksheets(Worksheets.Count)
26            End If
27            Set WSobj = Worksheets(SheetCount)  ' 最後のシートに着目
28            WSobj.UsedRange.Clear  ' 念のためシートを全クリア
29            WSobj.Name = Tbl(0)  ' シート名をテーブル名にする
30            sql = "select * from [" & Tbl(0) & "];"
31            With WSobj.QueryTables.Add(ConnStr, WSobj.Range("A1"), sql)
32                .BackgroundQuery = False  ' バックグラウンド処理をしない
33                .Refresh
34                .Delete  ' クエリテーブルを削除
35            End With
36        End If
37    Next
38    If (SheetCount > 0) And (Worksheets.Count > SheetCount) Then
39        Application.DisplayAlerts = False  ' 警告メッセージを抑制
40        For i = Worksheets.Count To (SheetCount+1) Step -1
41            Worksheets(i).Delete
42        Next
43        Application.DisplayAlerts = True
44    End If
45    Worksheets(1).Activate  ' 第1シートをアクティブに
46End Sub
47
48' ----------------
49
50Function GetTableInfo(ByVal DbPath As String)
51    Dim ConnStr As String
52    Dim CN As ADODB.Connection, CAT As ADOX.Catalog
53    Dim Tbl As Variant, TblAry() As Variant, TblCount As Long
54
55    ConnStr = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & DbPath & ";"
56    Set CN = New ADODB.Connection
57    CN.Open ConnStr
58    Set CAT = New ADOX.Catalog
59    CAT.ActiveConnection = CN
60
61    TblCount = 0
62    For Each Tbl In CAT.Tables  ' テーブルを一つずつたどる
63        ReDim Preserve TblAry(TblCount)
64        TblAry(TblCount) = Array(Tbl.Name, Tbl.Type, _
65            Tbl.DateCreated, Tbl.DateModified)
66        TblCount = TblCount + 1
67    Next
68    CN.Close
69    GetTableInfo = TblAry
70End Function

    

 Excelを起動してから上記の Macro1 を実行すると、処理するAccessデータベースの名前を入力する場面になります。SetExcelMacro.vbsでマクロを組み込んだ場合は Control + j のショートカットキーでMacro1を実行できます。

 名前として予め TestDB.mdb が入っているので、それでよければエンターキーをたたきます。

 Northwind.mdb などの別のファイルを処理したいときは適当に書き換えてからエンターキーを押してください。

 処理するAccessファイルは、マクロが組み込まれたワークブックがあるのと同じフォルダの中にあるものと仮定します。ただ、フルパスで指定した場合は、その限りではありません。

 あとは、Accessデータベースの標準テーブルと仮装テーブルを一つずつ Excelのワークシートに出力します。QueryTableを利用して行います。

 ワークシートが足りないときは追加し、余ったときは削除します。

    

 今回のテーマ「Table情報の取得」に関係しているのは GetTableInfo 関数です。

 この関数は、C:\work\TestDB.mdb などのデータベース名のフルパスを受け取ると、そのテーブル情報を配列に入れて返します。

 配列 TblAry は、TblAry(0) に第1のテーブルの情報が、TblAry(1) に第2の情報が入っています。

 一つのテーブルについてみると、下の四つの情報が格納されます。

  1. TblAry(0)(0): Name、テーブル名
  2. TblAry(0)(1): Type、テーブルの種類
  3. TblAry(0)(2): DateCreated、テーブルの作成日時
  4. TblAry(0)(3): DateModified、テーブルの更新日時

 サンプルプログラムの中では Name, Type しか使っていませんが、参考まで「作成日時」と「更新日時」も入れてみました。

    

 Northwind.mdb には画像データが格納されていますが、今回の方法でExcelに書き出したとき、画像などのバイナリデータが抜け落ちてしまうようです。

 また、真偽を示す True, False は、Excelに出力した段階で数値の 1, 0 になります。

 というような、いくつか気をつけるべき点はありますが、ExcelのQueryTableを使ってざっとデータベースの中身を確認することができます。

目次に戻る


    

(2) OLEを利用したVBScript

 先のVBAマクロと同じ処理を行う VBScript を掲げます。

 処理結果は Book1.xls として書き出されます。

△ vovTBL01.vbs

 1' AccessデータベースのテーブルをExcelに取り込む(QueryTable利用)
 2Option Explicit
 3Dim FSO, DbName, DbPath, BookName, BookPath
 4Dim ConnStr, Tbl, TblAry, sql
 5Dim EXLapp, WBobj, WSobj, SheetCount, i
 6Const xlWorkbookNormal = -4143
 7BookName = "Book1.xls"
 8
 9Set FSO = CreateObject("Scripting.FileSystemObject")
10DbName = InputBox("Accessファイルの名前: ", _
11    "Access→Excel", "TestDB.mdb")
12If DbName = "" Then WScript.Quit
13DbPath = FSO.GetAbsolutePathName(DbName)
14If FSO.FileExists(DbPath) = False Then
15    MsgBox "ファイルがみつかりません: " & DbPath
16    WScript.Quit
17End If
18TblAry = GetTableInfo(DbPath)
19BookPath = FSO.GetAbsolutePathName(BookName)
20If FSO.FileExists(BookPath) Then FSO.DeleteFile(BookPath)
21Set EXLapp = CreateObject("Excel.Application")  ' Excelの起動
22EXLapp.Visible = True  ' Excelを見える状態に
23Set WBobj = EXLapp.Workbooks.Add()  ' Workbookの新規作成
24
25ConnStr = "ODBC;DSN=MS Access Database;DBQ=" & DbPath
26SheetCount = 0
27For Each Tbl In TblAry
28    If (Tbl(1) = "TABLE") Or (Tbl(1) = "VIEW") Then
29        SheetCount = SheetCount + 1
30        If WBobj.Worksheets.Count < SheetCount Then  ' シート枚数不足
31            WBobj.Worksheets.Add Null, WBobj.Worksheets(WBobj.Worksheets.Count)
32        End If
33        Set WSobj = WBobj.Worksheets(SheetCount)  ' 最後のシートに着目
34        WSobj.Name = Tbl(0)  ' シート名をテーブル名にする
35        sql = "select * from [" & Tbl(0) & "];"
36        With WSobj.QueryTables.Add(ConnStr, WSobj.Range("A1"), sql)
37            .BackgroundQuery = False  ' バックグラウンド処理をしない
38            .Refresh
39            .Delete  ' クエリテーブルを削除
40        End With
41    End If
42Next
43If (SheetCount > 0) And (WBobj.Worksheets.Count > SheetCount) Then
44    EXLapp.DisplayAlerts = False  ' 警告メッセージを抑制
45    For i = WBobj.Worksheets.Count To (SheetCount+1) Step -1
46        WBobj.Worksheets(i).Delete
47    Next
48    EXLapp.DisplayAlerts = True
49End If
50WBobj.Worksheets(1).Activate  ' 第1シートをアクティブに
51WBobj.SaveAs BookPath, xlWorkbookNormal
52EXLapp.quit
53
54' ----------------
55
56Function GetTableInfo(ByVal DbPath)
57    Dim CN, ConnStr, CAT
58    Dim Tbl, TblAry(), TblCount
59
60    ConnStr = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & DbPath & ";"
61    Set CN = CreateObject("ADODB.Connection")
62    CN.Open ConnStr
63    Set CAT = CreateObject("ADOX.Catalog")
64    CAT.ActiveConnection = CN
65    TblCount = 0
66    For Each Tbl In CAT.Tables
67        ReDim Preserve TblAry(TblCount)
68        TblAry(TblCount) = Array(Tbl.Name, Tbl.Type, _
69            Tbl.DateCreated, Tbl.DateModified)
70        TblCount = TblCount + 1
71    Next
72    CN.Close
73    GetTableInfo = TblAry
74End Function

目次に戻る


    

(3) JScript

 先述の VBScript と同じ処理を行う JScript です。

  1// AccessデータベースのテーブルをExcelに取り込む(QueryTable利用)
  2var fso, dbName, dbPath, bookName, bookPath;
  3var connStr, tbl, tblAry, tn, sql;
  4var ExlApp, wb, ws, sheetCount, i;
  5var xlWorkbookNormal = -4143;
  6bookName = "Book1.xls";
  7
  8fso = WScript.CreateObject("Scripting.FileSystemObject");
  9dbName = InputBox("Accessファイルの名前: ",
 10    "Access→Excel", "TestDB.mdb");
 11if (dbName == "") WScript.Quit();
 12dbPath = fso.GetAbsolutePathName(dbName);
 13if (fso.FileExists(dbPath) == false) {
 14    var shellObj = WScript.CreateObject("WScript.Shell");
 15    WScript.Echo("ファイルがみつかりません: " + dbPath);
 16    while (shellObj.AppActivate("Windows Script Host") != true) {
 17        WScript.Sleep(100);
 18    }
 19    WScript.Quit();
 20}
 21tblAry = getTableInfo(dbPath);
 22bookPath = fso.GetAbsolutePathName(bookName);
 23if (fso.FileExists(bookPath) == true)  fso.DeleteFile(bookPath);
 24ExlApp = WScript.CreateObject("Excel.Application");  // Excelの起動
 25ExlApp.Visible = true;  // Excelを見える状態に
 26wb = ExlApp.Workbooks.Add();  // Workbookの新規作成
 27
 28connStr = "ODBC;DSN=MS Access Database;DBQ=" + dbPath;
 29sheetCount = 0;
 30for (tn = 0; tn < tblAry.length; tn++) {
 31    tbl = tblAry[tn];
 32    if (tbl[1] == "TABLE" || tbl[1] == "VIEW") {
 33        sheetCount = sheetCount + 1;
 34        if (wb.Worksheets.Count < sheetCount) {  // シート枚数不足
 35            wb.Worksheets.Add(null, wb.Worksheets(wb.Worksheets.Count));
 36        }
 37        ws = wb.Worksheets(sheetCount);  // 最後のシートに着目
 38        ws.Name = tbl[0];  // シート名をテーブル名にする
 39        sql = "select * from [" + tbl[0] + "];";
 40        with (ws.QueryTables.Add(connStr, ws.Range("A1"), sql)) {
 41            BackgroundQuery = false;  // バックグラウンド処理をしない
 42            Refresh();
 43            Delete();  // クエリテーブルを削除
 44        }
 45    }
 46}
 47if (sheetCount > 0 && wb.Worksheets.Count > sheetCount) {
 48    ExlApp.DisplayAlerts = false;  // 警告メッセージを抑制
 49    for (i=wb.Worksheets.Count; i>sheetCount; i--) {
 50        wb.Worksheets(i).Delete();
 51    }
 52    ExlApp.DisplayAlerts = true;
 53}
 54wb.Worksheets(1).Activate;  // 第1シートをアクティブに
 55wb.SaveAs(bookPath, xlWorkbookNormal);
 56ExlApp.Quit();
 57
 58// ----------------
 59
 60function getTableInfo(dbPath) {
 61    var cn, connStr, cat;
 62    var tbl, tblAry, tblCount, i;
 63
 64    connStr = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + dbPath + ";";
 65    cn = WScript.CreateObject("ADODB.Connection");
 66    cn.Open(connStr);
 67    cat = WScript.CreateObject("ADOX.Catalog");
 68    cat.ActiveConnection = cn;
 69    tblCount = cat.Tables.Count;
 70    tblAry = Array(tblCount);
 71    for (i = 0; i < tblCount; i++) {
 72        tbl = cat.Tables(i);
 73        tblAry[i] = [tbl.Name, tbl.Type,
 74            tbl.DateCreated, tbl.DateModified];
 75    }
 76    cn.Close();
 77    return tblAry;
 78}
 79
 80// ----------------
 81
 82function InputBox(prmpt, ttl, dflt) {
 83    var sh = WScript.CreateObject("WScript.Shell");
 84    var tmpFile = sh.Environment("Process").item("TEMP") + "\\MyTest.vbs";
 85    var fso = WScript.CreateObject("Scripting.FileSystemObject");
 86    var cmdStr = "Set FSO = CreateObject(\"Scripting.FileSystemObject\")\n" +
 87        "dbName = InputBox(\"" + prmpt + "\", _\n" +
 88        "\"" + ttl + "\", \"" + dflt + "\")\n" +
 89        "FSO.GetStandardStream(1).Write dbName\n";
 90    var Fobj = fso.OpenTextFile(tmpFile, 2, true);
 91    Fobj.Write(cmdStr);
 92    Fobj.Close();
 93    execObj = sh.Exec("CScript.exe /Nologo " + tmpFile);
 94    while (execObj.Status == 0) {  // コマンド実行の終了を待つ
 95        WScript.Sleep(100);  // 0.1秒待機
 96    }
 97    var resStr = execObj.StdOut.ReadLine();
 98    fso.DeleteFile(tmpFile);
 99    return resStr;
100}

    

 JScriptでは InputBox が用意されていないようなので、VBScriptの InputBox を利用するようにしました。関数 InputBox がそのためのものです。

 InputBox用のVBScriptプログラムをテンポラリファイルに書き出し、それを別プロセスで実行します。InputBoxで入力された文字列は標準出力に出力。

 元のプロセスに戻ってから標準出力を受け取って、テンポラリファイルを削除します。

 ScriptControl を使えばもう少し簡単に処理できますが、64bit環境ではつかえないので上記の方法を採りました。

 32bit版の WScript.exe, CScript.exe を使う手はありますが、そうすると、データベースエンジンとして ACE12.0 エンジンが使えなくなる(JET4.0 しか使えない)のでやめました。

    

 それから、VBAマクロとVBScriptでは For Each を結構使っています。
集合体からオブジェクトを一つずつ取り出すのに「何番目か」をかぞえることなく取り出せるので便利です。

 ですが、JScriptでは For Each を使えないようです。

 Enumeratorオブジェクトを使えば同じようなことをやれそうですが、あまり簡易な感じではなくなるので素直に「何番目か」をかぞえる方法を採りました。

〜 以上 〜