カテゴリー名: [ADOによるデータベースの中身の把握]
データベース操作のうち、Tableに関する情報の取得を取り上げます。
Accessデータベース(mdbファイル)を素材にします。
サンプルではエンジンとして JET4.0 でなく ACE12.0 を使っているので、
Office2007以降の環境が前提です。
素材とするmdbファイルの用意と、
Excel VBA でADOを利用するための準備について述べます。
データベースの作成方法は「ADOによるデータベースの新規作成」に記載しました。
今回素材とするデータベースは、それを参考に自作してもらってもいいですが、
面倒ならzip圧縮ファイルに同梱の MakeTestDB.vbs を実行してみて下さい。
TestDB.mdb が作成されます。
あるいは、下のサイトから Northwind.mdb をダウンロードできます。それを素材にするのもいいとおもいます。
コンピュータ演習 Excel2000 & Access2000
以下のサンプルプログラムでは、処理するデータベース名を TestDB.mdb にしていますが、別のデータベースを扱う場合はデータベース名を変更してください。
TestDB.mdb には二つの標準テーブル(TableA, TableB)と、
一つの仮装テーブル(TableMerge)が含まれています。
どちらも100人分が記録されており、ID番号が同じだと同一人物です。
TableMerge は、TableA, TableB の二つを結合したものです。つまり、
ID、性別、身長、体重、職業、免許の有無、生年月日からなるデータベースです。
今回、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にしておく必要がありますが、よかったらお試しください。
Accessデータベースのファイルを受け取ったとき、まずはそれにどんなTableが含まれているかを把握するのが先決です。
含まれているTableの名前が分かれば、ExcelのQueryTableの機能を利用して、Accessデータベースの中身をExcelファイルに書き出すことができます。
フィールド情報のことを知らなくても書き出せるので便利です。
今、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を利用します。手順は上のプログラムのとおりです。
テーブル名として次ぎのものが出力されるはずです。
MSysACEs
などの MS で始まるテーブル名は、システムテーブルといわれるものです。ユーザーが意図して作らなくても自動的に設けられます。
システムテーブルの中身に注意を払う必要は、普通はないとおもいます。
上のプログラムでは CAT.Tables
からTableオブジェクトを取り出すのに
For Each
を使っています。
これを番号で取り出す場合は CAT.Tables(0)
とか CAT.Tables(1)
のようにします。番号は0から始まります。
上記のプログラム中、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;
Accessデータベースのテーブルの名前と種類を取得して、標準テーブルをExcelファイルに書き出します。ExcelのQueryTable機能を用います。
まずは VBAマクロを掲げます。ADO,ADOX の参照設定ができているとの前提です。
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の情報が入っています。
一つのテーブルについてみると、下の四つの情報が格納されます。
サンプルプログラムの中では Name, Type しか使っていませんが、参考まで「作成日時」と「更新日時」も入れてみました。
Northwind.mdb には画像データが格納されていますが、今回の方法でExcelに書き出したとき、画像などのバイナリデータが抜け落ちてしまうようです。
また、真偽を示す True, False は、Excelに出力した段階で数値の 1, 0 になります。
というような、いくつか気をつけるべき点はありますが、ExcelのQueryTableを使ってざっとデータベースの中身を確認することができます。
先のVBAマクロと同じ処理を行う VBScript を掲げます。
処理結果は Book1.xls として書き出されます。
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
先述の 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オブジェクトを使えば同じようなことをやれそうですが、あまり簡易な感じではなくなるので素直に「何番目か」をかぞえる方法を採りました。
〜 以上 〜