ADOは ActiveX Data Objectsの略で、Microsoftが提供するデータベースへアクセスのためのライブラリです。
今回はADOのRecordsetをデータベースを扱わないExcelでも使うと便利なときもあるよ話をします。
使用方法
使用するときは、 ADOは参照設定で「Microsoft ActiveX Data Objects 6.1 Library」にチェックを入れることで使用できます。

Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
参照設定をしない場合は、CreateObjectで書くこともできます。
Dim rs As Object
Set rs = CreateObject("ADODB.Recordset")
Recordsetテーブルの作り方
特徴
Recordsetでは、データベースで使われる”ちゃんとしたテーブル”を作成するため、フィールド(列)を定義してからデータの追加を行います。
フィールドの追加
フィールドを追加するときは、Recordsetの Fields.Append を使用します。
Sub Append(Name As String, Type As DataTypeEnum, [DefinedSize As Long], [Attrib As FieldAttributeEnum = adFldUnspecified], [FieldValue])
- Name: 新しいフィールドの名前を指定する
String値です。Fieldsコレクション内で一意である必要があります。 - Type: 新しいフィールドのデータ型を指定する
DataTypeEnum値です。例えば、adIntegerやadVarWCharなどがあります。既定値はadEmptyです。 - DefinedSize (省略可能): 新しいフィールドの定義されたサイズを指定する
Long値です。文字数やバイト数で指定します。省略した場合、データ型から派生した既定値が使用されます。 - Attrib (省略可能): 新しいフィールドの属性を指定する
FieldAttributeEnum値です。例えば、adFldUpdatableやadFldKeyColumnなどがあります。既定値はadFldDefaultです。 - FieldValue (省略可能): 新しいフィールドの初期値を指定する
Variant値です。省略した場合、フィールドにはNull値が設定されます。
With rs
.Fields.Append "ID", adInteger
.Fields.Append "Name", adVarWChar, 50
.Fields.Append "Age", adInteger
.Open
End With
データの追加方法1
Recordsetでは、rsの中に「現在読み書きしている行」の状態が書き込まれているため、配列のようにインデックスが存在しないことに注意が必要です。
例えば、下図のようにAddNewするとテーブルに1行追加され、新しく作成した行にカーソルが移動します。
書き込みをした後は、Updateメソッドを実行してようやく確定になります。
rs.AddNew
rs.Fields("ID").Value = 1
rs.Fields("Name").Value = "John Doe"
rs.Fields("Age").Value = 30
rs.Update
データの追加方法2
AddNewには省略可能な引数が2つあり、これを指定することで1行で行の追加が可能です。
- FieldList (省略可能):
- 新しいレコード内のフィールドの名前、名前の配列、または序数位置を指定します。
- 省略した場合、
Fieldsコレクションのすべてのフィールドが対象となります。
- Values (省略可能):
- 新しいレコード内のフィールドの値、または値の配列を指定します。
FieldListが配列の場合、Valuesも同じ数のメンバーを含む配列である必要があります。
rs.AddNew Array("ID", "Name", "Age"), Array(1, "John Doe", 30)
rs.Update
Recordsetのフィールド部の出力方法
Recordset.Fieldsには、一括でセルへ出力したり配列に変換してくれる機能はありません。
煩雑にはなりますが、ForやForEachで個別にループする必要があります。
フィールド出力方法1 : 列順に個別にアクセス
Dim i As Long
For i = 1 To rs.Fields.Count
Cells(1, i).Value = rs.Fields(i - 1).Name
Next i
フィールド出力方法2 : 列をDictionaryに格納してから一気に出力
Dim dic As New Dictionary, fld: For Each fld In rs.Fields: dic(fld.Name) = fld.Type: Next Cells(10, 1).Resize(1, rs.Fields.Count) = dic.Keys()
Recordsetのデータ部の出力方法
Recordsetのデータをセルへ出力するには、個別にアクセスする方法の他に、一括で出力する方法と、配列変数へ変換してからセルへ出力する方法があります。
既に説明したように、Recordsetにはカーソル位置の状態が格納されているため、現在位置より後のデータだけが出力対象となるため、 rs.MoveFirst を必ず入れましょう。
データ出力方法1 : 列名で個別に出力
rs.MoveFirst
Do While Not rs.EOF
Debug.Print rs.Fields("ID").Value & ", " & rs.Fields("Name").Value & ", " & rs.Fields("Age").Value
rs.MoveNext
Loop
データ出力方法2 : Range.CopyFromRecordsetで全データ一気に出力
異なるライブラリにも関わらずExcel.Range型には CopyFromRecordset という関数があり、ADODB.Recordset型を渡すことができる機能があります。
Function CopyFromRecordset(Data As Unknown, [MaxRows], [MaxColumns]) As Long
引数
- Data (必須):
- 出力する
Recordsetオブジェクトを指定します。 - Recordsetの現在のカーソル以降が出力対象となる点に注意が必要です。
- この引数は省略できません。
- 出力する
- MaxRows (省略可能):
- ワークシートにコピーするレコードの最大数を指定します。
- この引数を省略すると、
Recordsetオブジェクトのすべてのレコードがコピーされます。
- MaxColumns (省略可能):
- ワークシートにコピーするフィールドの最大数を指定します。
- 実在するフィールド数より大きな数字を指定しても問題ありません。
- 0以下を指定するとエラーになります。
- この引数を省略すると、
Recordsetオブジェクトのすべてのフィールドがコピーされます。
例えば下のプログラムを実行すると、A2を基点として全てのデータをセルに出力することができます!
rs.MoveFirst Cells(2, 1).CopyFromRecordset rs
データ出力方法3 : GetRowsで2次元配列を取得してから一気に出力
Recordset には Recordset.GetRows() という関数があります。
引数
- NumRows (省略可能): 取得する行数を指定します。省略した場合、レコードセットのすべての行が取得されます。
- Start (省略可能): データの取得を開始するレコードのブックマークを指定します。省略した場合、現在のレコードから取得が開始されます。
- Fields (省略可能): 取得するフィールドを指定します。単一のフィールド名または位置、または複数のフィールド名や位置の配列を指定できます。省略した場合、すべてのフィールドが取得されます。
出力前に変数に格納できるので、メモリ上で計算ソースとしても使いやすいです。
一方で、GetRows()の結果は行と列が逆転した結果が返ってくるのですが、WorksheetFunction.Transpose関数などを使用して回転させなければなりません。
rs.MoveFirst Dim vv vv = WorksheetFunction.Transpose(rs.GetRows()) Cells(11, 1).Resize(rs.RecordCount, rs.Fields.Count) = vv
Transpose利用上の注意
ところがTranspose関数にあるいろいろな制限の都合でエラーになる可能性がかなり高いです。結果的に同様の回転関数を自作する必要があります。
- ※TransposeはNull値の混在がNG
- ※Transposeは行数が多いときもNG
ActiveXが廃止になるらしいという話について
ADOは ActiveX Data Objectsの略ですが、ActiveXという言葉は非常に幅広い意味が含まれているので、対象になることはないと考えています。
10月発売の「Office 2024」でActiveXが既定無効に ~「Microsoft 365」は2025年4月から - 窓の杜
https://qiita.com/yaju/items/1aa5b322d4dac893a5e9
完成コード
Option Explicit
Function CreateRecordsetInMemory() As ADODB.Recordset
Dim rs As ADODB.Recordset
Rem 新しいRecordsetオブジェクトを作成
Set rs = New ADODB.Recordset
Rem フィールドを追加
With rs
.Fields.Append "ID", adInteger
.Fields.Append "Name", adVarWChar, 50
.Fields.Append "Age", adInteger
.Open
End With
Rem データを追加
rs.AddNew
rs.Fields("ID").Value = 1
rs.Fields("Name").Value = "John Doe"
rs.Fields("Age").Value = 30
rs.Update
rs.AddNew
rs.Fields("ID").Value = 2
rs.Fields("Name").Value = "Jane Smith"
rs.Fields("Age").Value = 25
rs.Update
rs.AddNew
rs.Fields("ID").Value = 3
rs.Fields("Name").Value = "xxxxxxx"
rs.Fields("Age").Value = 55
rs.Update
Rem データを追加(一括)
rs.AddNew Array("ID", "Name", "Age"), Array(1, "John Doe", 30)
rs.Update
Set CreateRecordsetInMemory = rs
End Function
Function WriteRecordset(rs As ADODB.Recordset)
Rem ----------------------------
Rem データ出力方法1 : 列名で個別にアクセス
rs.MoveFirst
Do While Not rs.EOF
Debug.Print rs.Fields("ID").Value & ", " & rs.Fields("Name").Value & ", " & rs.Fields("Age").Value
rs.MoveNext
Loop
Rem ----------------------------
Cells.ClearContents
rs.MoveFirst
Rem フィールド出力方法1 : 列順に個別にアクセス
Dim i As Long
For i = 1 To rs.Fields.Count
Cells(1, i).Value = rs.Fields(i - 1).Name
Next i
Rem データ出力方法2 : Range.CopyFromRecordsetで全データ一気に出力
Rem メリット : 超シンプル
Cells(2, 1).CopyFromRecordset rs
Rem ----------------------------
rs.MoveFirst
Rem フィールド出力方法2 : 列をDictionaryに格納してから一気に出力
Dim dic As New Dictionary, fld: For Each fld In rs.Fields: dic(fld.Name) = fld.Type: Next
Cells(10, 1).Resize(1, rs.Fields.Count) = dic.Keys()
Rem データ出力方法3 : GetRowsで2次元配列を取得してから一気に出力
Rem メリット:出力前に加工ができる。計算ソースとしても使いやすい
Rem デメリット : Transpose互換の回転関数を自作する必要がある
Rem ※TransposeはNull値の混在がNG
Rem ※Transposeは行数が多いときもNG
Dim vv
vv = WorksheetFunction.Transpose(rs.GetRows())
Cells(11, 1).Resize(rs.RecordCount, rs.Fields.Count) = vv
Rem ----------------------------
Rem Recordsetを閉じる
rs.Close
Set rs = Nothing
End Function