2022/05/15

ExcelからAccessデータベースを作成・操作




1.背景

今まで「Excelのシート上にデータベース(DB)を作成し、別なExcelブックからデータを操作する」ものとして下記を紹介してきました。
Excelシート上にDBを作り、SQLを使ってデータを入出力する
ExcelシートDBとSQLを使った会議室予約システム
ExcelシートDBとSQLを使った倉庫管理システム
共有資料の登録と閲覧ができるサーバーシステム
複数の備品を同時予約可能な貸出台帳

今回は「(Accessが入っていないPC上の)Excel」から「Accessデータベース」ファイルを作成し操作をする方法について紹介します。データがExcelシート上にあってもAccessデータベース上にあっても、複数の人とデータ共有できることに変わりは無いのですが、Accessという「データの保管・管理・入出力に特化したシステム」にある方が、以下のようなメリットは得られると思います(私見です)。
DBデータの置き場所での比較
データ場所メリットデメリット
Excel
シート
・テーブルが簡単に作れる
・保存データが誰でも確認可
・Delete使えず、削除には工夫要
・データ量増加で(たぶん)処理遅い
・データ型制約等はVBA側で作り込み要
Access ・データの完全性が容易
・大量データでも(たぶん)処理速い
・自動で型制約してくれる
・Deleteが使用可
・テーブルを作る時もSQL必要
・テーブルが直接確認できない
・SQLを知らないとデータ閲覧・操作不可
図1-1

但し、どちらにしても「データベース」としてデータを操作するには「SQL」は避けては通れないので、VBAとは違った技術習得が必要です。
もし「SQLを使わずに」と言うのであれば、Excelなら可能です。データの読出し・書き出しごとに「データが保存されているExcelブックを開く」という手法を使うことになりますが、誰かがデータブックを開いている間は他の人は作業できません(読取専用で開く事は可)し、検索も自分で行う必要があるので、処理時間大というデメリットが生じます。

なお、今回はAccessデータベースの作成と操作に特化した説明ですが、実際のアプリ形式に落とし込んだものとしては以下のものが有ります。併せて参照下さい。
 「Accessデータベースを使用した売上台帳
 「設備の稼働状態を入力し、グラフで確認

2.ADOXについて

Accessのデータベースファイル(拡張子:accdb または mdb )を作るには、通常AccessがインストールされているPCから行いますが、インストールされていなくてもExcelがあればADOXというものを使って作成することが出来ます。また、実際にデータを収めるテーブルもADOXを使って作成・削除することも可能です。

ADOXオブジェクトは「ActiveX Data Objects Extensions for Data Definition Language and Security」の略であり、和訳すると「データ定義とセキュリティのために拡張したADO」となります。「データ定義」は上記のデータベースファイルやテーブルを作成できることを指し、「セキュリティ」はデータベースへのアクセス許可を設定できることを指しているようです。

ADOXオブジェクトは、図2-1のような9つのオブジェクトに分類できますが、Catalogオブジェクトが最上層にあり、ADOXを利用する場合は必ずCatalogオブジェクトを生成する必要があるようです。
オブジェクト内容
Catalogデータソースのスキーマ カタログを記述するコレクションを格納
Table列、インデックス、キーなどのデータベース テーブルを表す
Indexデータベース テーブルのインデックスを表す
Keyデータベース テーブルのプライマリ、外部、または一意のキー フィールドを表す
Columnテーブル、インデックス、キーの列を表す
Groupセキュリティで保護されたデータベース内のアクセス許可を持つグループアカウントを表す
Userセキュリティで保護されたデータベースへのアクセス許可を持つユーザーアカウントを表す
Procedureストアドプロシージャ(複数処理をまとめて実行)を表す
Viewフィルター処理されたレコードセットまたは仮想テーブルを表す
図2-1

上記のオブジェクトの関係をMicrosoftのサイトで「ADOXオブジェクトモデル」として、図2-2のように紹介しています。青色がオブジェクト、白色がコレクションを示しています。
ADOXの各オブジェクトの関係
図2-2

Catalogオブジェクトが最上位にあり、その下に他のオブジェクトがあるのは理解できるのですが、一番下の「Command」はオブジェクトではありません。どうもCommandは、ProcedureやViewに設定するためのCommandプロパティ、またはADOのCommandオブジェクトを指しているようです。

ADOXを利用するには、事前バインディングする方法と実行時バインディングで生成する方法の2通りがあります。
サンプルファイルでは実行時バインディングを使用していますが、事前バインディングでADOXを利用するのであれば、図2-3のようにVBEの「ツール」→「参照設定」から「Microsoft ADO Ext.〇〇 for DDL and ecurity」を有効にして下さい。なお〇〇はバージョンなので環境により異なります。
また事前バインディングの時は、オブジェクトを生成する時のコードが多少変わりますので注意下さい。
事前バインディング
図2-3

2-1.ADOXの定数宣言

ADOXを事前バインディングしている場合には当たり前のように使える定数も、今回は実行時バインディングのコードとしているため「定数宣言」しておく必要があります。
今回は、下記4種類を標準モジュール先頭の宣言部で宣言します(図2-4)。これ以外の定数を使う場合は追加が必要ですし、不要な定数は省略してもOKです。
 02~15行目:図4-1で説明するADOのデータ型
 17~18行目:図4-4で説明するAttributeプロパティの値
 20~22行目:図5-5で説明する制約の値
 24~27行目:図5-10、図6-3で説明するIndexNullsプロパティの値
  1. '========== ⇩(1) ADOXの定数宣言 ============
  2. Const adVarWChar As Long = 202
  3. Const adLongVarWChar As Long = 203
  4. Const adUnsignedTinyInt As Long = 17
  5. Const adSmallInt As Long = 2
  6. Const adInteger As Long = 3
  7. Const adSingle As Long = 4
  8. Const adDouble As Long = 5
  9. Const adGUID As Long = 72
  10. Const adNumeric As Long = 131
  11. Const adDate As Long = 7
  12. Const adCurrency As Long = 6
  13. Const adBoolean As Long = 11
  14. Const adLongVarBinary As Long = 205
  15. Const adVarBinary As Long = 204
  16. Const adColFixed As Integer = 1
  17. Const adColNullable As Integer = 2
  18. Const adKeyPrimary As Long = 1
  19. Const adKeyForeign As Long = 2
  20. Const adKeyUnique As Long = 3
  21. Const adIndexNullsAllow As Long = 0
  22. Const adIndexNullsDisallow As Long = 1
  23. Const adIndexNullsIgnore As Long = 2
  24. Const adIndexNullsIgnoreAny As Long = 4
図2-4

3.Accessデータベースの作成と削除

3-1.データベースの作成

Accessのデータベースファイルを作成するコードが図3-1です。
  1. '========== ⇩(2) データベースファイルの作成 ============
  2. Sub makeDB()
  3.  Const DBname As String = "it-081.accdb"
  4.  Dim DBpath As String: DBpath = ThisWorkbook.Path & "¥"
  5.  Dim Cat As Object
  6.  Set Cat = CreateObject("ADOX.Catalog")
  7.  Cat.Create "Provider=Microsoft.Ace.OLEDB.12.0;" & _
  8.        "Data Source=" & DBpath & DBname
  9.  Set Cat = Nothing
  10. End Sub
図3-1

33行目「Const DBname As String = "it-081.accdb"」では、作成するデータベースファイルを「it-081.accdb」としています。ファイル名は何でもOKですが、拡張子は下記のように決められています。今回は最新の拡張子(.accdb)を使いましたが、.mbdでも動作します。
Access拡張子
   ~97.mdb(Access97形式?)
2000.mdb(Access2000形式)
2002、2003.mdb(Access2002-2003形式)
2007~.accdb
図3-2

34行目「Dim DBpath As String」「DBpath = ThisWorkbook.Path & "¥"」は、2つのコードを「:(コロン)」でつなぎ、1行で表しています。内容は、前半でファイルを置く場所の変数DBpathを宣言し、後半で値を指定しています。今回は、このサンプルファイルと同じ場所にしています。

35行目「Dim Cat As Object」はADOXのCatalogオブジェクトを宣言しています。今回37行目でCreatObjectを使い、実行時バインディングでADOXオブジェクトを生成していますので、型はObjectとなっています。
37行目「Set Cat = CreateObject("ADOX.Catalog")」では、ADOXのCatalogオブジェクトを生成しています。
39~40行目「Cat.Create "Provider=Microsoft.Ace.OLEDB.12.0;" & "Data Source=" & DBpath & DBname 」ではADOXのCreateメソッドで「新規にデータベースファイルを作成」しています。作成する時の構文は以下の通りです。
 Catalogオブジェクト.Create "Provider=プロバイダ―; Data Source=データベースファイル名"

39行目で指定している「Microsoft.Ace.OLEDB.12.0」は、Access2007~2019に対応したProviderです。図3-3のようにバージョンにより対応するProviderが変わりますが、Accessがインストールされていない場合は、まずはAccessのバージョンをOfficeのバージョンに置き換えたProvider(例えばExcel2016であれば、Access2016として12.0または16.0)を使うのが良いと思います。なお、12.0は「何でもOK」みたいなので、今回は12.0を使用しています。
Provider対応するAccess
2000~200320072010201320162019
Microsoft.Jet.OLEDB.4.0
Microsoft.Ace.OLEDB.12.0(〇)
Microsoft.Ace.OLEDB.15.0
Microsoft.Ace.OLEDB.16.0
図3-3

寄り道
図3-2と図3-3を見比べてみると、「Microsoft.Jet.OLEDB.4.0」に対応している拡張子は「.mdb」のみのはずなのですが、「Microsoft.Jet.OLEDB.4.0 で、拡張子accdbのファイルを作成」するコードを実行してもファイルは一応作成されます。しかし内容はmbdのようで、DB使用時のみに作成される「ロック状態を示すファイル」の拡張子が「.ldb(mdbファイルのロックファイル)」となります。
なおデータ授受(Insert文やSelect文)の時には、
 ・4.0で作成した.accdbファイルには、4.0で操作可
 ・12.0で作成した.accdbファイルには、4.0では操作不可
となり「拡張子を見ているのでは無く、ファイルの構造を見ている」という事のようです。

40行目の「Data Source=・・・」には、データベースファイルのパスとファイル名を指定します。
なお、既に同じファイル名が存在する場合には、図3-4のように実行時エラーが発生します。図3-5の59~61行目のように「ファイルが存在しているか確認後、存在しているファイルを削除」してから作成する手順が必要です。
既にファイルが存在するとエラー
図3-4

42行目「Set Cat = Nothing」は、37行目で生成したオブジェクトを解放しています。

3-2.データベースの削除

データベースファイルを削除するコードが図3-5です。
  1. '========== ⇩(3) データベースの削除 ============
  2. Sub deleteDB()
  3.  Const DBname As String = "it-081.accdb"
  4.  Dim DBpath As String: DBpath = ThisWorkbook.Path & "¥"
  5.  Dim Ans As VbMsgBoxResult
  6.  Dim Fso As Object
  7.  Set Fso = CreateObject("Scripting.FileSystemObject")
  8.  If Not Dir(DBpath & DBname) = "" Then
  9.   Ans = MsgBox("DBファイルが存在します。削除して良いですか?", vbYesNo)
  10.   If Ans = vbNo Then Exit Sub
  11.   On Error Resume Next
  12.    Fso.GetFile(DBpath & DBname).Delete
  13.    If Not Err.Number = 0 Then
  14.     MsgBox "削除できません。ファイル使用中の可能性があります。"
  15.    End If
  16.   On Error GoTo 0
  17.  Else
  18.   MsgBox "ファイルが存在しません。"
  19.  End If
  20.  Set Fso = Nothing
  21. End Sub
図3-5

52~53行目は図3-1の時と同様に、データベースファイルの「ファイル名」と「パス名」です。
54行目「Dim Ans As VbMsgBoxResult」は、ユーザーの希望(=ファイルを削除するか否か)を取得するMsgBox関数(60 行目)の戻り値を入れる変数です。データ型を「VbMsgBoxResult」としていますが、内部的にはLong型ですし、戻り値は1~7の整数ですのでInteger型で宣言しても問題はありません。ただし「VbMsgBoxResult型で宣言」しておくことで、コードを書いている途中で「候補値を自動メンバー表示」してくれる機能が働きますので、コード記述時のミスが減ります。

55行目「Dim Fso As Object」は、FileSystemObjectオブジェクトの変数宣言をしています。今回は実行時バインディングを行っていますが、もし事前バインディングの場合は、VBEの参照設定で「Microsoft Scripting Runtime」を有効にして下さい。
57行目「Set Fso = CreateObject("Scripting.FileSystemObject")」では、FileSystemObjectオブジェクトを生成しています。今回は「ファイル削除」のためにFileSystemObjectを使用します。

59行目「If Not Dir(DBpath & DBname) = "" Then」ではDir関数を使って「作ろうとしているファイルが存在するか」を調べています。Dir関数は、引数に指定したファイルが存在するときには「そのファイル名」を戻してきますので、空で無い時(=ファイルが存在する時)に60~68行目を実行することになります。
ファイルが存在する時には、まず60行目「Ans = MsgBox("DBファイルが存在します。削除して良いですか?", vbYesNo)」で「削除して良いか」をユーザーに確認します。MsgBoxには「はい・いいえ(vbYesNo)」のボタンがついており、どちらかの値「vbYes または vbNo」が戻ってきます。
61行目「If Ans = vbNo Then Exit Sub」で、戻って来た値がvbNo(=「いいえ」をクリック)の場合は、削除処理を中止(Exit Sub を実行)します。

64行目「Fso.GetFile(DBpath & DBname).Delete」は、ユーザーが「はい」をクリックした場合に実行されます。
前半の「Fso.GetFile(DBpath & DBname)」では、FileSystemObject(Fso)のGetFileメソッドを使い、引数に指定したファイルを「Fileオブジェクト」にしています。そしてその「Fileオブジェクト」をDeleteメソッドで削除します。
異なる方法として、DeleteFileメソッドを使い「Fso.DeleteFile DBpath & DBname」でも削除できます。

但し、ファイルを削除できない場合があります。今回について言えば「ファイルが使用中」の場合です。その場合は図3-6の左側のようなエラーが発生しますので、63行目「On Error Resume Next」でエラーをスルーさせ、65行目「If Not Err.Number = 0 Then」でエラーが出ている(≒ファイル使用中)時は、66行目「MsgBox "削除できません。ファイル使用中の可能性があります。"」でコメント(図3-6の右側)を出します。
使用中のエラー表示
図3-6

寄り道
なおファイルが使用中の場合、同じファイル名で拡張子が「.laccdb(ロック状態)」のファイルが同じフォルダー内に作成されます(使用中が解除されると、ファイルが削除される)。
しかし何らかの条件でロックファイルが消えない状態が発生するようです。ですので、データベース未使用の場合でも「ファイル使用中」が出ることがあります。

今回のExcelを作っている最中にも何回も発生したのですが、発生条件は結局把握できませんでした。対策はネット上に以下のようなものが紹介されています。(4つ目は私が考えたもの)
 ・Excelの「オプション」→「トラストセンター」→「信頼できる場所」に、データベースファイルのパスを登録。
 ・更新プログラム(2016はKB5002099、2013はKB5002104)をインストールする。
 ・再起動後に.laccdbファイルを削除する。
 ・Excel終了後、タスクマネージャーで生きているExcelプロセスを終了させてから、ファイルを削除。
ちなみに2つ目の更新プログラムも試しましたが、(Accessが無いためか)インストールすら出来ませんでした。また1番目も(私の場合は)効果が見られませんでした。
運用中にデータベースファイルを削除する場面は無いとは思いますが、注意が必要です。

削除するファイルが無い(59行目のDir関数の戻り値が「""(長さゼロの文字列)」)場合は、71行目「MsgBox "ファイルが存在しません。"」でメッセージを出します。
最後に74行目「Set Fso = Nothing」で、FileSystemObjectオブジェクトを解放します。

4.テーブルの作成と削除

4-1.データ型について

テーブルを作成する時には、その中の列(カラム)の名前と同時にデータ型を指定する必要があります。Accessとして設定できるデータ型は図4-1の一番左の列のようになっているようです。
ADOXを利用してテーブルを作るときには「ADOのデータ型」を使用します。一方、SQL文を使ってテーブルを作ることもでき、その時にはSQLで使う型(「AccessDBのSQLで設定する時の型」の列)を使用します。
データ型比較表
図4-1

図4-1の表は、Microsoftのサイト「データ型の比較」を元にして作成しましたが、SQLを使って設定をしてみると、異なった型になってしまうデータ型が複数あることが分かりました(図4-1の二重取り消し線で見え消しにしてあるデータ型)。なお、この結果はExcel VBA から実行した場合であり、他の手段(Access内のコマンド等)を使った場合にはまた別の結果が出る可能性があると思います。
また、adBinary型(128)で列設定をしても、その結果はadVarBinary型(204)に変換されてしまう事も分かりました。

なお、VBAのVariant型に相当する「adVariant型」は存在するようですが、ADOXで列設定しようとするとエラーが発生し、またSQLで「VALUE型(VBAのVariant型に相当)」で設定しようとしてもエラーが発生します。
しかし、もし設定できる方法が見つかったとしても、ExcelにとってVariant型は「セル範囲を一気に配列化する」など特有の使い方には無くてはならない型である一方「何でもありの型」ですので、同一種類のデータを制御する目的のデータベースの型には適さないと思います。

図4-1に示した以外で、「adBigInt型(20):8バイトの符号付き整数」「adWChar型(130):Nullで終わるUnicode文字列」もテーブル作成時に使用可能でしたが、Accessとしてどの型に結び付いているのかは分かりませんでした。

4-2.テーブル作成の手順

テーブルを作る方法は2種類あります。1つはADOXを使う方法で、図4-2のような手順になります。
  •  Set Cat = CreateObject("ADOX.Catalog")   '←Catalogオブジェクト生成
  •  Set TBL = CreateObject("ADOX.Table")    '←Tableオブジェクト生成
  •  Cat.ActiveConnection = "Provider=Microsoft.Ace.OLEDB.12.0;" & _
  •               "Data Source= データベースファイル"   '←データベースに接続
  •  
  •  TBL.Name = テーブル名          '←テーブル名を設定
  •  Set TBL.ParentCatalog = Cat      '←データベースと繋げる
  •  
  •  TBL.Columns.Append "列名", ADOのデータ型   '←新たな列を新テーブルに作成
  •  
  •  Cat.Tables.Append TBL         '←新テーブルをデータベースに追加
図4-2

ADOXでテーブルを作る際、列のプロパティを変更する場合は「TBL.Columns.Append "列名", ADOのデータ型」の直後で設定する必要があります。一旦テーブルをデータベースファイルに追加「Cat.Tables.Append TBL」してしまうと、プロパティは読取のみになってしまい、変更することが不可能になります(可能なプロパティもあります)。

もう1つはSQLを使う方法で、図4-3のような手順になります。
  •  Set cn = CreateObject("ADODB.Connection")   '←Connectionオブジェクト生成
  •  Set rs = CreateObject("ADODB.Recordset")   '←Recordsetオブジェクト生成
  •  
  •  cn.Provider = "Microsoft.ACE.OLEDB.12.0"
  •  cn.Open データベースファイル名          '←データベースに接続
  •  
  •  sql = "Create Table テーブル名 ( 列名 SQLデータ型 )"   '←新テーブルを作るSQL
  •  
  •  rs.Open sql, cn    '←SQLを実行(cn.Execute sql でもOK)
図4-3

ただし図4-2と図4-3のデータ型部分に、図4-1の表の同じ系列のデータ型を使ったとしても、全く同じ列が出来る訳ではありません。データ型は同じでも、細かいプロパティは異なってきます。その違いを説明しているのが「4-3.作成される列の詳細プロパティ」です。
大雑把に言うと、ADOXでテーブルを作ると「ほぼ全てのデータ型でNot Nullの制約が掛かった状態」になります。通常作られるテーブルの列は「Null を許可」が既定状態だと思っていますので、ADOXで作成する場合は注意が必要です。

4-3.作成される列の詳細プロパティ

まず、Catalogオブジェクトの列のプロパティを図4-4に示します。その中の7番目の「Properties」には更に細かいプロパティがありますので図4-5に示しています。
プロパティ内容
Attribute属性を示す。
adColFixed(値=1):列は固定長
adColNullable(値=2):列へのNullは可
DefinedSize列の最大サイズ
Name列の名前
NumericScale小数点以下の桁数
ParentCatalog親のデータベースフィル
Precision全桁数
Properties(図4-5を参照)
RelatedColumn関連する列の名前
SortOrder並べ替え順序
Typeデータ型
図4-4

Noプロパティ名内容
1AutoIncrement列にオートナンバー型を設定する
2Defaultその列の値を設定する
3Description詳細を設定可能
4NullableNull値を許可する
5Fixed Length固定長=True
6Seedオートナンバー型の場合の初期値(行追加と共に増える)
7Incrementオートナンバー型の場合の増分値
8Jet OLEDB:Column Validation Textルールに合致しない時に出されるエラーメッセージ(~255文字)
9Jet OLEDB:Column Validation Rule入力規制の式(~2048文字)「例:<>0」
10Jet OLEDB:IISAM Not Last Columnこの列の後に追加される列が存在することをIISAMに伝える
11Jet OLEDB:AutoGenerate列に対しGUID値を自動的に生成(adGUIDのみ)
12Jet OLEDB:One BLOB per Pageスペース節約の為にDBページを共有する(adLongVarBinaryのみ)
13Jet OLEDB:Compressed Unicode Stringsディスク上のUNICODE文字列を圧縮する
14Jet OLEDB:Allow Zero Length長さ0の文字列(≠Null)を挿入可。(文字列型のみ)
15Jet OLEDB:Hyperlinkハイパーリンク型に設定(LongTextのみ)
図4-5

実際に、以下の様な単純なコードでテーブル・列を作成した時のプロパティを比較していきます。
 ADOX:「TBL.Columns.Append "列名", ADOのデータ型 → Cat.Tables.Append TBL」(図4-2)
 SQL :「Create Table テーブル名 ( 列名 SQLデータ型 )」(図4-3)
データ型ごとに様子が異なりますのでデータごとに説明し、また各表のADOXとSQLで異なる部分は黄色背景にしています。

まず、短いテキスト型に相当するADO型・SQL型の比較表が図4-6です。異なるのは黄色で塗った「Attribute」と「Compressed Unicode Strings」です。なお、この「adVarWChar型」は他の型と異なり、ADOで作成しても「Nullable(プロパティの4)」がTrueとなっています。
しかしADOXで作成したこの列に、Null値を行挿入(この列の値は指定せずに、他の列に値を入れる)しようとすると「値が入っていない」とのエラーが発生します。
そこでSQLと異なっているAttributeの値を揃えるために「TBL.Columns("短いテキスト").Attributes = adColNullable( = 2 でもOK)」の設定をすると、Attributeの値は「adColNullable」に変わり、Nullデータの挿入が可となります。
また、15個のProperties(図4-5)のどれかを設定(既定値を上書きでもOK)すると、なぜかNullデータの挿入が可(Attributeの値がadColNullableに変わる)となります。理由は分かりませんが、「Propertiesを設定するついでに他も見直ししたら、Nullableの値がTrueなのに気が付いて、あわててAttributeの値をadColNullableに変えた」様にも見えます。結果から言うと、短いテキスト型をNull許可にするには、まず「Attributeの値を(SQLと同じ)adColNullableにする」必要があるようです。
短いテキスト型で作成した列の比較
図4-6

長いテキスト型に相当する比較表が図4-7です。この型ではADOで作成すると「Attribute」はゼロ(図4-4で、ゼロ=可変長+Null不可)であり、且つ「Nullable」がFalseとなり、Null値は受け付けない状態になります。
この場合、短いテキスト型と同様にAttributeにadColNullable値を設定するか、または15個のPropertiesのどれかを設定するかするとNullable=Trueになり、Null値を受け付けてくれるようになります。
この様子も短いテキスト型と同様に「Propertiesを設定するついでに他も見直ししたら、Nullableを初期値のTrueにするのを忘れているのに気が付き、あわててAttributeの値をadColNullableに、Nullableの値をTrueに変えた」様に見えます。

なおハイパーリンク型も同じデータ型(adLongVarWChar型)を使うようですが、おそらく「Propertiesの15番目のHyperlinkをTrue」にすることでハイパーリンク機能をONにしているのだと思います。直接Accessから作ったテーブルを確認すればはっきりするのですが、未インストールのため確認できず、申し訳ありません。
長いテキスト型で作成した列の比較
図4-7

数値のバイト型に相当する比較表が図4-8です。SQL側のAttributeの値が「3」となっているのは、adColFixed(値=1)+adColNullable(値=2)の意味だと思われます(図4-4を参照下さい)。
上記同様「Attribute = adColNullable」「Nullable = True」が設定されていないため、このままではNull値挿入は不可です。
バイト型で作成した列の比較
図4-8

数値の整数型に相当する比較表が図4-9です。
上記同様「Attribute = adColNullable」「Nullable = True」が設定されていないため、このままではNull値挿入は不可です。
整数型で作成した列の比較
図4-9

数値の長整数型に相当する比較表が図4-10です。
上記同様「Attribute = adColNullable」「Nullable = True」が設定されていないため、このままではNull値挿入は不可です。
長整数型で作成した列の比較
図4-10

数値の単精度浮動小数型に相当する比較表が図4-11です。
上記同様「Attribute = adColNullable」「Nullable = True」が設定されていないため、このままではNull値挿入は不可です。
単精度型で作成した列の比較
図4-11

数値の倍精度浮動小数型に相当する比較表が図4-12です。
上記同様「Attribute = adColNullable」「Nullable = True」が設定されていないため、このままではNull値挿入は不可です。
倍精度型で作成した列の比較
図4-12

数値のレプリケーションID型に相当する比較表が図4-13です。この型ではNull値挿入不可に加え、「Fixed Length」と「Allow Zero Length」がADOとSQLで異なります。
このadGUID型に例えば100という値を入れると「{00000064-0000-0000-0000-000000000000}」のようになりますので、「固定長(Fixed Length)」= Trueというのは理解できますし、数値ですので「長さゼロの文字列(Allow Zero Length)」= False(不可)というのも分かります。Null値不可を除けば、ADO設定の方が正しい姿を現している気がします。
数値レプリケーションID型で作成した列の比較
図4-13

数値の十進型に相当する比較表が図4-14です。この型では「全体桁数(p:Precision)」と「小数点以下の桁数(n:Numeric Scalse)」を設定する必要があるので、設定によりプロパティ値は変わってきます(表の赤字の部分)
なお「全体桁数(p:Precision)」は必ず設定する必要があり、「小数点以下の桁数(n:Numeric Scalse)」を省略した場合はゼロ桁となります。
また「Attribute = adColNullable」「Nullable = True」が設定されていないため、このままではNull値挿入は不可です。
十進型で作成した列の比較
図4-14

日付時刻型に相当する比較表が図4-15です。
上記同様「Attribute = adColNullable」「Nullable = True」が設定されていないため、このままではNull値挿入は不可です。
日付時刻型で作成した列の比較
図4-15

通貨型に相当する比較表が図4-16です。
上記同様「Attribute = adColNullable」「Nullable = True」が設定されていないため、このままではNull値挿入は不可です。
通貨型で作成した列の比較
図4-16

オートナンバー型に相当する比較表が図4-17です。この型では、オートナンバー型にするために「AutoIncrement(Propertiesの1番目)をTrueに設定(赤字部分)」しています。
その為もあってかNullableもTrueになっており、ADOとSQLのプロパティの違いは見られません。またAttributeもadColNullableの設定がありませんが、Null値挿入のように列指定しなくても「自動的に番号」が振られます。
なお、この列を指定して値を挿入すると、自動番号では無く「指定した値」が保存されます。その後再び列無指定にすると「最後に入力した値を初期値として、再び自動的に番号」が振られていきます。
オートナンバー型で作成した列の比較
図4-17

はいいいえ型に相当する比較表が図4-18です。ADOXのこの型では「Nullable」はFalseで、且つAttributeにadColNullableの設定がありませんので「Null値挿入不可」となりそうですが、やってみると「False(いいえ相当)」が入ります。Propertiesの2番目の「Default」が初期値のEmptyとなっているため、非設定の場合は「Empty = False」で既定値としてのFalseが挿入されるようです。
はいいいえ型で作成した列の比較
図4-18

OLEオブジェクト型に相当する比較表が図4-19です。
上記同様「Attribute = adColNullable」「Nullable = True」が設定されていないため、このままではNull値挿入は不可です。
OLEオブジェクト型で作成した列の比較
図4-19

添付ファイル型(?)に相当する比較表が図4-20です。
上記同様「Attribute = adColNullable」「Nullable = True」が設定されていないため、このままではNull値挿入は不可です。
それ以外に「Fixed Length(固定長)」と「Allow Zero Length(長さゼロの文字列可否)」については、SQLの「BINARY」と「VARBINARY」との間でも異なっており、どちらを使うか注意が必要です。
なお、Microsoftのサイトには「Access自体はBINARYデータ型を使用しない」と書いてあるので、もしかしたらAccess内では添付ファイルのアドレスだけをデータベースファイルで管理し、ファイル本体は別保存しているのかもしれません。
添付ファイル型で作成した列の比較
図4-20

上記の通りADOXを使ってのテーブル+列作成では、ほとんどのデータ型で「Null値挿入不可」となりますので、実務ではSQLを使って作成した方が楽だと思います。但し細かいプロパティ値を操作してテーブルを作成する必要がある場合は、ADOXでの設定が必要になります。

4-4.テーブルの作成

4-4-1.ADOXによる方法

ADOXを利用してテーブルを作成するのが図4-21です。ここでは、Access操作上の型を列名にし、それに対応したADOデータ型を設定しています。
また全ての列は、列の既定状態と思われる「Null値挿入可」に設定します。この設定を後付けで設定出来れば良いのですが、データベース制約のところでも説明する通りテーブル作成後では困難になる為に、この「テーブルの作成段階」で設定します。
設定のやり方は「作成される列の詳細プロパティ」の各データ型のところで説明したように、列のプロパティ値をNull値挿入可の状態に変更します。
  1. '========== ⇩(4) ADOXによるテーブルの作成 ============
  2. Sub ADOmakeTable()
  3.  Const DBname As String = "it-081.accdb"
  4.  Dim DBpath As String: DBpath = ThisWorkbook.Path & "¥"
  5.  Const Tname As String = "AllDataType"
  6.  Dim Cat As Object    '←Catalogオブジェクト
  7.  Dim TBL As Object    '←Tableオブジェクト
  8.  Set Cat = CreateObject("ADOX.Catalog")
  9.  Set TBL = CreateObject("ADOX.Table")
  10.  Cat.ActiveConnection = "Provider=Microsoft.Ace.OLEDB.12.0;" & _
  11.              "Data Source=" & DBpath & DBname
  12.  TBL.Name = Tname
  13.  Set TBL.ParentCatalog = Cat
  14.  With TBL
  15.   .Columns.Append "短いテキスト", adVarWChar
  16.    .Columns("短いテキスト").Attributes = adColNullable    '←Nullを許可
  17.   .Columns.Append "長いテキスト", adLongVarWChar
  18.    .Columns("長いテキスト").Attributes = adColNullable    '←Nullを許可
  19.   .Columns.Append "数値バイト", adUnsignedTinyInt
  20.    .Columns("数値バイト").Attributes = adColNullable + adColFixed    '←Nullを許可
  21.   .Columns.Append "数値整数", adSmallInt
  22.    .Columns("数値整数").Attributes = adColNullable + adColFixed    '←Nullを許可
  23.   .Columns.Append "数値長整数", adInteger
  24.    .Columns("数値長整数").Attributes = adColNullable + adColFixed    '←Nullを許可
  25.   .Columns.Append "数値単精度", adSingle
  26.    .Columns("数値単精度").Attributes = adColNullable + adColFixed    '←Nullを許可
  27.   .Columns.Append "数値倍精度", adDouble
  28.    .Columns("数値倍精度").Attributes = adColNullable + adColFixed    '←Nullを許可
  29.   .Columns.Append "数値レプリケーション", adGUID
  30.    .Columns("数値レプリケーション").Attributes = adColNullable + adColFixed    '←Nullを許可
  31.   .Columns.Append "数値十進型", adNumeric
  32.    .Columns("数値十進型").Attributes = adColNullable + adColFixed    '←Nullを許可
  33.    .Columns("数値十進型").Precision = 4
  34.    .Columns("数値十進型").NumericScale = 2
  35.   .Columns.Append "日付時刻", adDate
  36.    .Columns("日付時刻").Attributes = adColNullable + adColFixed    '←Nullを許可
  37.   .Columns.Append "通貨", adCurrency
  38.    .Columns("通貨").Attributes = adColNullable + adColFixed    '←Nullを許可
  39.   .Columns.Append "オートナンバー", adInteger
  40.    .Columns("オートナンバー").Properties("AutoIncrement") = True
  41.   .Columns.Append "Yes_No", adBoolean
  42.   .Columns.Append "OLE_object", adLongVarBinary
  43.    .Columns("OLE_object").Attributes = adColNullable    '←Nullを許可
  44.   .Columns.Append "ハイパーリンク", adLongVarWChar
  45.    .Columns("ハイパーリンク").Properties("Jet OLEDB:Hyperlink") = True
  46.   .Columns.Append "添付ファイル", adVarBinary
  47.    .Columns("添付ファイル").Attributes = adColNullable    '←Nullを許可
  48.  End With
  49.  On Error Resume Next
  50.   Cat.Tables.Append TBL
  51.   If Not Err.Number = 0 Then
  52.    MsgBox "作成できません。設定が無効か、または既に存在している可能性があります。"
  53.   End If
  54.  On Error GoTo 0
  55.  Set Cat = Nothing
  56.  Set TBL = Nothing
  57. End Sub
図4-21

84行目「Const Tname As String = "AllDataType"」は、作成するテーブルの名前を定数設定しています。
88行目「Set Cat = CreateObject("ADOX.Catalog")」でCatalogオブジェクトを生成し、89行目「Set TBL = CreateObject("ADOX.Table")」でTableオブジェクトを生成します。

90~91行目「Cat.ActiveConnection = "Provider=Microsoft.Ace.OLEDB.12.0;" & "Data Source=" & DBpath & DBname」でデータベースファイルに接続します。この「ActiveConnectionプロパティ」は、アクティブな接続オブジェクトを返すもので、その引数には「接続の定義を含む接続オブジェクト 又は 文字列」を設定しますので、今回は「接続定義の文字列」を指定しています。
なおMicrosoftのサイトで紹介されている手法は「接続の定義を含む接続オブジェクト」を指定するもので、図4-22のようにまずADODB.ConnectionのOpenメソッドでデータベースファイルに接続し、その接続状態をCat.ActiveConnectionに設定するようです。但しこの場合は「cn.Close」で接続を終了させる必要があります。
  •  Set Cat = CreateObject("ADOX.Catalog")
  •  Set cn = CreateObject("ADODB.Connection")
  •  
  •  cn.Open "Provider=Microsoft.Ace.OLEDB.12.0;" & "Data Source=" & DBpath & DBname
  •  Set Cat.ActiveConnection = cn
図4-22

93行目「TBL.Name = Tname」では、TableオブジェクトのNameプロパティに「テーブル名」を設定します。
94行目「Set TBL.ParentCatalog = Cat」では、Tableオブジェクトの親Catalog(=テーブルを所有するCatalog)として、データベースファイルに接続しているCatalog(Cat)を設定します。
なお、このParentCatalogの設定は、133行目「Cat.Tables.Append TBL」でテーブルを登録する時に自動的に設定されます。しかしNull設定以外でも、例えば122行目「.Columns("オートナンバー").Properties("AutoIncrement") = True」のような列のプロパティ設定が必要な場合には「事前にParentCatalog設定をしておかないとエラー」となりますので、列設定の前(94行目)のタイミングでParentCatalog設定をしておいた方が無難です。

96~130行目で合計16列を作っていきます。今回は図4-1の表の「Access操作上の型名称」を列名にし、それに対応したデータ型としました。
基本的には「 Table.Columns.Append 列名 , ADOデータ型 」という形で各列を設定していきます。
但し97行目の「adVarWChar(短いテキスト型)」は、データ型の後ろにカンマに続けて「文字数」を設定することが可能です。97行目の様に未設定の場合には最大の255文字が設定されますが、255超の値を設定するとエラーが発生します。
また99行目「adLongVarWChar(長いテキスト型)」も文字数設定がありそうですが、短いテキスト型と同様の方法で設定してもプロパティ(Defined Size)値はゼロのまま変化ありません(且つ、制限である65,536文字を超えた値を設定してもエラーも出ません)。

一方113~116行目の「十進型」は、Precisionプロパティに「全体の桁数」を指定し、NumericScaleプロパティに「小数点以下の桁数」を指定する必要があります。
また121~122行目のオートナンバー型では、プロパティの「AutoIncrement」をTrueにしておく必要があります。なおオートナンバー型には「adGUID型」もあるようですが、adGUIDを設定するとエラーが発生してしまいます。

新たに作るテーブル(ここではTBLオブジェクト)の列設定が完了したら、133行目「Cat.Tables.Append TBL」でデータベースファイルにテーブルを追加します。しかし「追加しようとしたテーブル名は既に存在」していたり「テーブルの設定が間違っている」場合もありますので、132行目「On Error Resume Next」でエラーをスルーさせ、134行目「If Not Err.Number = 0 Then」でエラー有無を確認し、エラー(=テーブルが作られない)の場合は135行目「MsgBox "作成できません。設定が無効か、または既に存在している可能性があります。"」でコメントを表示します。

4-4-2.SQLによる方法

ADOXを使わず、データベースに対してSQLを発行してテーブルを作成する方法が、図4-23です。
  1. '========== ⇩(5) SQLによるテーブルの作成 ============
  2. Sub SQLmakeTable()
  3.  Dim DBpath As String: DBpath = ThisWorkbook.Path & "¥"
  4.  Const DBname As String = "it-081.accdb"
  5.  Const Tname As String = "AllDataType"
  6.  Dim sql As String    '←SQL文
  7.  Dim cn As Object    '←Connectionオブジェクト
  8.  Dim rs As Object    '←Recordsetオブジェクト
  9.  Set cn = CreateObject("ADODB.Connection")
  10.  Set rs = CreateObject("ADODB.Recordset")
  11.  cn.Provider = "Microsoft.ACE.OLEDB.12.0"
  12.  cn.Open DBpath & DBname
  13.  sql = "Create Table " & Tname & " (" & _
  14.      "短いテキスト ALPHANUMERIC," & _
  15.      "長いテキスト LONGTEXT," & _
  16.      "数値バイト BYTE," & _
  17.      "数値整数 SHORT," & _
  18.      "数値長整数 LONG," & _
  19.      "数値単精度 SINGLE," & _
  20.      "数値倍精度 DOUBLE," & _
  21.      "数値レプリケーション GUID," & _
  22.      "数値十進型 NUMERIC(4,2)," & _
  23.      "日付時刻 DATETIME," & _
  24.      "通貨 CURRENCY," & _
  25.      "オートナンバー COUNTER," & _
  26.      "Yes_No BIT," & _
  27.      "OLE_object LONGBINARY," & _
  28.      "ハイパーリンク LONGTEXT," & _
  29.      "添付ファイル BINARY" & _
  30.      ")"
  31.  On Error Resume Next
  32.   rs.Open sql, cn    '← cn.Execute sql でもOKです
  33.   If Not Err.Number = 0 Then
  34.    MsgBox "設定が無効か、作成するテーブルが既に存在する可能性があります。"
  35.   End If
  36.  On Error GoTo 0
  37.  cn.Close
  38.  Set rs = Nothing
  39.  Set cn = Nothing
  40. End Sub
図4-23

159行目「Set cn = CreateObject("ADODB.Connection")」でConnectionオブジェクトを生成し、160行目「Set rs = CreateObject("ADODB.Recordset")」でRecordsetオブジェクトを生成します。
162行目「cn.Provider = "Microsoft.ACE.OLEDB.12.0"」でプロバイダを設定し、163行目「cn.Open DBpath & DBname」でデータベースファイルを開きます。
162~163行目を一緒にして「cn.Open "Provider=Microsoft.Ace.OLEDB.12.0;" & "Data Source=" & DBpath & DBname」としてもOKです。

165~182行目は1つのSQL文で、ADOXでテーブルの時と同じく合計16列を作ります。列名は「Access操作上の型名称」とし、それに対応したデータ型は図4-1の表の「AccessDBのSQLで設定する時の型」を使用しています。複数の型が使用できるものは先頭の型を使用していますが、他の型でも同じ結果が得られるはずです(但し、添付ファイル型以外は)。
特記するものとして「十進型」は、NUMERICまたはDECIMALを使用し引数のカッコ内に「全体の桁数」と「小数点以下の桁数」をカンマで区切って指定します。
また、オートナンバー型はCOUNTERまたはAUTOINCREMENTを指定するだけで、図4-21の122行目「AutoIncrement」プロパティがTrueになってくれます。

なおSQLの場合、列名に「YesNo」や「OLEobject」という文字列を使用すると、「フィールド定義の構文エラー」というエラーが発生します。一般的なデータベースでは、どちらも予約語の対象にはなっていないのですが、Accessでは予約語(列名、オブジェクト名、変数名には使用できないワード)に登録されています(Microsoftサイト:Access の予約語の一覧)。「なぜかSQL文がエラーになる」ような場合は、一度予約語の問題を疑ってみると良いと思います。

SQL文が完成したら、185行目「rs.Open sql, cn」でRecordsetをOpenさせてSQL文を実行します。
但し、SQL文が間違ってたり、既に同名のテーブルが存在する場合にはエラーとなりますので、184行目「On Error Resume Next」でエラーをスルーさせ、186行目「If Not Err.Number = 0 Then」でエラーを検出し、187行目「MsgBox "設定が無効か、作成するテーブルが既に存在する可能性があります。"」でコメントを出します。

Create TableのSQL文の時は「SQLによって取得するデータは無い」ためにRecordsetは勝手に閉じるので、まだ閉じていないConnectionを191行目「cn.Close」で閉じます。
寄り道
なお「Create」「Drop」「Insert」「Update」のように、テーブルからのレコード(戻り値)を受け取らない(=テーブルに対して命令を実行するだけ)時の実行には、いくつかの方法があります。
ExcelシートDBとSQLを使った倉庫管理システム」でも紹介していますが、3種が考えられます。

1つ目は図4-24の「Recordsetオブジェクトを使用」する方法です。サンプルファイルではこの方法を多く使っています。
戻り値であるレコードを受取らないSQL文の場合にはRecordsetが勝手に閉じるため、rs.Closeは不要となっています。
(私は良く使う方法ですが、他サイトではほとんど紹介されていません。)
  •  Set cn = CreateObject("ADODB.Connection")
  •  Set rs = CreateObject("ADODB.Recordset")
  • ' Set cmd = CreateObject("ADODB.Command")
  •  
  •  cn.Provider = "Microsoft.ACE.OLEDB.12.0"
  •  cn.Open データベースファイル名
  •  
  •   sql = "Create Table " & テーブル名 & " (列設計)"
  •  
  •   rs.Open sql, cn
  •  
  •  cn.Close
図4-24

2つ目は図4-25の「ConnectionオブジェクトのExecuteメソッドでSQL文を実行」させる方法です。最もコード数の少ない方法で、しかも実行部分を「Set rs = cn.Execute sql」などとしておけば、Select文のSQLを実行した場合に「取得したレコードが変数rsに戻ってくる」ようにもできます。
  •  Set cn = CreateObject("ADODB.Connection")
  • ' Set rs = CreateObject("ADODB.Recordset")
  • ' Set cmd = CreateObject("ADODB.Command")
  •  
  •  cn.Provider = "Microsoft.ACE.OLEDB.12.0"
  •  cn.Open データベースファイル名
  •  
  •   sql = "Create Table " & テーブル名 & " (列設計)"
  •  
  •   cn.Execute sql
  •  
  •  cn.Close
図4-25

3つ目は図4-26の「CommandオブジェクトのExecuteメソッドでSQL文を実行」させる方法です。実行部分を「Set rs = cmd.Execute」などとしておけば、Select文を実行した場合に「取得したレコードが変数rsに戻ってくる」ようにもできます。
またSQLインジェクション対策として「プレースホルダ」を使う場合は、この手法が必要そうです。
  •  Set cn = CreateObject("ADODB.Connection")
  • ' Set rs = CreateObject("ADODB.Recordset")
  •  Set cmd = CreateObject("ADODB.Command")
  •  
  •  cn.Provider = "Microsoft.ACE.OLEDB.12.0"
  •  cn.Open データベースファイル名
  •  
  •   sql = "Create Table " & テーブル名 & " (列設計)"
  •  
  •   cmd.ActiveConnection = cn
  •   cmd.CommandText = sql
  •   cmd.Execute
  •  
  •  cn.Close
図4-26

他サイトを見た限りでは3つ目の方法が一番多いようですが、分かり易さから言うと2つ目かな と思います。また、Select文でレコードを取得する時にはほとんどが1つ目のパターンですので、Select文以外でも同じパターンを使って「SQL実行時以降を取得データ有無で分岐させる」という考え方で、私は1つ目を良く使っています。

4-5.テーブルの削除

4-5-1.ADOXによる方法

ADOXを利用してテーブルを削除するのが図4-27です。
  1. '========== ⇩(6) ADOXによるテーブルの削除 ============
  2. Sub ADOdeleteTable()
  3.  Const DBname As String = "it-081.accdb"
  4.  Dim DBpath As String: DBpath = ThisWorkbook.Path & "¥"
  5.  Const Tname As String = "AllDataType"
  6.  Dim Cat As Object     '←Catalogオブジェクト
  7.  Set Cat = CreateObject("ADOX.Catalog")
  8.  Cat.ActiveConnection = "Provider=Microsoft.Ace.OLEDB.12.0;" & _
  9.              "Data Source=" & DBpath & DBname
  10.  On Error Resume Next
  11.   Cat.Tables.Delete Tname
  12.   If Not Err.Number = 0 Then
  13.    MsgBox "削除できません。「" & Tname & "」が存在しないか、使用中の可能性があります。"
  14.   End If
  15.  On Error GoTo 0
  16.  Set Cat = Nothing
  17. End Sub
図4-27

207行目「Set Cat = CreateObject("ADOX.Catalog")」でCatalogオブジェクトを生成し、208~209行目「Cat.ActiveConnection = "Provider=Microsoft.Ace.OLEDB.12.0;" & "Data Source=" & DBpath & DBname」でデータベースファイルを開きます。

そして212行目「Cat.Tables.Delete Tname」で、テーブル「Tname」を削除します。構文は以下です。
 「Catalogオブジェクト.Tables.Delete 削除するテーブル名 」
但し削除しようとするテーブルが無かったり、テーブルが使用中だったりした時には削除できずエラーが出ます。そこで211行目「On Error Resume Next」でエラーをスルーさせ、214行目「If Not Err.Number = 0 Then」でエラーを検出し、215行目「MsgBox "削除できません。「" & Tname & "」が存在しないか、使用中の可能性があります。"」でコメントを出します。

4-5-2.SQLによる方法

ADOXを使わず、データベースに対してSQLを発行してテーブルを削除する方法が、図4-28です。
  1. '========== ⇩(7) SQLによるテーブルの削除 ============
  2. Sub SQLdeleteTable()
  3.  Dim DBpath As String: DBpath = ThisWorkbook.Path & "\"
  4.  Const DBname As String = "it-081.accdb"
  5.  Const Tname As String = "AllDataType"
  6.  Dim sql As String     '←SQL文
  7.  Dim cn As Object     '←Connectionオブジェクト
  8.  Dim rs As Object     '←Recordsetオブジェクト
  9.  Set cn = CreateObject("ADODB.Connection")
  10.  Set rs = CreateObject("ADODB.Recordset")
  11.  cn.Provider = "Microsoft.ACE.OLEDB.12.0"
  12.  cn.Open DBpath & DBname
  13.  sql = "drop Table " & Tname
  14.  On Error Resume Next
  15.   rs.Open sql, cn
  16.   If Not Err.Number = 0 Then
  17.    MsgBox "削除するテーブルが存在しないか、使用中の可能性があります。"
  18.   End If
  19.  On Error GoTo 0
  20.  cn.Close
  21.  Set rs = Nothing
  22.  Set cn = Nothing
  23. End Sub
図4-28

239行目「Set cn = CreateObject("ADODB.Connection")」でConnectionオブジェクトを生成し、240行目「Set rs = CreateObject("ADODB.Recordset")」でRecordsetオブジェクトを生成します。
242行目「cn.Provider = "Microsoft.ACE.OLEDB.12.0"」でプロバイダを設定し、243行目「cn.Open DBpath & DBname」でデータベースに接続します。

245行目「sql = "drop Table " & Tname」は、テーブルを削除するSQL文で構文は以下の通りです。
 「Drop Table 削除するテーブル名 」
248行目「rs.Open sql, cn」でRecordsetをOpenさせてSQL文を実行します。
但しテーブルが無かったり、テーブルが使用中だったりすると、削除できずにエラーとなりますので、247行目「On Error Resume Next」でエラーをスルーさせ、249行目「If Not Err.Number = 0 Then」でエラーを検出し、250行目「MsgBox "削除するテーブルが存在しないか、使用中の可能性があります。"」でコメントを出します。
Drop TableのSQL文の時は「SQLによって取得するデータは無い」ためにRecordsetは勝手に閉じるので、まだ閉じていないConnectionを254行目「cn.Close」で閉じます。

4-6.テーブルの修正

4-6-1.ADOXによる方法

ADOXを使ってテーブルの修正をするのが図4-29です。なお図4-29では、「追加した列」を「列の削除」しているため、そのまま実行しても意味がありません。また見え消しにしている「列属性の変更」を実行するとエラーがでますので注意して下さい。
  1. '========== ⇩(8) ADOXによるテーブルの修正 ============
  2. Sub ADOalterTable()
  3.  Const DBname As String = "it-081.accdb"
  4.  Dim DBpath As String: DBpath = ThisWorkbook.Path & "\"
  5.  Const Tname As String = "AllDataType"
  6.  Dim Cat As Object     '←Catalogオブジェクト
  7.  Set Cat = CreateObject("ADOX.Catalog")
  8.  Cat.ActiveConnection = "Provider=Microsoft.Ace.OLEDB.12.0;" & _
  9.              "Data Source=" & DBpath & DBname
  10.  On Error Resume Next
  11.   '// 列の追加 //
  12.   Cat.Tables(Tname).Columns.Append "alterColumn", adUnsignedTinyInt
  13.   '// 列属性の変更 //
  14. '  Cat.Tables(Tname).Columns("alterColumn").Type = adInteger
  15.   '// 列の削除 //
  16.   Cat.Tables(Tname).Columns.Delete "alterColumn"
  17.   If Not Err.Number = 0 Then
  18.    MsgBox "変更するテーブルが存在しないか、列変更が間違っている可能性があります。"
  19.   End If
  20.  On Error GoTo 0
  21.  Set Cat = Nothing
  22. End Sub
図4-29

267行目「Set Cat = CreateObject("ADOX.Catalog")」でCatalogオブジェクトを生成し、268~269行目「Cat.ActiveConnection = "Provider=Microsoft.Ace.OLEDB.12.0;" & "Data Source=" & DBpath & DBname」でデータベースファイルを開きます。

列の修正には3種類が考えられます。「列の追加」「列の属性(データ型)変更」「列の削除」です。
まず「列の追加」は273行目「Cat.Tables(Tname).Columns.Append "alterColumn", adUnsignedTinyInt」のように
 「Catalogオブジェクト.Tables(テーブル名).Columns.Append 追加する列名,データ型
とします。ここでは追加する列名をalterColumn、データ型をバイト型(adUnsignedTinyInt)としています。
テーブルを新規に作成(図4-21)する際には、Tableオブジェクトに対して列をAppendしていたのですが、列追加では「既に存在するテーブルに対する変更」のため、直接Catalogオブジェクトの列に対して処理をします。

1つ飛ばして「列の削除」は、279行目「Cat.Tables(Tname).Columns.Delete "alterColumn"」のように
 「Catalogオブジェクト.Tables(テーブル名).Columns.Delete 削除する列名
とします。

最後に「列の属性(データ型)変更」ですが、本来ならば276行目「Cat.Tables(Tname).Columns("alterColumn").Type = adInteger」のような形でデータ型を変更できれば良いのですが、エラーが出てしまいます。
ですので、一旦「列の削除」を行った後、新たなデータ型で「列の追加」をするしか手が無さそうです。

4-6-2.SQLによる方法

SQLによるテーブルの修正を行っているのが図4-30です。なおここでは、同じ列名に対して「列の追加」「列属性の変更」「列の削除」を行っているため、そのまま実行しても意味がない事を御了解下さい。
  1. '========== ⇩(9) SQLによるテーブルの修正 ============
  2. Sub SQLalterTable()
  3.  Dim DBpath As String: DBpath = ThisWorkbook.Path & "¥"
  4.  Const DBname As String = "it-081.accdb"
  5.  Const Tname As String = "AllDataType"
  6.  Dim sql As String     '←SQL文
  7.  Dim cn As Object     '←Connectionオブジェクト
  8.  Dim rs As Object     '←Recordsetオブジェクト
  9.  Set cn = CreateObject("ADODB.Connection")
  10.  Set rs = CreateObject("ADODB.Recordset")
  11.  cn.Provider = "Microsoft.ACE.OLEDB.12.0"
  12.  cn.Open DBpath & DBname
  13.   '// 列の追加 //
  14.   sql = "alter Table " & Tname & " ADD alterColumn BYTE "
  15.   rs.Open sql, cn
  16.   '// 列属性の変更 //
  17.   sql = "alter Table " & Tname & " ALTER COLUMN alterColumn long "
  18.   rs.Open sql, cn
  19.   '// 列の削除 //
  20.   sql = "alter Table " & Tname & " DROP alterColumn "
  21.   rs.Open sql, cn
  22.  cn.Close
  23.  Set rs = Nothing
  24.  Set cn = Nothing
  25. End Sub
図4-30

299行目「Set cn = CreateObject("ADODB.Connection")」でConnectionオブジェクトを生成し、300行目「Set rs = CreateObject("ADODB.Recordset")」でRecordsetオブジェクトを生成します。
302行目「cn.Provider = "Microsoft.ACE.OLEDB.12.0"」でプロバイダを設定し、303行目「cn.Open DBpath & DBname」でデータベースに接続します。

「列の追加」の時には、306行目「sql = "alter Table " & Tname & " ADD alterColumn BYTE "」のように
Alter Table テーブル名 Add 追加する列名 データ型
というSQL文にします。

「列属性の変更」の時には、310行目「sql = "alter Table " & Tname & " ALTER COLUMN alterColumn long "」のように
Alter Table テーブル名 ALTER 修正する列名 新たなデータ型
というSQL文にします。ここではデータ型をByte型→Long型に変更しています。

「列の削除」の時には、314行目「sql = "alter Table " & Tname & " DROP alterColumn "」のように
Alter Table テーブル名 DROP 削除する列名
というSQL文にします。

各SQL文の下の行の「rs.Open sql, cn」でRecordsetをOpenさせてSQL文を実行します。
テーブルが無かったり使用中だったりした時のエラー処理は省略しています。必要に応じて付けて下さい。

5.データベース制約の設定と解除

データベースの制約には、主として以下のようなものがあります。
種類内容Accessでの設定可否
Check(検査制約)列のデータ値が特定の条件を満たすか否かを検査△(Column Validation Ruleで設定可)
NOT NULL(非ヌル制約)列のデータがNullで無いことを保証
UNIQUE(一意性制約)列のデータが他の行と重複しないことを保証
PRIMARY KEY(主キー制約)主キーの列に設定。(=「UNIQUE」+「NOT NULL」)
FOREIGN KEY(参照制約)列のデータが他のテーブルのデータを参照して一致することを保証
図5-1

Accessとして「Check制約」という名前は使っていないようですが、プロパティ「Jet OLEDB:Column Validation Rule」を使えば同様の機能を得ることが可能です。
ADOによるテーブルの作成」でも説明しましたが、ADOを使った場合「NOT NULL」制約はテーブルを新規作成する段階での設定が必要です(KeyやIndexを使った後付け設定も出来そうな気はするのですが、詳しくは調べていません)。
但しSQLを使えば、Alter Tableを使って「後付けでNot Nullの設定」が可能となります。

5-1.ADOXによる制約設設定方法

ADOXを使ったデータベース制約の設定を説明するために、図5-2で2つのテーブル(KeyTypeA、KeyTypeB)を作成し、その後の図5-4で制約の設定をします。但しNOT NULLはテーブル作成時の設定が必要なために図5-2側で行います。
サンプルファイルを実行するのであれば、図5-2のあとで図5-4を実行して下さい。
  1. '========== ⇩(10) 2つのテーブル作成 ============
  2. Sub ADOmakeKey1()
  3.  Const DBname As String = "it-081.accdb"
  4.  Dim DBpath As String: DBpath = ThisWorkbook.Path & "¥"
  5.  Const TnameA As String = "KeyTypeA"
  6.  Const TnameB As String = "KeyTypeB"
  7.  Dim Cat As Object    '←Catalogオブジェクト
  8.  Dim TBL1 As Object   '←Tableオブジェクト(KeyTypeA用)
  9.  Dim TBL2 As Object   '←Tableオブジェクト(KeyTypeB用)
  10.  Set Cat = CreateObject("ADOX.Catalog")
  11.  Set TBL1 = CreateObject("ADOX.Table")
  12.  Set TBL2 = CreateObject("ADOX.Table")
  13.  Cat.ActiveConnection = "Provider=Microsoft.Ace.OLEDB.12.0;" & _
  14.              "Data Source=" & DBpath & DBname
  15.  TBL1.Name = TnameA
  16.  Set TBL1.ParentCatalog = Cat
  17.  With TBL1
  18.   .Columns.Append "数値整数", adSmallInt
  19.   .Columns("数値整数").Attributes = adColNullable + adColFixed
  20.   .Columns.Append "数値長整数", adInteger
  21.   .Columns("数値長整数").Attributes = adColNullable + adColFixed
  22.  End With
  23.  Cat.Tables.Append TBL1
  24. '============================
  25.  TBL2.Name = TnameB
  26.  Set TBL2.ParentCatalog = Cat
  27.  With TBL2
  28.   .Columns.Append "短いテキスト", adVarWChar
  29.    .Columns("短いテキスト").Properties("Nullable") = False   '←Not Nullの設定
  30.   .Columns.Append "長いテキスト", adLongVarWChar
  31.    .Columns("長いテキスト").Attributes = adColNullable
  32.   .Columns.Append "数値バイト", adUnsignedTinyInt
  33.    .Columns("数値バイト").Attributes = adColNullable + adColFixed
  34.   .Columns.Append "数値整数", adSmallInt
  35.    .Columns("数値整数").Attributes = adColNullable + adColFixed
  36.   .Columns.Append "数値長整数", adInteger
  37.    .Columns("数値長整数").Attributes = adColNullable + adColFixed
  38.  End With
  39.  Cat.Tables.Append TBL2
  40.  Set TBL1 = Nothing
  41.  Set TBL2 = Nothing
  42.  Set Cat = Nothing
  43. End Sub
図5-2

350行目「Set Cat = CreateObject("ADOX.Catalog")」でCatalogオブジェクトを生成した後、351行目「Set TBL1 = CreateObject("ADOX.Table")」と352行目「Set TBL2 = CreateObject("ADOX.Table")」で2つのTableオブジェクトを生成しています。これは今回2つのテーブルを1つのプロシージャ内で作成している為です。
353~354行目「Cat.ActiveConnection = "Provider=Microsoft.Ace.OLEDB.12.0;" & "Data Source=" & DBpath & DBname」でデータベースファイルに接続します。

ここで作る2つのテーブルのイメージは、図5-3のようになっています。
サンプルファイルの2つのテーブル内容
図5-3

まず、356~366行目は1つ目のテーブル(KeyTypeAテーブル)の作成です。
356行目「TBL1.Name = TnameA」でテーブル名を設定し、357行目「Set TBL1.ParentCatalog = Cat」で、その所属を接続中のデータベースファイル(Cat)にしています。

359行目「With TBL1」以下でTBL1(KeyTypeAテーブル)の列を作成します。
360行目「.Columns.Append "数値整数", adSmallInt」で「数値整数」列を整数型(adSmallInt)で作成し、361行目「.Columns("数値整数").Attributes = adColNullable + adColFixed」で、その列を「Null挿入可」の状態にします。
362行目「.Columns.Append "数値長整数", adInteger」で「数値長整数」列を長整数型(adInteger)で作成し、363行目「.Columns("数値長整数").Attributes = adColNullable + adColFixed」で、その列を「Null挿入可」の状態にします。

366行目「Cat.Tables.Append TBL1」で、テーブル(KeyTypeAテーブル)をデータベース上に作成します。

次に、369~385行目は2つ目のテーブル(テーブル名=KeyTypeB)の作成です。
369行目「TBL2.Name = TnameB」でテーブル名を設定し、370行目「Set TBL2.ParentCatalog = Cat」で、その所属を接続中のデータベースファイル(Cat)にしています。

372行目「With TBL2」以下でTBL2(KeyTypeBテーブル)の列を作成します。
373行目「.Columns.Append "短いテキスト", adVarWChar」で「短いテキスト」列を作成し、374行目「.Columns("短いテキスト").Properties("Nullable") = False」で「Not Null」の設定(=非ヌル制約)を行っています。
374行目でNot Nullを指定しなくても、図4-6で示したように373行目だけで「Not Null状態」になっているのですが、明示的に非ヌル制約設定をしています。

375行目「.Columns.Append "長いテキスト", adLongVarWChar」で「長いテキスト」列を作成し、376行目「.Columns("長いテキスト").Attributes = adColNullable」でNull挿入可の状態にしています。
377行目「.Columns.Append "数値バイト", adUnsignedTinyInt」で「数値バイト」列を作成し、378行目「.Columns("数値バイト").Attributes = adColNullable + adColFixed」でNull挿入可の状態にしています。
379行目「.Columns.Append "数値整数", adSmallInt」で「数値整数」列を作成し、380行目「.Columns("数値整数").Attributes = adColNullable + adColFixed」でNull挿入可の状態にしています。
381行目「.Columns.Append "数値長整数", adInteger」で「数値長整数」列を作成し、382行目「.Columns("数値長整数").Attributes = adColNullable + adColFixed」でNull挿入可の状態にしています。

385行目「Cat.Tables.Append TBL2」で、テーブル(KeyTypeBテーブル)をデータベース上に作成します。

これで、KeyTypeBテーブルの短いテキスト列のみ「Not Null制約」を設定し、残りはKeyTypeAの全列を含めてNull許容の状態になりました。
この状態から、各列に残りの制約を設定していくのが、図5-4です。
  1. '========== ⇩(11) ADOによるデータベース制約の設定 ============
  2. Sub ADOmakeKey2()
  3.  Const DBname As String = "it-081.accdb"
  4.  Dim DBpath As String: DBpath = ThisWorkbook.Path & "¥"
  5.  Const TnameA As String = "KeyTypeA"
  6.  Const TnameB As String = "KeyTypeB"
  7.  Dim Cat As Object    '←Catalogオブジェクト
  8.  Set Cat = CreateObject("ADOX.Catalog")
  9.  Cat.ActiveConnection = "Provider=Microsoft.Ace.OLEDB.12.0;" & _
  10.              "Data Source=" & DBpath & DBname
  11.  With Cat.Tables(TnameA)
  12.   .Keys.Append "key1", adKeyPrimary, "数値長整数"
  13.  End With
  14.  With Cat.Tables(TnameB)
  15.   .Columns("長いテキスト").Properties("Jet OLEDB:Column Validation Rule") = "<>'abc'"
  16.   .Columns("長いテキスト").Properties("Jet OLEDB:Column Validation Text") = "違反です"
  17.   .Keys.Append "key2", adKeyUnique, "数値バイト"
  18.   .Keys.Append "key3", adKeyPrimary, "数値整数"
  19.   .Keys.Append "key4", adKeyForeign, "数値長整数", TnameA, "数値長整数"
  20.  End With
  21.  Set Cat = Nothing
  22. End Sub
図5-4

408行目でCatalogオブジェクトを生成し、409~410行目でデータベースファイルに接続します。
413行目「.Keys.Append "key1", adKeyPrimary, "数値長整数"」は、KeyTypeAテーブル(TnameA)の「数値長整数」列に対して主キー制約を設定しています。ADOで主キー設定をするには、キーの型を図5-5の表の中の定数adKeyPrimaryを選びます。
 テーブル.Keys.Append キーの名前 , キーの型 , 列名 (主キー・一意性制約)
定数内容
adKeyPrimary1主キー制約(既定値)
adKeyForeign2参照制約
adKeyUnique3一意性制約
図5-5

416~423行目ではKeyTypeBテーブル(TnameB)に対して制約設定をしています。
417行目「.Columns("長いテキスト").Properties("Jet OLEDB:Column Validation Rule") = "<>'abc'"」では、「長いテキスト」列のPropertiesの9番目(図4-5参照)に「入力規制の式」として「<>'abc'」を設定しています。これは「文字列として 'abc' 以外を入力しなさい」という意味です。
そして、その規制に引っ掛かった(='abc' を入力した)場合に出力する文字をPropertiesの8番目に設定します。ここでは418行目「.Columns("長いテキスト").Properties("Jet OLEDB:Column Validation Text") = "違反です"」で「違反です」との文字列が出力されるようにしています。

なお違反した時の文字列を取得するには、データを行挿入するコード(今回だと「cn.Execute sql」など)の前に「On Error Resume Next」でエラーが出てもスルーさせるようにしておき、エラーが発生していたら「MsgBox Err.Description」とエラー内容の説明文字列をメッセージ出力させます(図5-6の左側)。
なお出力文字列を設定しない場合は、図5-6の右側のように詳しいエラー内容の説明文が表示されることになりますが、テーブル構造(テーブル名、列名、規制内容)が丸見えになってしまいますので、もし設定する場合は適切なエラー内容を出力させるようにして下さい。
入力規制に違反した際のメッセージ
図5-6

420行目「.Keys.Append "key2", adKeyUnique, "数値バイト"」は「数値バイト」列にUNIQUE規制を設定しています。このUniqueは「他の行のデータと重複しない」という意味ですが、Null値は許容していますので注意が必要です。
421行目「.Keys.Append "key3", adKeyPrimary, "数値整数"」は「数値整数」列を主キー(PRIMARY KEY)に設定しています。主キーの列は「Not Null」+「Unique」ですので、「他の行のデータと重複せず、Nullも含まない」列となり、「テーブル内でレコードを一意に識別」することができます。

422行目「.Keys.Append "key4", adKeyForeign, "数値長整数", TnameA, "数値長整数"」は、「KeyTypeBの数値長整数行」と「KeyTypeAの数値長整数行」の間に参照制約を設定しています。図5-3で、KeyTypeAテーブルとKeyTypeBテーブルを結びつけている「青い線」の部分になります。
 テーブル.Keys.Append キーの名前 , adKeyForeign , キー作成側の列名 , 関連テーブル名 , 関連テーブル側の列名 (参照制約)

ここで「関連テーブル側の列」は、キー作成側の列と「同じデータ型」で、且つ「Primary Key 相当」である必要があります。但し「キー作成側の列」はNot Null やUniqueである必要はありません。

ここで設定しているデータベース制約の記録は「テーブル内のKeyオブジェクト」のプロパティとして保存されます。プロパティは図5-7のように6つあります。
Keyのプロパティ
プロパティ内容
Columns対象の列
DeleteRule主キーが削除された場合の動作
Nameキー名
RelatedTable環形テーブルの名前
Typeキーの種類(主キー、参照キー、一意キー)
UpdateRule主キーが更新された場合の動作
図5-7

図5-4で設定した4つのKey(Key1~Key4)が、どこにどのように書き込まれているかを表したのが図5-8です。
Colomnsプロパティの中は更に分かれているので、その内の「列名(Name)」と「関連する列(RelatedColumn)」のみを図5-8では示しています。
TableKeyTypeAKeyTypeB
Keyの
プロパティ
1
Primary
2
Unique
3
Primary
4
Foreign
Columns().Name"数値長整数""数値バイト""数値整数""数値長整数"
Columns.RelatedColumn"""""""数値長整数"
DeleteRuleadRlNoneadRlNoneadRlNoneadRlNone
Name"Key1""Key2""Key3""Key4"
RelatedTable"""""""KeyTypeA"
TypeadKeyPrimaryadKeyUniqueadKeyPrimaryadKeyForeign
UpdateRuleadRlNoneadRlNoneadRlNoneadRlNone
図5-8

図5-8の1番目は、413行目「.Keys.Append "key1", adKeyPrimary, "数値長整数"」を実行した結果です。「Key1」というキー名で、Primary制約を設定していますので、TypeプロパティにはadKeyPrimaryという値が設定されています。
2番目は420行目「.Keys.Append "key2", adKeyUnique, "数値バイト"」でUnique制約を、3番目は421行目「.Keys.Append "key3", adKeyPrimary, "数値整数"」でPrimary制約をしています。
そして4番目は422行目「.Keys.Append "key4", adKeyForeign, "数値長整数", TnameA, "数値長整数"」で、Foreign制約のため、TypeプロパティにはadKeyForeign値が設定され、且つその関連先の情報を「相手先のテーブル名」はRelatedTableプロパティに、「相手先の列名」はColumnsプロパティの中のRelatedColumnに設定されています。

なお「インデックスの設定と解除」でも説明しますが、設定した制約は、Keyオブジェクトのみに記録されるのでは無く、Indexオブジェクトのプロパティにも記録されている様です。図5-4で設定した後のIndexの各プロパティ値を図5-9に示します。
TableKeyTypeAKeyTypeB
Indexの
プロパティ
1
Primary
2
Unique
3
Primary
4
Foreign
ClusteredFalseFalseFalseFalse
Columns().Name"数値長整数""数値バイト""数値整数""数値長整数"
IndexNullsadIndexNullsDisallowadIndexNullsAllowadIndexNullsDisallowadIndexNullsAllow
Name"Key1""Key2""Key3""Key4"
PrimaryKeyTrueFalseTrueFalse
Properties----
UniqueTrueTrueTrueFalse
図5-9

1番目と3番目はPrimary制約ですので、PrimaryKeyプロパティがTrueになります。またPrimaryとは「Unique」+「Not Null」ですので、UniqueプロパティがTrueに、IndexNullsプロパティがadIndexNullsDisallow(Null値入力を許可しない:図5-10参照)となります。
2番目はUnique制約ですので、UniqueプロパティがTrueになります。UniqueではNullは許しますので、IndexNullsはadIndexNullsAllow(Null挿入を許可:図5-10参照)となります。
4番目はForeign制約ですが、その列(KeyTypeBの数値長整数列)はPrimary、Not Null、Uniqueのどれにも当てはまらないため、図5-9のような値となります。
定数内容
adIndexNullsAllow0Null値入力を許可
adIndexNullsDisallow1Null値入力を許可しない(既定値)
adIndexNullsIgnore2Null値はインデックスに含めない
adIndexNullsIgnoreAny4複数列キーの場合、Null値はインデックスに含めない
図5-10

5-2.SQLによる制約設定方法

5-2-1.Alter Tableで後付け設定する方法

SQLにより制約を設定するのが、図5-11です。ADOXの時と同様に2つのテーブル(KeyTypeA、KeyTypeB)を作成した後、制約設定をしています。なお、Not NullもAlter Tableで「列修正に合わせてNot Null設定可」ですが、ここではADOX時と同様にCreate Table時に設定をしています。
  1. '========== ⇩(12) SQLによるデータベース制約の設定1 ============
  2. Sub SQLmakeKey1()
  3.  Dim DBpath As String: DBpath = ThisWorkbook.Path & "¥"
  4.  Const DBname As String = "it-081.accdb"
  5.  Const TnameA As String = "KeyTypeA"
  6.  Const TnameB As String = "KeyTypeB"
  7.  Dim sql As String    '←実行するSQL文
  8.  Dim cn As Object    '←Connectionオブジェクト
  9.  Dim rs As Object    '←Recordsetオブジェクト
  10.  Set cn = CreateObject("ADODB.Connection")
  11.  Set rs = CreateObject("ADODB.Recordset")
  12.  cn.Open "Provider=Microsoft.ACE.OLEDB.12.0;" & _
  13.           "Data Source=" & DBpath & DBname
  14.  sql = "Create Table " & TnameA & " (" & _
  15.      "数値整数 SHORT ," & _
  16.      "数値長整数 LONG" & _
  17.      ")"
  18.  rs.Open sql, cn
  19.  sql = "Create Table " & TnameB & " (" & _
  20.      "短いテキスト ALPHANUMERIC NOT NULL," & _
  21.      "長いテキスト LONGTEXT," & _
  22.      "数値バイト BYTE ," & _
  23.      "数値整数 SHORT," & _
  24.      "数値長整数 LONG" & _
  25.      ")"
  26.  rs.Open sql, cn
  27. '=============================
  28.  sql = "Alter Table " & TnameA & _
  29.      " ADD CONSTRAINT key1 Primary Key(数値長整数)"
  30.  rs.Open sql, cn
  31.  sql = "Alter Table " & TnameB & _
  32.      " ADD CONSTRAINT key2 Unique(数値バイト)"
  33.  rs.Open sql, cn
  34.  sql = "Alter Table " & TnameB & _
  35.      " ADD CONSTRAINT key3 Primary Key(数値整数)"
  36.  rs.Open sql, cn
  37.  sql = "Alter Table " & TnameB & _
  38.      " ADD CONSTRAINT key4 Foreign Key(数値長整数) " & _
  39.      " References " & TnameA & " (数値長整数)"
  40.  rs.Open sql, cn
  41.  cn.Close
  42.  Set rs = Nothing
  43.  Set cn = Nothing
  44. End Sub
図5-11

450~451行目で、ConnectionオブジェクトとRecordsetオブジェクトを生成し、453~454行目でデータベースファイルと接続します。それ以降456~469行目で、第一段階として「KeyTypeAテーブルとKeyTypeBテーブルを作成」し、473~488行目で第二段階として「Keyを追加」します。

456~459行目のSQL文は、2つの列(数値整数、数値長整数)を持つテーブルを作成するものです。
456行目「sql = "Create Table " & TnameA & " (" & _ 」は、TnameA(="KeyTypeA")という名のテーブルを作ります。
457行目「"数値整数 SHORT ," & _ 」で「数値整数」という名の列をSHORT型(ADOXだとadSmallInt型)で指定します。
458行目「"数値長整数 LONG" & _ 」で「数値長整数」という名の列をLONG型(ADOXだとadInteger型)で指定します。
460行目「rs.Open sql, cn」では、そのSQL文を実行し「KeyTypeAテーブル」を作成します。

462~468行目のSQL文は、5つの列を持つテーブルを作成するものです。
462行目「sql = "Create Table " & TnameB & " (" & _ 」は、TnameB(="KeyTypeB")という名のテーブルを作ります。
463行目「"短いテキスト ALPHANUMERIC NOT NULL," & _ 」で「短いテキスト」という名の列をALPHANUMERIC型(ADOXだとadVarWChar型)で指定します。この時 Not Null制約も付加し、Null値を挿入できないようにします。
464行目「"長いテキスト LONGTEXT," & _ 」で「長いテキスト」という名の列をLONGTEXT型(ADOXだとadLongVarWChar型)で指定します。
465行目「"数値バイト BYTE ," & _ 」で「数値バイト」という名の列をBYTE型(ADOXだとadUnsignedTinyInt型)で指定します。
466行目「"数値整数 SHORT," & _ 」で「数値整数」という名の列をSHORT型(ADOXだとadSmallInt型)で指定します。
467行目「"数値長整数 LONG" & _ 」で「数値長整数」という名の列をLONG型(ADOXだとadInteger型)で指定します。
469行目「rs.Open sql, cn」では、そのSQL文を実行し「KeyTypeBテーブル」を作成します。

473行目以降は、上記で作った2つのテーブル(KeyTypeA、KeyTypeB)に対して、キーを設定していきます。キーを設定する構文は以下の様になります。
 「 Alter Table テーブル名 Add Constraint キー名 制約(列名)
制約の部分には図5-12を指定します。但し、Not NullはExcel側からは実行不可(エラーが出る)なので、列のプロパティで設定するしか手がありません(Access上では実行可能かもしれません)。
また参照制約の場合は、制約(列名)に続けて関連するテーブルと列を指定する必要があるため、以下のような構文になります。
 「 Alter Table テーブル名 Add Constraint キー名 Foreign Key(列名) References テーブル名(列名)
制約内容
Primary Key主キー
Unique一意
Not Null非ヌル。Excelからは実行不可
Foreign Key参照。+References テーブル名(列名)
図5-12

473行目「sql = "Alter Table " & TnameA & _ 」でKeyTypeAテーブルを対象に、474行目「" ADD CONSTRAINT key1 Primary Key(数値長整数)" 」で「数値長整数」列に対して「Primary Key」をKey設定します。Key値は「key1」とします。
SQLでは、列に対して制約を設定する場合は「制約(列名)」という形で指定します。
475行目「rs.Open sql, cn」で、そのSQL文を実行し「KeyTypeAテーブル」にKey1を追加します。

477行目「sql = "Alter Table " & TnameB & _ 」でKeyTypeBテーブルを対象に、478行目「" ADD CONSTRAINT key2 Unique(数値バイト)" 」で「数値バイト」列に対して「Unique」をKey設定します。Key値は「key2」とします。
479行目「rs.Open sql, cn」で、そのSQL文を実行し「KeyTypeBテーブル」にKey2を追加します。

481行目「sql = "Alter Table " & TnameB & _ 」でKeyTypeBテーブルを対象に、482行目「" ADD CONSTRAINT key3 Primary Key(数値整数)" 」で「数値整数」列に対して「Primary Key」をKey設定します。Key値は「key3」とします。
483行目「rs.Open sql, cn」で、そのSQL文を実行し「KeyTypeBテーブル」にKey3を追加します。

485行目「sql = "Alter Table " & TnameB & _ 」でKeyTypeBテーブルを対象に、486行目「" ADD CONSTRAINT key4 Foreign Key(数値長整数) " & _ 」で「数値長整数」列に対して「Foreign Key」をKey設定します。Key値は「key4」とします。
そして、その関連するテーブル・列を487行目「" References " & TnameA & " (数値長整数)" 」で指定します。指定の方法は、「References」の後に「テーブル名(列名)」という形にします。
488行目「rs.Open sql, cn」で、そのSQL文を実行し「KeyTypeBテーブル」にKey4を追加します。

なお、ADOX側の図5-4の417~418行目で設定したような「"長いテキスト"列へのCheck制約」は、SQL文では実現できないようです。Microsoftのサイト等では「sql = "Alter Table " & TnameB & " ADD CONSTRAINT key5 CHECK (長いテキスト <> 'abc') "」とすればCheck制約の設定は可能 との様に読み取れるのですが、エラーが出ないのにKeyへの登録はされません。
もしCheck制約を設定したい場合は、ADOXを使用した図5-4の「直接プロパティ値に書き込む」方法が良さそうです。

この図5-11では Key1~Key4の4つのKeyを設定していますが、それらの情報はADOXの時と同様に、図5-8(Keyオブジェクト)と図5-9(Indexオブジェクト)に保存されています。

5-2-2.Create Table時に併行して設定する方法

制約を図5-11のように後付け設定するのもOKですが、SQLを使うのであれば図5-13のように「テーブル作成(Create Table)時に併せて設定」する方が簡単です。構文としては以下になります。
 「Create Table テーブル名 ( 列名 データ型 制約 , ・・・)
なお参照制約については、通常の列作成に続けて下記のように指定します。
 「Create Table テーブル名 ( ・・・ , 列名 データ型 , Foreign Key(列名) 関連テーブル名(関連テーブルの列名) )」
  1. '========== ⇩(13) SQLによるデータベース制約の設定2 ============
  2. Sub SQLmakeKey2()
  3.  Dim DBpath As String: DBpath = ThisWorkbook.Path & "¥"
  4.  Const DBname As String = "it-081.accdb"
  5.  Const TnameA As String = "KeyTypeA"
  6.  Const TnameB As String = "KeyTypeB"
  7.  Dim sql As String    '←実行するSQL文
  8.  Dim cn As Object    '←Connectionオブジェクト
  9.  Dim rs As Object    '←Recordsetオブジェクト
  10.  Set cn = CreateObject("ADODB.Connection")
  11.  Set rs = CreateObject("ADODB.Recordset")
  12.  cn.Open "Provider=Microsoft.ACE.OLEDB.12.0;" & _
  13.        "Data Source=" & DBpath & DBname
  14.  sql = "Create Table " & TnameA & " (" & _
  15.       "数値整数 SHORT ," & _
  16.       "数値長整数 LONG PRIMARY KEY" & _
  17.       ")"
  18.  rs.Open sql, cn
  19.  sql = "Create Table " & TnameB & " (" & _
  20.       "短いテキスト ALPHANUMERIC NOT NULL," & _
  21.       "長いテキスト LONGTEXT," & _
  22.       "数値バイト BYTE UNIQUE," & _
  23.       "数値整数 SHORT PRIMARY KEY," & _
  24.       "数値長整数 LONG," & _
  25.       "FOREIGN KEY(数値長整数) REFERENCES " & TnameA & " (数値長整数) " & _
  26.       ")"
  27.  rs.Open sql, cn
  28.  cn.Close
  29.  Set rs = Nothing
  30.  Set cn = Nothing
  31. End Sub
図5-13

520~521行目で、ConnectionオブジェクトとRecordsetオブジェクトを生成し、523~524行目でデータベースファイルと接続します。

526~529行目では「KeyTypeAテーブルを制約付きで作成」しています。
526行目「sql = "Create Table " & TnameA & " (" & _ 」で、TnameA(="KeyTypeA")という名のテーブルを作ります。
527行目「"数値整数 SHORT ," & _ 」で「数値整数」という名の列をSHORT型(ADOXだとadSmallInt型)で指定します。
528行目「"数値長整数 LONG PRIMARY KEY" & _ 」で「数値長整数」という名の列をLONG型(ADOXだとadInteger型)で指定し、その列をPrimary Keyに指定しています。
530行目「rs.Open sql, cn」では、そのSQL文を実行し「KeyTypeAテーブル」を作成します。

532~540行目のSQL文は「KeyTypeBテーブルを制約付きで作成」しています。
532行目「sql = "Create Table " & TnameB & " (" & _ 」は、TnameB(="KeyTypeB")という名のテーブルを作ります。
533行目「"短いテキスト ALPHANUMERIC NOT NULL," & _ 」で「短いテキスト」という名の列をALPHANUMERIC型(ADOXだとadVarWChar型)で指定します。この時 Not Null制約も付加し、Null値を挿入できないようにします。

534行目「"長いテキスト LONGTEXT," & _ 」で「長いテキスト」という名の列をLONGTEXT型(ADOXだとadLongVarWChar型)で指定します。この列はADOXではCheck制約を付けており、もしSQLで設定するとすれば「"長いテキスト LONGTEXT Check (長いテキスト <> 'abc')," & _ 」としたり、SQL文の最後に「" CHECK (長いテキスト <> 'abc') " & _」を付けたりするのでしょうが、今回環境ではどちらも有効にはなりません(列のデータ型の後にCheckを付けると、エラーとなります)。
ですので、今回は(ADOX側とテーブルの形を合わせるために)通常の列を作るだけとしました。

535行目「"数値バイト BYTE UNIQUE," & _ 」で「数値バイト」という名の列をBYTE型(ADOXだとadUnsignedTinyInt型)で指定し、その列をUniqueに指定しています。
536行目「"数値整数 SHORT PRIMARY KEY," & _ 」で「数値整数」という名の列をSHORT型(ADOXだとadSmallInt型)で指定し、その列をPrimary Keyに指定しています。
537行目「"数値長整数 LONG" & _ 」で「数値長整数」という名の列をLONG型(ADOXだとadInteger型)で指定します。その列の制約として、538行目「"FOREIGN KEY(数値長整数) REFERENCES " & TnameA & " (数値長整数) " & _ 」で「数値長整数」列に対して「Foreign Key」をKey設定し、その関連するテーブル(KeyTypeA)・列(数値長整数)を指定します。
最後に540行目「rs.Open sql, cn」では、そのSQL文を実行し「KeyTypeBテーブル」を作成します。

この図5-13で作成されたテーブルの制約情報は、図5-8(Keyオブジェクト)と図5-9(Indexオブジェクト)の様にに保存されています。但し図5-13は「Key名」を指定しない方法ですので、「Key.Name」や「Index.Name」にはKey1~Key4の様な分かり易いキー名の代わりに「Index_・・・」や「Rel_・・・」等のランダムな名前が付きます。ですので、後からKeyを削除する事が考えられるのであれば、図5-11のように「Key名」を指定した制約設定の方が良いかもしれません。

5-3.ADOXよる制約解除方法

図5-4で設定したデータベース制約を解除するのが図5-14です。構文としては以下になります。
 「Catalogオブジェクト.Tables(テーブル名).Keys.Delete キー名
  1. '========== ⇩(14) ADOXよる制約解除 ============
  2. Sub ADOdeleteKey()
  3.  Const DBname As String = "it-081.accdb"
  4.  Dim DBpath As String: DBpath = ThisWorkbook.Path & "¥"
  5.  Const TnameA As String = "KeyTypeA"
  6.  Const TnameB As String = "KeyTypeB"
  7.  Dim Cat As Object    '←Catalogオブジェクト
  8.  Set Cat = CreateObject("ADOX.Catalog")
  9.  Cat.ActiveConnection = "Provider=Microsoft.Ace.OLEDB.12.0;" & _
  10.               "Data Source=" & DBpath & DBname
  11.  With Cat.Tables(TnameB)
  12.   .Columns("長いテキスト").Properties("Jet OLEDB:Column Validation Rule") = ""
  13.   .Columns("長いテキスト").Properties("Jet OLEDB:Column Validation Text") = ""
  14.   .Keys.Delete "Key4"   '← Foreign Keyの解除
  15.   .Keys.Delete "Key3"   '← Primary Keyの解除
  16.   .Keys.Delete "Key2"   '← Uniqueの解除
  17.  End With
  18.  With Cat.Tables(TnameA)
  19.   .Keys.Delete "Key1"   '← Primary Keyの解除
  20.  End With
  21.  Set Cat = Nothing
  22. End Sub
図5-14

568行目でCatalogオブジェクトを生成し、569~570行目でデータベースファイルに接続します。
572行目以降で制約を解除するのですが、解除する順番には注意が必要です。図5-3でも分かるように、KeyTypeAとKeyTypeBを結びつけている「Foreign Key」は、KeyTypeBから「KeyTypeAのPrimary Keyである列」につなげています。結んでいる矢印の先は「Primary Key」である必要があります。
ですので、KeyTypeBのForeign Keyを解除する前にKeyTypeAのPrimary Keyを解除してしまうと、エラーが発生してしまいます。

そのため、ここでは設定の時(図5-4)とは逆の順序「KeyTypeB → KeyTypeA」で解除していきます。
まずKeyTypeBです。まず図5-4の417~418行目で設定したCheck制約相当を解除します。
573行目「.Columns("長いテキスト").Properties("Jet OLEDB:Column Validation Rule") = ""」では、「入力規制の式(元は、"<>'abc'")」を空欄に戻しています。
574行目「.Columns("長いテキスト").Properties("Jet OLEDB:Column Validation Text") = ""」では、入力規制に引っ掛かった時に出力する文字列(元は、"違反です")を空欄にしています。574行目はそのままでも問題無いかもしれませんが、入力規制の式とセットと考えて削除しています。

576行目「.Keys.Delete "Key4"」、577行目「.Keys.Delete "Key3"」、578行目「.Keys.Delete "Key2"」と逆の順番に削除しています。今回はKeyTypeB側の削除処理さえ先に行えば問題ありませんが、同じテーブル内での処理順序が関係ある場合も考えてテーブル内でも逆順で処理をしています。
KeyTypeB側の削除処理が終了したら、582行目「.Keys.Delete "Key1"」でKeyTypeA側の削除処理をします。

5-4.SQLよる制約解除方法

5-4-1.Key名が既知の場合の制約解除

図5-11のように「Key名をCONSTRAINTで設定して制約設定」した場合は、図5-15のような手順で制約解除します。削除するKey名が分かっている訳ですから解除も容易です。
  1. '========== ⇩(15) SQLよる制約解除(Key名を指定して制約設定した場合) ============
  2. Sub SQLdeleteKey1()
  3.  Dim DBpath As String: DBpath = ThisWorkbook.Path & "¥"
  4.  Const DBname As String = "it-081.accdb"
  5.  Const TnameA As String = "KeyTypeA"
  6.  Const TnameB As String = "KeyTypeB"
  7.  Dim sql As String    '←SQL文
  8.  Dim cn As Object    '←Connectionオブジェクト
  9.  Dim rs As Object    '←Recordsetオブジェクト
  10.  Set cn = CreateObject("ADODB.Connection")
  11.  Set rs = CreateObject("ADODB.Recordset")
  12.  cn.Open "Provider=Microsoft.ACE.OLEDB.12.0;" & _
  13.       "Data Source=" & DBpath & DBname
  14.  sql = "Alter Table " & TnameB & _
  15.     " DROP CONSTRAINT key4 "   '← Foreign Keyの解除
  16.  rs.Open sql, cn
  17.  sql = "Alter Table " & TnameB & _
  18.     " DROP CONSTRAINT key3 "   '← Primary Keyの解除
  19.  rs.Open sql, cn
  20.  sql = "Alter Table " & TnameB & _
  21.     " DROP CONSTRAINT key2 "   '← Uniqueの解除
  22.  rs.Open sql, cn
  23.  sql = "Alter Table " & TnameA & _
  24.     " DROP CONSTRAINT key1 "   '← Primary Keyの解除
  25.  rs.Open sql, cn
  26.  cn.Close
  27.  Set rs = Nothing
  28.  Set cn = Nothing
  29. End Sub
図5-15

610~611行目で、ConnectionオブジェクトとRecordsetオブジェクトを生成し、613~614行目でデータベースファイルと接続します。
616行目以降で、1つずつ制約を解除していきますが、その解除する順序はADOX(図5-14)の時と同様に「設定とは逆の順番」で行っていきます。

解除の構文は以下の通りです。
 「Alter Table テーブル名 Drop Constraint 解除するキー名
616~617行目「sql = "Alter Table " & TnameB & " DROP CONSTRAINT key4 " 」ではKeyTypeBテーブル上の制約Key4を削除(Drop)します。618行目「rs.Open sql, cn」で制約解除を実行しています。
620~621行目「sql = "Alter Table " & TnameB & " DROP CONSTRAINT key3 " 」ではKeyTypeBテーブル上の制約Key3を削除します。
624~625行目「sql = "Alter Table " & TnameB & " DROP CONSTRAINT key2 " 」ではKeyTypeBテーブル上の制約Key2を削除します。

628~629行目「sql = "Alter Table " & TnameA & " DROP CONSTRAINT key1 " 」では、今度はKeyTypeAテーブル上の制約Key1を削除します。

5-4-2.Key名が未知の場合の制約解除

図5-13のように「CONSTRAINTを使用せずに制約設定」した場合は「削除するKey名が未知」です。この場合の解除方法として書籍やサイトでは「sql = "Alter Table " & TnameA & " DROP Primary key"」や「sql = "Alter Table " & TnameA & " DROP Unique(数値バイト)"」等と書いてありますが、残念ながら今回は受け付けてくれません(Accessがインストールされている環境であれば別かもしれません)。
そこで今回は「ADOXで未知のKey名を取得し、CONSTRAINTを使って制約を削除」します(図5-16)。
  1. '========== ⇩(16) Key名を指定せずに制約設定した場合 ============
  2. Sub SQLdeleteKey2()
  3.  Dim DBpath As String: DBpath = ThisWorkbook.Path & "¥"
  4.  Const DBname As String = "it-081.accdb"
  5.  Const TnameA As String = "KeyTypeA"
  6.  Const TnameB As String = "KeyTypeB"
  7.  Dim sql As String    '←SQL文
  8.  Dim cn As Object    '←Connectionオブジェクト
  9.  Dim rs As Object    '←Recordsetオブジェクト
  10.  Dim Cat As Object    '←Catalogオブジェクト
  11.  Dim i As Integer    '←カウンタ変数(Keyの数)
  12.  Dim t As Variant    '←テーブル名
  13.  Set Cat = CreateObject("ADOX.Catalog")
  14.  Set cn = CreateObject("ADODB.Connection")
  15.  Set rs = CreateObject("ADODB.Recordset")
  16.  Cat.ActiveConnection = "Provider=Microsoft.ACE.OLEDB.12.0;" & _
  17.               "Data Source=" & DBpath & DBname
  18.  cn.Open Cat.ActiveConnection
  19.  For Each t In Array(TnameB, TnameA)
  20.   With Cat.Tables(t)
  21.    For i = .Keys.Count - 1 To 0 Step -1
  22.     sql = "ALTER TABLE " & t & _
  23.        " DROP CONSTRAINT " & .Keys(i).Name
  24.     rs.Open sql, cn
  25.    Next i
  26.   End With
  27.  Next t
  28.  cn.Close
  29.  Set rs = Nothing
  30.  Set cn = Nothing
  31. End Sub
図5-16

663行目「Set Cat = CreateObject("ADOX.Catalog")」で、ADOXのCatalogを生成します。このCatalogオブジェクトは「Key名を取得」するために使用します。
665~666行目で、ConnectionオブジェクトとRecordsetオブジェクトを生成し、668~669行目でCatalogオブジェクトをデータベースファイルと接続します。 そして671行目「cn.Open Cat.ActiveConnection」で、Connectionオブジェクトとしても接続をします。こちらは、SQL文を実行するために使用します。

673行目「For Each t In Array(TnameB, TnameA)」では、テーブル名をTnameB → TnameA の順(=設定時とは逆の順序)に変数tに代入しています。For Eachで呼ばれる順番に保証は無く、もし TnameA → TnameB の順になった場合はエラーが発生してしまいますが、試したところ一応大丈夫な様です。
674行目「With Cat.Tables(t)」で、そのテーブル名を基準にして処理を進めます。
675行目「For i = .Keys.Count - 1 To 0 Step -1」では、カウンタ変数iを「テーブルのKeyの数」の分だけ回します。この時も、設定時とは逆順に回すために Step -1 とし、またKeyのインデックスはゼロ始まりのため1を差し引いています。

676~677行目「sql = "ALTER TABLE " & t & " DROP CONSTRAINT " & .Keys(i).Name」の最後の「.Keys(i).Name」は、削除対象のKeyの名前(=Key名)です。設定時にシステムが自動で割り振ったKey名をここで取得します。
あとは構文に従って「Alter Table テーブル名 Drop Constraint 解除するKey名」という形にしています。
SQL文が完成したら、678行目「rs.Open sql, cn」で実行し、Keyを削除します。

6.インデックスの作成と削除

データベーステーブルに対してインデックス(索引)を設定することで、データ検索や並べ替えを効率的に行うことが出来るようになります。インデックスの概念としては「テーブル本体とは別に、テーブルのインデックス指定列のデータを順番に並べ、同じ行にテーブル本体へのポインタ(テーブルのどの位置にあるかを示す情報)を記したテーブル」のようなものです。
検索をした列がインデックスに登録してあれば、まずインデックスのテーブルを順番に探し、見つかったらそのポインタを頼りにテーブル本体からデータを取得する、という動作をします。

6-1.インデックスの作成

6-1-1.ADOXによるインデックス作成

ADOXを使ってインデックスを作成するのが図6-1です。
  1. '========== ⇩(17) ADOXによるインデックス作成 ============
  2. Sub ADOmakeIndex()
  3.  Const DBname As String = "it-081.accdb"
  4.  Dim DBpath As String: DBpath = ThisWorkbook.Path & "¥"
  5. ' Const TnameA As String = "KeyTypeA"
  6.  Const TnameB As String = "KeyTypeB"
  7.  Dim Cat As Object    '←Catalogオブジェクト
  8.  Dim Idx As Object    '←Indexオブジェクト
  9.  Call ADOmakeKey1    '←図5-2を呼出してテーブル作成
  10.  Set Cat = CreateObject("ADOX.Catalog")
  11.  Set Idx = CreateObject("ADOX.Index")
  12.  Cat.ActiveConnection = "Provider=Microsoft.Ace.OLEDB.12.0;" & _
  13.               "Data Source=" & DBpath & DBname
  14.  With Idx
  15.   .Name = "Idx1"
  16.   .Columns.Append "長いテキスト"
  17.   .IndexNulls = adIndexNullsDisallow
  18. '  .PrimaryKey = True
  19. '  .Unique = True
  20.  End With
  21.  Cat.Tables(TnameB).Indexes.Append Idx
  22.  Set Cat = Nothing
  23.  Set Idx = Nothing
  24. End Sub
図6-1

インデックスを作成するためには、元となるテーブルが必要ですので、709行目「Call ADOmakeKey1」で図5-2を呼び出し、KeyTypeAとKeyTypeBの2つのテーブルを作成しておきます。

711行目「Set Cat = CreateObject("ADOX.Catalog")」で、Catalogオブジェクトを生成し、712行目「Set Idx = CreateObject("ADOX.Index")」ではIndexオブジェクトを生成します。
713~714行目でデータベースファイルに接続します。

716行目「With Idx」以下で、まずインデックスの中身を組み立てています。図4-21で「データベースに追加する前に、Tableの中身を組み立てる」のと同じ流れになります。
まず717行目「.Name = "Idx1"」でインデックスの名前を設定します。ここでは仮に「Idx1」としました。.Nameはもちろんプロパティで、インデックスのプロパティは図6-2のように7つあります。
(Propertiesの下には更に11個のプロパティがありますが、他サイトでも殆ど情報が無いため今回は割愛します。)
Indexのプロパティ
プロパティ内容
Clusteredテーブルのクラスター化インデックスを表すか否か
Columns対象の列
IndexNullsNull値のレコードをインデックスの対象とするか否か
Nameインデックス名
PrimaryKey主キーか否か
Properties詳細設定
Unique一意か否か
図6-2

718行目「.Columns.Append "長いテキスト"」で、「長いテキスト」列をインデックスの列に追加します。
719行目「.IndexNulls = adIndexNullsDisallow」で、「IndexNullsプロパティ」に値を設定します。このIndexNullsプロパティに入れる値は図5-10でも紹介しましたが、図6-3として再掲します。なお全く同じ表です。
定数内容
adIndexNullsAllow0Null値入力を許可
adIndexNullsDisallow1Null値入力を許可しない(既定値)
adIndexNullsIgnore2Null値はインデックスに含めない
adIndexNullsIgnoreAny4複数列キーの場合、Null値はインデックスに含めない
図6-3

720行目「.PrimaryKey = True」、721行目「.Unique = True」は見え消しにしてありますが、ここで併せて「PrimaryKey」「Unique」の設定も可能です。また図6-3のIndexNullsプロパティの設定により、「Not Null」の設定も可能ということになります。

インデックスの組み立てが完了したら、724行目「Cat.Tables(TnameB).Indexes.Append Idx」でテーブルにインデックスを追加します。

6-1-2.SQLによるインデックス作成

SQLでインデックスを作成するには「Create Index」を使用します。
  1. '========== ⇩(18) SQLによるインデックス作成 ============
  2. Sub SQLmakeIndex()
  3.  Dim DBpath As String: DBpath = ThisWorkbook.Path & "¥"
  4.  Const DBname As String = "it-081.accdb"
  5.  Const TnameB As String = "KeyTypeB"
  6.  Dim sql As String    '←SQL文
  7.  Dim cn As Object    '←Connectionオブジェクト
  8.  Dim rs As Object    '←Recordsetオブジェクト
  9.  Set cn = CreateObject("ADODB.Connection")
  10.  Set rs = CreateObject("ADODB.Recordset")
  11.  cn.Open "Provider=Microsoft.ACE.OLEDB.12.0;" & _
  12.       "Data Source=" & DBpath & DBname
  13.  sql = "Create Table " & TnameB & " (" & _
  14.        "短いテキスト ALPHANUMERIC NOT NULL," & _
  15.        "長いテキスト LONGTEXT," & _
  16.        "数値バイト BYTE ," & _
  17.        "数値整数 SHORT," & _
  18.        "数値長整数 LONG" & _
  19.        ")"
  20.  rs.Open sql, cn
  21.  sql = "Create Index Idx1 On " & TnameB & " (短いテキスト) " & _
  22.        "With DISALLOW NULL"
  23.  rs.Open sql, cn
  24.  sql = "Create Index Idx2 On " & TnameB & " (長いテキスト)"
  25.  rs.Open sql, cn
  26.  sql = "Create Unique Index Idx3 On " & TnameB & " (数値バイト)"
  27.  rs.Open sql, cn
  28.  sql = "Create Index Idx4 On " & TnameB & " (数値整数)" & _
  29.        "With Primary"
  30.  rs.Open sql, cn
  31.  sql = "Create Index Idx5 On " & TnameB & " (数値長整数)" & _
  32.        "With IGNORE NULL"
  33.  rs.Open sql, cn
  34.  cn.Close
  35.  Set rs = Nothing
  36.  Set cn = Nothing
  37. End Sub
図6-4

749~750行目で、ConnectionオブジェクトとRecordsetオブジェクトを生成し、752~753行目でデータベースファイルと接続します。755~762行目では、5列のKeyTypeBテーブルを作成します。そして作ったテーブルKeyTypeBを元にして、764行目以降でインデックスを作成します。

Create Indexの構文は以下の通りです。
 ①基 本 形「Create Index インデックス名 On テーブル名 (列名)
 ②一意併用形「Create Unique Index インデックス名 On テーブル名 (列名)
 ③制約併用形「Create Index インデックス名 On テーブル名 (列名) With 制約

764~765行目「sql = "Create Index Idx1 On " & TnameB & " (短いテキスト) " & "With DISALLOW NULL"」は③の制約併用形です。「短いテキスト」列に対してインデックスを作成し、且つその列はNull値の挿入を禁止します。つまり「Not Null」を設定したのと同等になります。
With句の後に続けられるのは、図6-5の通り3種類です。
With句 ~内容
DISALLOW NULLNull値の挿入を禁止
IGNORE NULLNull値のレコードはインデックスに含めない
PRIMARY主キーとして指定
図6-5

768行目「sql = "Create Index Idx2 On " & TnameB & " (長いテキスト)"」は、①の基本形で、「長いテキスト」列に対してインデックスを作成しています。
771行目「sql = "Create Unique Index Idx3 On " & TnameB & " (数値バイト)"」は、②の一意併用形で、「数値バイト」列に対してインデックスを作成し、且つその列をUnique制約にしています。

774~775行目「sql = "Create Index Idx4 On " & TnameB & " (数値整数)" & "With Primary"」は③制約併用形で、「数値整数」列に対してインデックスを作成し、その列をPrimary Keyにします。
778~779行目「sql = "Create Index Idx5 On " & TnameB & " (数値長整数)" & "With IGNORE NULL"」も③制約併用形で、「数値長整数」列に対してインデックスを作成し、その列はNull挿入は許すものの、インデックスにはNull値を含めないことになります。

6-2.インデックスの削除

6-2-1.ADOXによるインデックス削除

ADOXを使ってインデックスを削除するには、以下の構文を使用します。
 「Catalogオブジェクト.Tables(テーブル名).Indexes.Delete インデックス名
図6-6は、図6-1を実行してインデックス付きのテーブルを作成した後、そのインデックスを削除しています。
  1. '========== ⇩(19) ADOXによるインデックス削除 ============
  2. Sub ADOdeleteIndex()
  3.  Const DBname As String = "it-081.accdb"
  4.  Dim DBpath As String: DBpath = ThisWorkbook.Path & "¥"
  5.  Const TnameB As String = "KeyTypeB"
  6.  Dim Cat As Object
  7.  Call ADOmakeIndex   '←図6-1でインデックス付きテーブルを作成
  8.  
  9.  Set Cat = CreateObject("ADOX.Catalog")
  10.  Cat.ActiveConnection = "Provider=Microsoft.Ace.OLEDB.12.0;" & _
  11.               "Data Source=" & DBpath & DBname
  12.  Cat.Tables(TnameB).Indexes.Delete "Idx1"
  13.  Set Cat = Nothing
  14. End Sub
図6-6

797行目「Call ADOmakeIndex」で図6-1を呼び出し、インデックス付きテーブルを作成します。
799行目「Set Cat = CreateObject("ADOX.Catalog")」でCatalogオブジェクトを生成し、800~801行目「Cat.ActiveConnection = "Provider=Microsoft.Ace.OLEDB.12.0;" & "Data Source=" & DBpath & DBname」でデータベースファイルに接続します。

803行目「Cat.Tables(TnameB).Indexes.Delete "Idx1"」で、KeyTypeBテーブルのインデックス「Idx1(長いテキスト列に対して作成したインデックス名)」を削除します。

6-2-2.SQLによるインデックス削除

SQLを使ってインデックスを削除するには、以下の構文を使用します。
 「Drop Index インデックス名 On テーブル名
図6-7は、図6-4を実行してインデックス付きのテーブルを作成した後、そのインデックスを削除しています。
  1. '========== ⇩(20) SQLによるインデックス削除 ============
  2. Sub SQLdeleteIndex()
  3.  Dim DBpath As String: DBpath = ThisWorkbook.Path & "¥"
  4.  Const DBname As String = "it-081.accdb"
  5.  Const TnameB As String = "KeyTypeB"
  6.  Dim sql As String    '←SQL文
  7.  Dim cn As Object    '←Connectionオブジェクト
  8.  Dim rs As Object    '←Recordsetオブジェクト
  9.  Dim i As Integer    '←カウンタ変数(インデックスの数)
  10.  Call SQLmakeIndex    '←図6-4でインデックス付きテーブルを作成
  11.  Set cn = CreateObject("ADODB.Connection")
  12.  Set rs = CreateObject("ADODB.Recordset")
  13.  cn.Open "Provider=Microsoft.ACE.OLEDB.12.0;" & _
  14.       "Data Source=" & DBpath & DBname
  15.  For i = 5 To 1 Step -1
  16.   sql = "Drop Index Idx" & i & " On " & TnameB
  17.   rs.Open sql, cn
  18.  Next i
  19.  cn.Close
  20.  Set rs = Nothing
  21.  Set cn = Nothing
  22. End Sub
図6-7

820行目「Call SQLmakeIndex」で図6-4を呼出し、インデックス付きのテーブルを作成します。作られたインデックスは5個で、名前を Idx1~Idx5 としています。
822~823行目でConnectionオブジェクトとRecordsetオブジェクトを生成し、825~826行目でデータベースファイルに接続しています。

828行目「For i = 5 To 1 Step -1」で、カウンタ変数iを5→1へ降順で回し、1つずつインデックスを削除しています。これは「作った順番と逆の順番でインデックスを削除」するためです。
829行目「sql = "Drop Index Idx" & i & " On " & TnameB」でSQL文を作成します。インデックス名はIdx1~Idx5ですのでカウンタ変数iを利用してインデックス名にしています。今回、インデックスを作成したテーブルはKeyTypeBのみのため、「TnameB」で固定しています。
830行目「rs.Open sql, cn」でインデックス削除実行しています。

6-3.制約(Key)とインデックスの関係

図2-2で示したADOXオブジェクトモデルの中で、Key(制約)とIndex(索引)は、かなり関係しているように思います。例えばKeyのPrimary Key設定をすれば、テーブル内のKeyオブジェクトのプロパティ(図5-8)に記録されるだけで無く、Indexオブジェクトのプロパティ(図5-9)にも記録されます。逆にIndex設定時に併行してUnique設定すれば、Indexオブジェクトのプロパティだけで無くKeyオブジェクトのプロパティにも記録されます。
ここで制約(Key)とインデックスの関係を整理してみたのが下記の表です。図6-8は、制約(Key)を設定した時に、対象列のプロパティを変更するか否か、またテーブルのKeyプロパティ、Indexプロパティに記録されるか否かを示したものです。
制約列プロパティKeyIndex
Check
Not Null
Unique
Primary Key
Foreign Key
図6-8

AccessにはCheck制約が無いので、テーブルのKeyやIndexには記録されません。しかし、ADOXを使って列プロパティの「Jet OLEDB:Column Validation Rule」に式を記入すれば同等の機能となります。
またNot Null制約については、ADOXを使用した場合はKeys.Appendには対応する定数がありませんので、列のプロパティを設定するしかありません。SQLを使った場合も図5-12で説明した通り、ADD CONSTRAINTに「Not Null」を指定することは(Excel VBAからは)不可能なために、列設定時にNot Nullを指定することで、列のプロパティを設定するしかありません。

一方で図6-9は、インデックス(Index)を設定した時に、テーブルのIndexプロパティ、Keyプロパティに記録されるか否かを示したものです。
インデックス列プロパティIndexKey
IndexNullsAllow
DisAllow
Ignore
IgnoreAny
Unique
Primary Key
図6-9

この表の通り、インデックスを設定したものは全てテーブルのIndexプロパティに記録されます。しかしNull関連はKeyプロパティには記録されません。言い換えれば、Not Nullを指定するにはインデックスから行えば良い、と言えるかもしれません。

なお、削除する時は同じオブジェクトの筋で削除した方が良さそうです。制約を設定したらKeyを削除、インデックスを設定したらインデックスを削除という具合です。
例えば、Foreign Keyを設定するとIndexプロパティにも記録されますが、それをIndexで削除するとエラーが出てしまいます。

7.テーブル構造の確認

作ったテーブルの列名とデータ型を確認するのが図7-1です。このプロシージャでは「テーブル名」「列名/データ型」のみを表示させていますが、必要なプロパティ、KeyやIndex値なども必要であれば値を取得し表示する事も可能です。
  1. '========== ⇩(21) テーブル構造の確認 ============
  2. Sub getTable()
  3.  Const DBname As String = "it-081.accdb"
  4.  Dim DBpath As String: DBpath = ThisWorkbook.Path & "¥"
  5.  Dim Cat As Object     '←Catalogオブジェクト
  6.  Dim TBL As Object     '←Tableオブジェクト
  7.  Dim str As String     '←MsgBoxに表示する文字列
  8.  Dim i As Long       '←カウンタ変数(列の数)
  9.  Set Cat = CreateObject("ADOX.Catalog")
  10.  Cat.ActiveConnection = "Provider=Microsoft.Ace.OLEDB.12.0;" & _
  11.              "Data Source=" & DBpath & DBname
  12.  For Each TBL In Cat.Tables
  13.   If TBL.Type = "TABLE" Then
  14.    str = TBL.Name & vbLf & vbLf
  15.    For i = 0 To TBL.Columns.Count - 1
  16.     str = str & TBL.Columns(i).Name & " / " & TBL.Columns(i).Type & vbLf
  17.    Next i
  18.    MsgBox str
  19.   End If
  20.  Next TBL
  21.  Set Cat = Nothing
  22. End Sub
図7-1

図7-1を実行すると、例えば図7-2のようなメッセージボックスが表示されます。複数のテーブルが存在すれば、テーブルの数だけ表示されることになります。
表示は「テーブル名」と「列名/データ型」になり、データ型は図4-1で示したADOのデータ型の値になります。
テーブル構造の表示
図7-2

859行目「Set Cat = CreateObject("ADOX.Catalog")」で、Catalogオブジェクトを生成し、860行目でデータベースファイルに接続します。

863行目「For Each TBL In Cat.Tables」で、データベースファイル内の全てのテーブルを走査します。テーブルには、ユーザーが作るデータを入れるためのテーブル(TABLE)以外に、システムテーブル(SYSTEM TABLE)などがあります。
864行目「If TBL.Type = "TABLE" Then」では、その走査したテーブルが「データを入れるテーブルか否か」を調べています。大文字小文字は別扱いですので、「大文字半角」で "TABLE" とします。本当は「TABLE」という文字列を戻してくれる関数があれば良いのですが見当たりませんでした。

メッセージボックスには変数strを文字列として表示しますので、まず865行目「str = TBL.Name & vbLf & vbLf」で、テーブル名を変数strに代入します。その後に「vbLf」を2つ入れることでテーブル名と列情報の間を空けています。
866行目「For i = 0 To TBL.Columns.Count - 1」でカウンタ変数iをそのテーブルの列数分だけ回します。
867行目「str = str & TBL.Columns(i).Name & " / " & TBL.Columns(i).Type & vbLf」では、列名「TBL.Columns(i).Name」と列のデータ型「TBL.Columns(i).Type」を変数strに加えていきます。

全ての列情報を変数strに書き込んだら、870行目「MsgBox str」でメッセージを表示します。
1つのテーブルの表示が終わったら、863行目のFor Eachで次のテーブルを走査します。

8.データの操作

テーブルの準備が出来たらデータの出し入れの段階になりますが、この操作は「SQLを使用する」しかありません。データ操作をするSQLの命令文は基本的に4つです。
 テーブルからデータを検索し、その結果を戻す「 Select
 テーブルに新たにデータ行を挿入する「 Insert
 既存のデータ行(レコード)を更新する「 Update
 データ行を削除する「 Delete

8-1.データの挿入・更新・削除

テーブル上のデータに変化を与えるのが「Insert」「Update」「Delete」です。図8-1ではその3種を実行しています。
  1. '========== ⇩(22) データの挿入・更新・削除 ========
  2. Sub execData()
  3.  Const DBname As String = "it-081.accdb"
  4.  Dim DBpath As String: DBpath = ThisWorkbook.Path & "¥"
  5.  Const Tname As String = "AllDataType"
  6.  Dim cn As Object        '←Connectionオブジェクト
  7.  Dim sql(1 To 4) As String    '←SQL文(4種)
  8.  Dim i As Integer        '←カウンタ変数(列数)
  9.  Call ADOmakeTable        '←図4-21でテーブル作成
  10.  Set cn = CreateObject("ADODB.Connection")
  11.  cn.Provider = "Microsoft.ACE.OLEDB.12.0"
  12.  cn.Open DBpath & DBname
  13.  sql(1) = "insert into " & Tname & _
  14.       " (短いテキスト,長いテキスト,数値整数)" & " values('abc','def',123)"
  15.  sql(2) = "insert into " & Tname & _
  16.       " (短いテキスト,長いテキスト,数値整数)" & " values('ghi','jkm',45)"
  17.  sql(3) = "update " & Tname & _
  18.       " set 短いテキスト='ghi' where 数値整数=123"
  19.  sql(4) = "delete from " & Tname & _
  20.       " where 数値整数=123"
  21.  For i = 1 To 4
  22.   On Error Resume Next
  23.    cn.Execute sql(i)
  24.    If Not Err.Number = 0 Then MsgBox Err.Description
  25.   On Error GoTo 0
  26.  Next i
  27.  cn.Close
  28.  Set cn = Nothing
  29. End Sub
図8-1

889行目「Call ADOmakeTable」では図4-21を呼出し、一応全てのデータ型を持つテーブルを作成します。
891行目「Set cn = CreateObject("ADODB.Connection")」でConnectionオブジェクトを生成し、893行目「cn.Provider = "Microsoft.ACE.OLEDB.12.0"」でデータベースに接続するプロバイダをACE系にし、894行目「cn.Open DBpath & DBname」でデータベースファイルに接続します。

896~906行目ではSQL文を作成しています。今回は「Insert(挿入)文」を2つ、「Update(更新)文」「Delete(削除)文」を各1つ作り、886行目「Dim sql(1 To 4) As String」で宣言した配列にそのSQL文を代入しています。
それぞれの基本的な構文を示します。
 挿入: Insert into テーブル名(列名1,列名2,・・・) values(挿入データ1,挿入データ2,・・・)
 更新: Update テーブル名 set 列名1 = データ1,列名2 = データ2,・・・ Where 更新する条件式
 削除: Delete from テーブル名 Where 削除する条件式

896~897行目「sql(1) = "insert into " & Tname & " (短いテキスト,長いテキスト,数値整数)" & " values('abc','def',123)"」は、3つの列にデータを挿入するSQL文です。残りの列はNullとなるのですが、オートナンバー型・はい/いいえ型は図4-17、図4-18で説明した様に「Nullにはならず、オートナンバー型には連番が、はい/いいえ型にはいいえ(False)」が入ることになります。
899~900行目「sql(2) = "insert into " & Tname & " (短いテキスト,長いテキスト,数値整数)" & " values('ghi','jkm',45)"」も異なるデータが挿入されます。

902~903行目「sql(3) = "update " & Tname & " set 短いテキスト='ghi' where 数値整数=123"」は、数値整数列が123のレコードに対して、短いテキスト列のデータを更新しています。Where句を付け忘れたり、常に成立するようなWhere句(例:Where 1 = 1 )にすると、全データを更新してしまうことになりますので注意が必要です。

905~906行目「sql(4) = "delete from " & Tname & " where 数値整数=123"」は、数値整数列が123のレコードを削除しています。Where句を忘れたり常に成立するようなWhere句だと全データが削除されます。

908~913行目では、上記で作成した4つのSQL文を順番に実行しています。
908行目「For i = 1 To 4」では、カウンタ変数iを1~4まで変化させ、910行目「cn.Execute sql(i)」でSQL文を1つずつ実行していきます。しかし、接続が切れた・SQL文が間違っていた等の理由で実行できなかった場合にはエラーが出てしまいますので、909行目「On Error Resume Next」でエラーをスルーさせ、911行目「If Not Err.Number = 0 Then MsgBox Err.Description」でエラー内容「Err.Description」を表示させます。

なお、ここではSQL文を実行するのに「cn.Execute SQL文」という構文を使っていますが、もちろんRecordsetオブジェクトを生成して「rs.Open SQL文,cn」としてもOKです。

8-2.データ取得

データを取り出すのが「Select」です。データ取得しワークシート上にデータ一覧を貼り付けるのが図8-2です。Select文にはRecordsetオブジェクトは必ず必要となります。
  1. '========== ⇩(23) データ取得と表示 ============
  2. Sub selectData()
  3.  Const DBname As String = "it-081.accdb"
  4.  Dim DBpath As String: DBpath = ThisWorkbook.Path & "¥"
  5.  Const Tname As String = "AllDataType"
  6.  Dim cn As Object     '←Connectionオブジェクト
  7.  Dim rs As Object     '←Recordsetオブジェクト
  8.  Dim sql As String     '←SQL文
  9.  Dim i As Integer      '←カウンタ変数(列数)
  10.  Set cn = CreateObject("ADODB.Connection")
  11.  Set rs = CreateObject("ADODB.Recordset")
  12.  cn.Provider = "Microsoft.ACE.OLEDB.12.0"
  13.  cn.Open DBpath & DBname
  14.  sql = "SELECT * FROM " & Tname
  15.  rs.Open sql, cn
  16.  Sheet1.UsedRange.ClearContents
  17.  For i = 1 To rs.Fields.Count
  18.   Sheet1.Cells(1, i) = rs.Fields(i - 1).Name
  19.  Next i
  20.  Sheet1.Cells(2, 1).CopyFromRecordset rs
  21.  rs.Close
  22.  cn.Close
  23.  Set rs = Nothing
  24.  Set cn = Nothing
  25. End Sub
図8-2

940行目「Set cn = CreateObject("ADODB.Connection")」でConnectionオブジェクトを生成し、941行目「Set rs = CreateObject("ADODB.Recordset")」でRecordsetオブジェクトを生成します。
943行目「cn.Provider = "Microsoft.ACE.OLEDB.12.0"」で、Connectionオブジェクトにデータベース接続プロバイダをセットし、944行目「cn.Open DBpath & DBname」でデータベースファイルに接続します。
943~944行目を1行にまとめて「cn.Open "Provider=Microsoft.ACE.OLEDB.12.0; Data Source=" & データベースファイル」としてもOKです。

946行目「sql = "SELECT * FROM " & Tname」ではSQL文を作成します。Select文の基本構文は以下になります。
 選択: Select 検索する列名1,検索する列名2,・・・ from テーブル名 Where 検索する条件式 ここでは「全ての列」という意味の「*(アスタリスク)」を使っています。またWhere句を付けていないため「全てのレコード」を選択することになります。
947行目「rs.Open sql, cn」でそのSQL文を実行し、結果はRecordsetオブジェクト(変数rs)に戻されます。

949行目「Sheet1.UsedRange.ClearContents」では、データ一覧を貼り付けるSheet1をクリアしています。

951~953行目では、データの列名をワークシート上に書き込んでいます。SQL文を実行して取得するRecordsetには、もちろんデータだけでなくその他の情報も入っていますが、単純にワークシート上に貼り付ける(955行目)と「データのみ」が貼り付きます。ですので、列名を1つずつ取り出し、1つずつワークシート上に貼っていきます。
951行目「For i = 1 To rs.Fields.Count」で、カウンタ変数iを列数分だけ回します。そして952行目「Sheet1.Cells(1, i) = rs.Fields(i - 1).Name」で列の名前「rs.Fields(i - 1).Name」を取り出し、ワークシート上に順番に貼っていきます。RecordsetのFields(列)のインデックスはゼロ始まりのため、-1しています。

列名が貼り終わったら、955行目「Sheet1.Cells(2, 1).CopyFromRecordset rs」でCopyFromRecordsetメソッドを使ってRecordsetオブジェクトの内容(データのみ)を列名の下に貼り付けます。
なおCopyFromRecordsetメソッドの第一引数にデータの入ったRecordsetオブジェクトを指定していることになるのですが、第二引数「貼り付けるレコード数」、第三引数「貼り付ける列数」を指定可能です。これを使用すると、Excelの固定した範囲内に収まるようにデータを貼り付けることも可能です。

最後に957行目「rs.Close」でRecordsetを閉じ、958行目「cn.Close」で接続を閉じます。

9.最後に

昔、Accessでは無かったですがデータベースを使ったシステムを作った際、それまで使っていた旧システムのデータも(古いデータも見たいだろうと思って)データベース化したものですから、稼働開始時であるにも関わらずデータ量が100万レコードにもなってしまいました。
テーブルは4列だったのですが、そのシステムの重いこと重いこと。これでは使ってもらえないと思い、その時に初めてインデックスにトライしました。
どの列をインデックスにすれば良いのか知識も無く、設定をしては実行速度を確かめるのトライ&エラーでした。インデックスをやたらに付けても逆効果になることも分かり、最終的には桁2つ分くらい速くすることが出来ました。

システム使用開始時からお世話になるのは珍しいかもしれませんが、データが増えて来た時には必須のアイテムだと思いますし、そのインデックスをうまく働かせられるか否かが、アプリをユーザーが使い続けてくれるかの分かれ道になるかもしれません。


ExcelからAccessデータベースを作成・操作(it-081.xlsm)
この「サンプルファイルit-081.xlsm 」内の各プロシージャは、Accessデータベースの作成・操作を1つ1つ説明したもので、基本的には繋がりは薄いです。ですので、プロシージャの実行順序によっては「テーブルは既に存在します」などのエラーが出ることがありますのでご了承下さい。
プロシージャ操作を簡単にするため、シート上にプロシージャを呼び出すボタンを配置しましたが、そのボタンの操作でも同様です。一旦データベースファイルを更新(削除→再度作成)すれば、エラーは少なくなると思います。

セキュリティ向上を目的として「インターネット経由でダウンロードしたOfficeファイル(Excel等)のマクロは、既定でブロック」されるようにOfficeアプリケーションの既定動作が変更になりました。(2022年4月より切替開始)
解除の方法については「ダウンロードファイルのブロック解除方法」を参照下さい。