ExcelVBAにおけるレコード操作について

ExcelVBAで利用するADOオブジェクトとSQLの併用について

ExcelVBAではデータベース操作の基本であるレコード操作がオブジェクトを利用することで編集することが出来ます
また、このオブジェクトを利用せずにSQL文を利用してレコード操作を行うことも出来ます

どちらも同じ結果を導くので、どっちゃでもええのですが操作内容によっては処理速度に大きな差が出るので、そこには注意してください

基本的にコードの実行回数が同じ回数を実行する場合は、SQLよりオブジェクトを利用した方が早いです

例えば、レコードの追加が明確に差があります
SQL文ではレコードの追加はINSERT文を使用します、これはフィールド名と入力内容を指定することでデータベースに新規レコードとして追加する構文です
それに対して、オブジェクトを利用する場合はAddnewメソッドを利用します
このメソッドはデータの入力は行いません、新規レコードを追加するだけです
このメソッドを実行した後に各フィールド名を指定して値を代入していきます

この2つの全く同じ結果を求められる処理では、後者のオブジェクト利用が圧倒的に早いです

そもそもの想定の違いかもしれませんが、SQLは基本的に細かいコードを連続して実行するものではなく、なるべく1回のコード実行で結果を出すものだからです
対してExcelVBAは、細かいコードを大量に実行します

なのでどちらが優位というものでもないのでしょうが、ExcelVBAにおいてのレコード操作という点ではExcelVBAのオブジェクト利用が優位です

そして、ここまでオブジェクトと記載してきましたが、実際にはADOオブジェクトというものを利用します
これと似たような操作のできるDAOオブジェクトもありますが古いオブジェクトとなりますのでADOオブジェクトを利用できるようになれば十分です

このADOオブジェクトは、FileSystemObjectと同じようなインスタンスを作成して利用することのできるオブジェクトになります

Accessデータベースのファイルを作成し、接続を行い、テーブルの作成を行ったら
次に行うのがこのオブジェクトの作成です
それからようやくレコード操作が可能になります
実際のコードにすれば、ほんの数行の話なのであまり気負うものでもありませんが、初めは戸惑いたっぷりになるとは思いますが、基本はセルなどのオブジェクト操作と変わりませんので、この記事を読んでいる方のレベルなら特段難しくはありません

と、ここまでExcelVBAのオブジェクト利用の優位性を言い過ぎましたが、上記でも言いましたが実行回数が同じであれば確かにオブジェクト利用が優位です
ですが、オブジェクト利用ではレコードを1つずつ操作することになります

データベース全体に対するレコード操作を行いたい場合に1000レコードのデータベースであれば1000回コードを実行することになります
ですが、SQLのUPDATE文を利用すればたった1回の構文でデータベース全体の編集を行うことが出来ます

こうなるとコードの可読性においてもこちらが優位です

つまり言いたいのは、ADOオブジェクトだけではレコード操作を有効利用できない、ということです
おすすめ書籍でもあげているSQLの基礎本などを参考にSQLもすこしずつ理解を深めていってください

フィルタやワークシート関数を多用して、かつピボットテーブルまで使ってようやく出来るような集計がSQLでは驚くほど簡単に出来ることが多いです
データベースをデータの保管庫としてだけでなく、集計処理でも有効にデータベースを利用してください

データベースに接続・切断する(ADODB.Connection)

ADODB.Connectionオブジェクトを利用してデータベースへの接続と切断を行うコード

'accdbファイルに接続する
zzDB_Con.Open "Provider=Microsoft.Ace.OLEDB.12.0;Data Source=" & ThisWorkbook.Path & "\作成DB.accdb"

'接続を解除(切断)する
zzDB_Con.Close

データベースの作成が完了したら、次にデータベースにOpenメソッドを使用して接続することで操作が可能となります
また、接続を行うとデータベースが接続状態となります、この状態を明示的に解除するのがCloseメソッドになります

データベースは性質上、排他的に開いたりすることが出来ます
データベースを操作する場合は、この切断処理は必ず行うようにしてください

なおコード中の「zzDB_Con」はインスタンスを作成したObject型の変数です
詳細に関しては以下の記事を確認してください

コード解説

zzDB_Con.Open "Provider=Microsoft.Ace.OLEDB.12.0;Data Source=" & ThisWorkbook.Path & "\作成DB.accdb"

Openメソッドを使用して、データベースに接続を開始します
引数のProviderには既定の文字列を指定します、accdbファイルでは例コードの文字列を基本的に使用します
そのあとに続くSourceにAccessデータベースファイルの絶対パスを文字列で指定します
今回の場合では、同じフォルダ内のデータを参照しています

このOpenメソッド以降で、データベースに関する操作を行うことが出来るようになります

空白のファイルを作成して、最初に接続した場合はさらにテーブルの作成を行う必要があります
このテーブルの作成まで行うと、そこにレコードを登録していくことが出来るようになるためデータベースにデータを保存していくことが出来ます

zzDB_Con.Close

このコードを実行すると接続しているデータベースを切断します
データベースの利用が終了したら確実に行います
また、なるべくデータベースは無駄に接続することの無いようにしてください

データベースから受け取ったデータの加工等に時間のかかる処理であれば、処理後にまたデータベースを利用するとしても、いったん接続は切断するようにします

ただ、処理に時間が大きくかかる可能性が無いことや、データベースの利用自体がそもそも個人レベルのものである場合はそこまで気にしなくてもいいです

なんせ、接続したら終了時には切断する
ここをしっかり行うようにしてください

データベース接続用オブジェクトについて(ADODB.Connection)

データベースに接続するためのADODB.Connectionオブジェクトの宣言コード

'接続用オブジェクトの作成
Dim zzDB_Con As Object
Set zzDB_Con = CreateObject("ADODB.Connection")

Set zzDB_Con = Nothing

Accessデータベースを操作するには、まずデータベースファイルの作成を行う必要がありました

次に必要なのが、そのデータベースと接続を行うことです
この接続という操作を行って初めてデータベースの操作が可能になります

その際に使用するのが記事コードのADODB.Connectionオブジェクトになります
これはインスタンスの作成を行う必要がありますので、最初にCreateObject関数でインスタンスの作成を行います

このオブジェクトは単にデータベースとの接続だけを行うものでは無く、接続したデータベースにSQL文を実行することが出来ます
基本的には接続とSQL文の実行の2つを実行するオブジェクトと認識してください

そしてSQL文の実行に関して、このオブジェクトではレコードを取得する必要が無い場合に主に利用します

例えば、UPDATE文はデータベースのデータを更新しますが、結果を取得しません
そういった構文に利用します
なのでSELECT文のようなレコードの取得を行う構文はこのオブジェクトでは利用できません

コード解説

Dim zzDB_Con As Object

ADODB.Connectionを代入させる変数です
事前バインディングの場合は専用の型を使用しますが、実行時バインディングを利用しますのでここではObject型で指定します

Set zzDB_Con = CreateObject("ADODB.Connection")

CreateObject関数でインスタンスの作成を行っています
Class名はADODB.Connectionと指定します

この作成後にデータベースの操作を行います

Set zzDB_Con = Nothing

操作が完了したらオブジェクト変数なので、最後に解放して初期化しておきます

実際の使用について

これで作成を実行した後に、使用するメソッドは主に3つになります
まずはデータベースとの接続を行うOpenメソッド接続を解除するCloseメソッドSQL文の実行を行うExecuteメソッドになります

OpenメソッドとCloseメソッドはセットで使用します
Openメソッド使用後はデータベースが使用中になります
この状態を解除するのがCloseメソッドなので必ず使用してください

Executeメソッドは、引数にSQL文を文字列で指定することで接続しているデータベースに対して実行します
上記にも解説したようにレコードの取得を必要としない構文を実行します

Accessデータベースを操作するうえで利用するのは、あとレコードを代入させるためのADODB.Recordsetオブジェクトを使用します

ちなみに、ここで使用しているADODB以外にDAOというオブジェクトもありますが、こちらはADODBより古いオブジェクトになります
こちらはこちらで利点もありますし、使用感も似ていますが両方を覚える必要はありません
せめてインスタンス作成が同じで利用できるなら良いのですが、別のインスタンスを作成する必要があるので両方の利用は面倒ですね

Accessデータベースの操作について

ExcelVBAでAccessデータベースを操作する流れ

Excelは今でこそ100万行のデータ行数に、凄まじい速さとなった関数などのおかげで大容量データもストレスなく扱えるようになりました
2003時代では数メガのデータ容量となったExcelファイルは保存するだけでストレスをよく感じたものです

そんな時代によく聞いたのが、容量の問題でExcelに限界を感じて、Accessに移行したという話です
やはり当時数千行データとなると、どうしてもこの話に勝てませんでした

とにかく、わしのPCにAccess入ってへんから知らんがな、と強がりを言ったものですが、数千行のデータとなってもストレスフリーなExcelに育ったのも少し感慨深いものです

さて、脱線しましたが、ここで言いたいのは現在の状況であってもやはりデータの保管という点においてAccessデータベースには敵わないという点です
いくら100万行あるとはいっても、さすがに数万というデータになるときつくなってきます

とはいえ保管されたデータを見やすくする点でExcelにはAccessは全く敵いません
今はPowerBIというアプリもありますが、まだまだExcelの加工・グラフは第一線でしょう

つまり、大容量データの保管をAccessデータベースに、データの利用にはExcelにというのが最大の利用法になります

そしてこの利用法において、最大級にExcelに軍配が上がります
なぜなら、ExcelVBAを利用することで、データベースの作成・操作が可能だからです
データベースの操作を行いつつ、Excel上で加工もできる
Accessが無くても問題はありません、Excelだけで可能です
逆にAccessはあるけど、Excelが無い人はこれが出来ません
今は強がりではなく、Accessは知らんExcelで十分と言えます

これを言うとAccessファンの人の癇に障るかもしれませんが、自分はExcelファンでここに来ている人もきっとそうなので問題なしとしておきましょう

操作の流れ

ExcelVBAでデータベースを操作する場合の流れとしては、大きく2通りあります

1つ目はデータベースファイル、accdbファイルを作成し、そこにテーブルを作成してレコードを登録、加工や集計を行う
必要に応じてワークシートに出力する

2つ目はデータベースファイルを介さず、メモリ上にデータベースを作成し、同じく登録等を行い、ワークシートに出力する

この2つの流れになります
2つ目の方法は少し難しくなりますが、処理の動きをイメージできるようになれば加工・集計にSQLを利用することが出来るようになるため処理の幅が広がります

基本的には1つ目のデータベースファイルを作成して操作を行います
この2つの違いは、データベースに登録したレコードを保存するかしないかという点で考えてください
保存する必要がある場合は1つ目、加工・集計のみであれば2つ目の流れになります

とはいえ、2つ目の流れは1つ目の流れが包括しているのでこのAccessデータベース操作が慣れてきたら挑戦してみてください

まずは以下の記事からデータベースの作成を行ってください

作成が行えたら次に作成するのがテーブルです
テーブルとはExcelで言うテーブル機能のようなものです

Accessデータベース操作はレコードセットを利用したり、SQLを利用するため少しレベルが上がる内容ですが
ここも使えるようになると、ビッグデータが怖くなくなるので1年1ファイルなんて言うケチ臭いことは考えなくて良くなります

ExcelVBAで出来ることの限界を極めるためにここをやりきってみてください

SQLについて

データベース操作において、必ず使用することになるものです
これはオブジェクト操作ではなく、別の言語です

データベースを操作するための言語なのですが、これがなかなか大変なのが扱うデータベースの種類によって微妙な差がある点です

そしてAccessデータベースで使用するSQLはMicrosoft Access SQLという言語を利用します

OracleやMySQL、Microsoft SQL Server等々の有名な言語がありますが、別物です
特にMicrosoft SQL ServerとMicrosoft Access SQLは別なので混同しないようにしてください

このAccessデータベース操作をやり始めた時に、ここの理解が全く出来ておらず
ビューの作成を行い、そのビューの構成を変更したくて「ALTER VIEW」がエラーとなった際に全く原因が分かりませんでした
Microsoft Access SQLにはこのコードがサポートされていません

そこで初めて、いま使っているSQLは一体どの種類のモノなのか?
という疑問点を長く持ち続けて、ようやく気付いたのがMicrosoft Access SQLというSQLを使用していることでした

ほとんどは他のSQLと差は無くエラーが発生することはありません
なのであまり大きく意識をする必要もなかったというのもあります
また、このSQLを明確に記載しているSQLの書籍を見たことがありませんでしたし、このSQLの専門書もありません
しかし、今はMicrosoftのヘルプサイトが充実しているのである程度何とかなります

他の言語との微妙な違いではデータ登録時の点がいくつかあります

文字列を表す場合、多くのSQLでは’(シングルクォーテーション)を使用しますが、こちらでは”(ダブルクォーテーション)も利用できます
日付データ時には’(シングルクォーテーション)か#で挟むかのどちらも利用可能です
自分は#を使用します、これはVBAの仕様と一致するからです

ワイルドカードでも少し差があります
Excelは通常、任意の1文字を「?」、0以上文字を「*」で表現します
Microsoft Access SQLでは、任意の1文字を「_」(アンダースコア)、0以上文字を「%」で表現します

なお任意の1文字は「?」、0以上文字は「\*」も利用可能なのですが、これも混乱しました
Excel通常の「?」が使えるのに「*」が使えないのはなんでやねん、となりましたから

データ型に関しても独特です
本来SQLの型にString型は存在しません、CHARやTEXTが文字列型になります
しかし類義語という指定に含まれており、自動的に変換されておりVBAからString型を指定してもエラーは発生しません
ここがVBA使いとしては楽な点です、コードの可読性を下げることなく型指定を行うことが出来る訳ですから
本来は正式な型宣言を行うべきなのでしょうが、まあ自動的に変換してくれてるんだから有効利用しましょう

この辺り含め詳細に関してはヘルプサイトを参照してください
Microsoft Access SQL リファレンスはこちらから

最後に、SQLの勉強に関してですが
ここはそれほど複雑な構文を作成する必要はありませんので、基本の書籍で十分です
おすすめ書籍に記載しているSQLの絵本という書籍があるのですが、ExcelVBAからの利用の観点で行けば十分すぎる内容がありますのでお勧めします
ちなみにMicrosoft Access SQLについての記載は無いので、微妙な違いの点についてはリファレンスと合わせて確認を進めてください

Accessデータベースの空ファイルの作成

Accessが無い環境でAccessファイルを作成するコード

'実行ファイルと同じ場所に空のAccessファイルを作成
Dim zzDBAdox As Object
Set zzDBAdox = CreateObject("ADOX.Catalog")
zzDBAdox.Create "Provider=Microsoft.Ace.OLEDB.12.0;Data Source=" & ThisWorkbook.Path & "\作成DB.accdb"
Set zzDBAdox = Nothing

Accessデータベースの2007以降のバージョンである、accdbファイルを処理実行ファイルと同じ場所に新規作成します
同じ名称のファイルが存在する場合は実行時エラーが発生します

基本的にこのコードはAccessがPCに入っていない環境でデータベースを扱う際に使用します
Accessがあるなら、Accessで作成してもらってもいいです

コード解説

Dim zzDBAdox As Object

Accessデータベースを作成するには、ExcelVBAの標準コードでは実行できません
ADOX.Catalogというものを使用しますので、これのインスタンスの作成を先に行う必要がありますので、それを代入させるための変数宣言です

事前バインディングであれば、「Microsoft ADO Ext. 6.0 for DDL and Security」を参照設定して「ADOX.Catalog」の型指定を行うことでインテリセンスを使用できます
参照設定の数字の部分は使用するバージョンによって差があります

ただ、このオブジェクトではデータベースを作成する以外にテーブルの設定などを取得できますが、ほぼこの作成機能しか使用しないのでそこまではしなくてもいいとも思います

Set zzDBAdox = CreateObject("ADOX.Catalog")

実行時バインディングを実行しています
その際に使用するのが「ADOX.Catalog」という文字列になります
これで機能を使用することが出来る状態になりました

zzDBAdox.Create "Provider=Microsoft.Ace.OLEDB.12.0;Data Source=" & ThisWorkbook.Path & "\作成DB.accdb"

実際のファイル作成のコードになります
Createメソッドを使用することでファイルの作成を行えます

通常のExcelのファイルを作成するように、ただファイルの絶対パスを指定するだけでは作成できません

Providerという引数を指定する必要があります
これに関してはAccessのバージョンによって違いがあります

「Microsoft.Ace.OLEDB.12.0」というのはAccessの2007以降のaccdbファイルを利用する場合に指定する文字列です

ここに関してはOfficeの上位互換を考慮すると、それほど意識する必要はありません
実際、2019でも365でも12の利用に問題はありません
これの上位に「Microsoft.Ace.OLEDB.16.0」というものがあります
主にBit環境に依存するようですので、もし接続エラーが発生する場合に切り替えるようにすればいいと思います
正直Bit依存の対応は永久的な方法は存在しないので、あまり考えてもしょうがないです、ExcelVBAの業務改善はその時その場面での利用が多いからです
WinAPIの利用時と同じようなイメージということになります

このProvider文字列は作成時だけでなく、接続時にも使用します
接続時とは、データベースを操作するタイミングのことです
なので、以降もよく出てくるので広域常数やモジュールレベル常数で作成しておけば良いです

この接続文字の後にファイルの絶対パスを指定します

ファイルの拡張子はaccdbを指定してください
Accessのデータベースファイルには、これ以前にmdbファイルというものがあります
ただこの拡張子はもうaccdbに代わって10年以上経っています
なので本サイトではここ以外では扱いません

なお、mdbファイルの作成及び接続には以下の文字列を使用します
「Microsoft.Jet.OLEDB.4.0」で接続して、ファイルの拡張子はmdbとします

接続後の操作に関しては全く同じです

Set zzDBAdox = Nothing

オブジェクト変数を使用しているので、最後に解放します

作成後について

これでファイルの作成が完了です
ただし空のファイルなので、テーブルも存在していません
テーブルというのはデータを収める場所です

イメージ的には全くシートの存在しないExcelファイルを作成しただけで、シートを作成しないとデータを入力することが出来ない状態というようなイメージです

テーブルの作成もレコードの操作もExcelVBAから行えますので、そちらを行ってください

また、このファイル作成を一時ファイルとして利用することも出来ます
というのも、データベースでの最大の利点がSQLを利用することが出来る点になります
これを利用すると、集計やデータ全体の加工などがExcel上で扱うより楽に出来たり、処理速度の向上に繋げることも出来ます

なので、このコードで一時ファイルを作成してデータベースを登録して加工・集計してワークシートに出力して、一時ファイルを削除する

という流れの処理を作成することも出来ます、ExcelVBAにSQLを加えることが出来ます

もし大容量データを加工・集計して、別のシートやブックに転記するような処理があった場合は検討してみてください

指定セル範囲からDictionaryオブジェクトを作成する

引数に指定したセル範囲のデータを取得したDictionaryオブジェクトを作成する関数です

Function zzz辞書作成(ByVal hzzセル範囲 As Range, Optional ByVal hzzオフセット列数 As Long = 0) As Object
Dim zz辞書 As Object: Set zz辞書 = CreateObject("Scripting.Dictionary")
Dim zz対象セル As Range


For Each zz対象セル In hzzセル範囲
    If zz対象セル <> "" Then
        If zz辞書.Exists(zz対象セル.Text) = False Then
            zz辞書.Add zz対象セル.Text, zz対象セル.Offset(0, hzzオフセット列数).Text
        Else: End If
    Else: End If
Next zz対象セル

If zz辞書.Count <> 0 Then
    Set zzz辞書作成 = zz辞書
Else: End If
Set zz辞書 = Nothing


End Function

コード中にある辞書とは、Dictionaryオブジェクトのことになります
これを使用すると、作成したリストから特定のkeyプロパティに対応するitemプロパティを取得することが出来ます
Dictionaryオブジェクトについては以下の記事で解説しています

ですが、これを作成する場合は、インスタンスの作成からデータの取得までを行う必要があります
これが作成のたびに作るのが案外面倒なので処理化しました

この関数を実行すると、引数に指定したセル範囲のデータからDictionaryオブジェクトのリストを作成して、そのDictionaryオブジェクトを返します
なお、取得したアイテムが無ければNothingを返します

目的とは少し違ってきますが、Dictionaryオブジェクト自体が重複不可なリストを作成しますので、セル範囲のデータから重複しないデータのリストを作成することも可能です

なお、この関数では取得するデータは全て文字列として取得されます

関数の書式

引数(太字は必須引数)
(hzzセル範囲, hzzオフセット列数)
戻り値の型 Object型

「hzzセル範囲」は取得を行うセル範囲を指定します
いちおう複数列は指定可能ですが、次のオフセット列数を指定する場合はその分列がずれていく点には注意が必要です

「hzzオフセット列数」は指定したセル範囲のデータをitemとして取得するかどうかを設定するための数値です
引数に指定したセル範囲のデータをitemとして取得してよければ省略してください
オフセット数値なので負の数値も設定可能ですが、指定セル範囲から参照できない数値を指定した場合は実行時エラーが発生します

関数の使用例

Dim zz辞書 As Object
Set zz辞書 = zzz辞書作成(Range("A2:A10"), 1)
If zz辞書 Is Nothing Then
Exit Sub
Else: End If

Dim zz取得キー As Variant
For Each zz取得キー In zz辞書.Keys
Debug.Print zz辞書(zz取得キー)
Next zz取得キー

実際に使用する場合は、戻り値を代入させる変数の宣言を行っておく必要があります
また、この変数の型は戻り値の型がObject型なので合わせておいてください

引数には指定セル範囲がA1~A10までの10個のセルを指定
オフセット数値に1を指定していますので、itemはB1~B10の内容が取得されることになります

セル範囲をB1~B10にして、数値を-1に指定すれば、B列がkeyとなりA列がitemとなるDictionaryオブジェクトの作成が可能です

上記に記載しましたが、この関数は取得したアイテムの数は0個の場合にはNothingを返しますので、その判定を一応行っています
その場合には処理を終了しています

取得が出来ていれば、そのitemを全てイミディエイトに出力しています
実際の処理時には、すべてをループするような処理よりはExistsメソッドを使用して必要なitemを検索取得するような流れになる場合が多いと思います
Existsメソッドについては以下の記事を確認してください

また、ここでは全てを紹介しきれないのでコード自体は割愛しますが、この関数は上記でも解説したように全てが文字列として取得されていますので、keyから取得するitemも全て文字列で返されます
なので、日付や数値などを利用する場合はその型に変換して取得させるようにしてください
比較を行う場合は、逆にそれらを文字列型に変換してから比較するようにします

コード解説

Function zzz辞書作成(ByVal hzzセル範囲 As Range, Optional ByVal hzzオフセット列数 As Long = 0) As Object

~~中略~~

End Function

戻り値として、作成したDictionaryオブジェクトを返すのでFunctionプロシージャとして作成をしています
また、返すのはDictionaryオブジェクトですがバインディングが必要な型になるので、汎用的なObject型として指定しています

そのため、この関数の戻り値を代入させる変数も同じ型にしておいてください

Dim zz辞書 As Object: Set zz辞書 = CreateObject("Scripting.Dictionary")
Dim zz対象セル As Range

関数内で使用する変数の宣言です
使用する変数は2つになります

1つ目はDictionaryオブジェクトです
事前バインディングは行わず、実行時バインディングを行っています
なので、変数の型はObject型となります
変数の宣言後、すぐにインスタンスの作成を行っています
特に処理の実行条件確認は行っていないためです

2つ目は引数のセル範囲をループさせるためのセル変数です

For Each zz対象セル In hzzセル範囲

~~中略~~

Next zz対象セル

引数のセル範囲を全てループします
セルはオブジェクトなので、For Eachループを使用してすべてのセルを1つ1つ参照していきます

    If zz対象セル <> "" Then

~~中略~~

    Else: End If

セルの入力値が空白であれば取得は行いません
そもそも空白のデータというものが、空白というデータなのかデータが無いというのかを判定するのは面倒ですし、何より引数に指定するセル範囲に空白が存在しないようにするもの面倒です

なので、前提として空白は取得を行いません

        If zz辞書.Exists(zz対象セル.Text) = False Then
            zz辞書.Add zz対象セル.Text, zz対象セル.Offset(0, hzzオフセット列数).Text
        Else: End If

次にDictionaryオブジェクトにデータを取得させるには、すでに存在するkeyは取得できないので、データの存在確認を行います
ExistsメソッドによりFalseが返された場合にのみ取得を行います

取得する際にkeyはループ参照しているセルのTextプロパティを取得させます
keyは代入させられる型がセルほどにありません
文字列と数値で同じ見た目でも別の値と認識されて、itemが取得できなくなるので前提として統一しておいた方が後で取り出しやすくなります
なので、この関数では全てのデータは文字列として取得させています

itemは参照セルからオフセット数値を列移動させたデータを取得させます
オフセット数値が省略時は0が指定されるので、移動しないので参照セルを取得する形になります
つまりkeyとitemが同じものが取得されることになります
こちらもkey同様にTextプロパティを取得しています

If zz辞書.Count <> 0 Then
    Set zzz辞書作成 = zz辞書
Else: End If
Set zz辞書 = Nothing

指定セル範囲からリストの作成が完成したら、最後にそのDictionaryオブジェクトをFunctionプロシージャの戻り値に設定します

しかし、そもそもitemが全くないDictionaryオブジェクトを返しても意味は無いので、item数をCountプロパティから取得してその個数が0でなければ戻り値に代入します

0であれば戻り値に代入しませんので、Object型の初期値であるNothingが返されることになります

これで、この関数の戻り値がNothingかどうかを判定することでitemの有無を判定させることが出来ます

最後に使用したObject型変数の解放を行っています

この関数を使用すれば、Dictionaryオブジェクトを使用する際にインスタンスの作成を自分で作成する必要が無いので文字列型で問題なければ有効に利用できます

開かれているブックから指定したシートを取得する

全てのブックの全てのシートから指定した名称のシートを1つ取得する関数です

Function zzzワークシート取得(ByVal hzz検索文字列, Optional ByVal hzzCN検索 As Boolean = False) As Worksheet
Dim zz対象Bk As Workbook, zz対象Sh As Worksheet, zz取得Sh As Worksheet


For Each zz対象Bk In Application.Workbooks
    For Each zz対象Sh In zz対象Bk.Worksheets
        If hzzCN検索 = True Then
            If zz対象Sh.CodeName Like hzz検索文字列 Then
                Set zz取得Sh = zz対象Sh: Exit For
            Else: End If
        Else
            If zz対象Sh.Name Like hzz検索文字列 Then
                Set zz取得Sh = zz対象Sh: Exit For
            Else: End If
        End If
        If Not zz取得Sh Is Nothing Then
            Exit For
        Else: End If
    Next zz対象Sh
Next zz対象Bk
Set zz対象Sh = Nothing
Set zz対象Bk = Nothing

If Not zz取得Sh Is Nothing Then
    Set zzzワークシート取得 = zz取得Sh
    Set zz取得Sh = Nothing
Else: End If


End Function

開かれているブックの中から指定の名称のシートを取得する関数です
この関数は引数に指定した文字列に完全に一致するシートをオブジェクトで返します

シートオブジェクトにはVBAでのみ扱うオブジェクト名というものが存在します
CodeNameプロパティを使用することで取得できます
この名称でも検索を行えるようにしています、その場合は2つ目の引数にTrueを指定してください

関数の書式

引数(太字は必須引数)
(hzz検索文字列, hzzCN検索)
戻り値の型 Worksheet型

「hzz検索文字列」は、検索を行う際のシート名、もしくはオブジェクト名を文字列で指定します
完全一致での検索になるので、検索したい名称のすべてを指定するようにします

「hzzCN検索」は、検索方法の設定です
Trueを指定すると、オブジェクト名で検索します
Falseを指定すると、シート名で検索を実行します
省略が可能で、省略時はFalseが指定されます

関数の使用方法

Dim zz対象Sh As Worksheet
Set zz対象Sh = zzzワークシート取得("Sheet1")
If zz対象Sh Is Nothing Then
Exit Sub
Else: End If

zz対象Sh.Range("A1") = 1000
zz対象Sh.Parent.Save

実際に使用する場合は、返し値のある関数なので代入先を作成してから使用します
今回で言えば、「zz対象Sh」というWorksheet型の変数を作成してそこに代入させています

さらに、この関数は検索が見つからなかった場合はNothingが返されるので関数の後に判定を行う必要があります
もし取得できていなかった場合は実行時エラーが発生するからです

その際にはオブジェクトの比較をIs演算子で行います

オブジェクトの取得が完了していれば、処理を実行します
今回の処理では、A1セルに「1000」を入力してから、ブックの上書き保存を実行しています

このシートからの指定の最大の利点がアクティブを遷移させる必要が無いため、処理の高速化が図れる点です
シートの切り替えを行わずにバックグランドで処理を実行すれば画面更新の抑止も全く必要なくなります
セル選択を減らすことが処理の高速化につながりますが、さらにシート・ブックまでも選択を減らすことでさらに改善できます

また、Parentプロパティを使用することでブックの指定を行うことも可能なのでブックに対する処理も取得したオブジェクトで実行することが出来ます
あまり分かりやすいとは言えないので、せいぜいこの程度の処理で行うと良いです
Parentプロパティを経由すれば別のシートの操作も可能ですが、あまりしない方が良いですね

コード解説

Function zzzワークシート取得(ByVal hzz検索文字列, Optional ByVal hzzCN検索 As Boolean = False) As Worksheet

~~中略~~

End Function

引数を2つ使用する関数の作成です
取得したワークシートを返すので、関数の返し値の型はWorksheet型に指定しています
最後のEnd Functionまでが処理の中身になります

Dim zz対象Bk As Workbook, zz対象Sh As Worksheet, zz取得Sh As Worksheet

変数の宣言です
「zz対象Bk」はWorkbook型で、この関数の処理では開かれているすべてのブックを参照するので、そのループ処理に使用します

「zz対象Sh」と「zz取得Sh」はWorksheet型です
対象Shはループ用の変数で、取得Shは検索文字列と一致するものを見つけた時の代入先です

For Each zz対象Bk In Application.Workbooks
    For Each zz対象Sh In zz対象Bk.Worksheets

~~中略~~

    Next zz対象Sh
Next zz対象Bk
Set zz対象Sh = Nothing
Set zz対象Bk = Nothing

ここでは2つのオブジェクトループを行っています
1つ目が開かれているブック全てのループです
Application.WorkbooksはExcelが開いているブック全てを返すプロパティです

ここで1つ注意点として、このApplicationというのはVBAが実行されているブックを開いているExcelという意味になります
実際、Excelは複数起動することが可能です
単純にExcelブックを開いただけならExcelが複数起動することは無く、Excelの中でブックが複数開かれる状態になります

もし、あえて複数起動を行っている状況であれば少し複雑な処理になってきますので、ここでは割愛しますが、複数起動されていても同様の目的の処理を作成することは可能です

次に、その1つ1つのブックのシートのループです
zz対象Bk.Worksheetsというのは、1つ目のブックループで取得された1つのブックの中のすべてのシートを返すプロパティです
これですべてのブックのすべてのシートをループさせることが出来ます

オブジェクトループはループが終了すると代入変数は解放されます
しかし、今回の処理では目的のものが見つかった時点でループを最後まで実行せず途中で強制終了します
その場合は、変数が解放されません

そのため、ループを抜けた後に変数をそれぞれ解放しています

        If hzzCN検索 = True Then
            If zz対象Sh.CodeName Like hzz検索文字列 Then
                Set zz取得Sh = zz対象Sh: Exit For
            Else: End If
        Else
            If zz対象Sh.Name Like hzz検索文字列 Then
                Set zz取得Sh = zz対象Sh: Exit For
            Else: End If
        End If

シートのループまで実行して、1枚のシートが取得されたら
そのシートの名前もしくはオブジェクト名が指定のものかどうかを判定します

その判定基準が引数で指定されたものになるので、まずはhzzCN検索がTrueなのかどうかで処理を分岐します

もしTrueであればオブジェクト名で判定を行うことになります
オブジェクト名はCodeNameプロパティを使用することで取得できます
このプロパティは取得専用です

またFalseであった場合はNameプロパティを使用して判定します
Nameプロパティはシートの見出しに表示されている文字列なのでユーザーが認識しているシートの名前です

この2つのプロパティはどちらが良いというものでは無いので、関数としてどちらも検索を行えるようにしました
あえてこの2つを並列に検索するようなことはしていませんが、もしいずれかにでも指定文字列が含まれるかを調べる関数にしたい場合はここの処理分岐を無くせば可能です

                Set zz取得Sh = zz対象Sh: Exit For

そしてどちらにしても、検索文字列と一致するものであった場合は取得変数に代入してループを抜けます

特に意味は無いのですが、ここでは「:」を使用して処理を1行に収めています

        If Not zz取得Sh Is Nothing Then
            Exit For
        Else: End If

シートのループが終了もしくは抜けてきたら、zz取得Shが代入済みかを判定します
この時点で代入済みであればブックのループももう必要ないのでループを抜けます

シートが見つかった時点でGotoを使用すれば一気にブックループも抜けられますが、基本的にそういった処理の組み方はお勧めできません
変数の型によってはロックしたりしてしまいます

ループの個数分、しっかり終了させるようにしてください

If Not zz取得Sh Is Nothing Then
    Set zzzワークシート取得 = zz取得Sh
    Set zz取得Sh = Nothing
Else: End If

最後に、ブックのループを終了もしくは抜けてきた場合にzz取得Shが代入済みかを判定します
ここで代入済みであれば、その代入されたオブジェクトを返し値に設定します
それが済めばzz取得Shは解放します

この1つ前の処理で、全く同じ条件式でIf分岐が存在していますが、そちらはあくまでもブックのループを終了させるかどうかの判定用です
こちらとは目的が違っているので、同じ条件式ならまとめてしまっていいじゃないの~と感じるかもしれませんが、そこは丁寧にコーディングしてあげてください

この関数を使用すれば、別ブックのシートなどをいちいちブック指定から行う必要がなくなるので非常にコードがスッキリします
最近は処理の最前にこの関数があるのが当たり前になってきましたので、実際に使い慣れてくるとめちゃめちゃ便利です

フィルタ条件の解除(単独列)

フィルタ条件を列ごとに解除するコード

'1列目のフィルタ条件を解除
Range("A1").AutoFilter 1

フィルタをかけた列の条件を列ごとに解除するには「AutoFilter」メソッドの条件を省略して実行します

1つ目の引数は指定列の数値指定になります
この数値はワークシートの列数とは一致しませんので注意してください
この点に関しては、以下の記事で解説しています

各列ごとにフィルタ条件を解除する動き
列ごとにフィルタ条件を解除

画像の動きの様にメソッドに指定列数を指定するだけで実行すると、その列のみを解除します

この動きはフィルタがかかっているかいないかは関係なく動作するのですが、逆にフィルタがかかっているものを解除する、という動きにするにはフィルタの状態を判定する必要があります

ただ、すべてをまとめて一括で条件解除をするにはShowAllDataメソッドを使用します
以下の記事で解説しています

フィルタをかける(単独列、単独条件)

最も単純な単独列の単独条件によるフィルタをかけるコード

'表範囲の1列目にフィルタをかける
Range("A1").AutoFilter 1, "=条件"

ワークシートの表範囲にフィルタの条件を指定して抽出するには「AutoFilter」メソッドを使用します

Mac版ではサポートされていないため使用できないようです
以下のページにて記載されています
https://docs.microsoft.com/ja-jp/office/vba/api/excel.range.autofilter(外部リンク)

このメソッドでは、Excel2007以降で機能がかなり追加されたため引数による柔軟な抽出がより行いやすくなりました

その中でも基本の単独列に単独の条件を指定する方法がこの例コードになります

メソッドの書式

引数(太字は必須引数)
AutoFilter Field, Criteria1, Operator, Criteria2, SubField, VisibleDropDown

今回使用する引数は2つです

「Field」はフィルタをかける表範囲の中での列数を数値で指定します
ワークシートの列数では無い点に注意が必要です

「Criteria1」はフィルタをかける際の条件になります
文字列や数値日付データなどで指定をすることが出来ます
またワイルドカードによる部分一致も可能です

Operator以下は今回は割愛します

実際の動き

フィルタをかけた時のシートデータ範囲の動き
フィルタをかける動き

画像の動きを確認してください
今回は2回フィルタをかけています

1回目が1列目に最初に「001」を含むものを指定しています
ワイルドカードによる指定が行えます
なので、「001-01,001-02,001-03」の3つが抽出されています

2回目が2列目に「対象3」を指定しています
これは1つしかデータは存在しないのでそのデータ行のみが抽出されます

条件に関して、1回目と2回目で見比べてもらうと分かる通り、最初に比較演算子を入れますが、省略も可能です
省略した場合は「=」を指定したときと同じ動きをしていることが確認できます
ですが、あまり良い使い方とはいえないかもしれませんので基本的にはちゃんと記載するようにしてください

また途中で解説したように、指定フィールド数はワークシートの列数とは一致しませんので、画像のような体裁のために空白列・行があるような場合は特に注意が必要です
画像を確認してもらうと分かりますが、1列目で変更されているのはB列なのでワークシート上では2列目になります

1列や2列であれば、簡単に判別できますが、これが数十列・数百列となってくると判別するのは困難になります
操作する表の上・左に空白行がある場合は要注意です

また気づいた方もいるかもしれませんが、このメソッドオブジェクトがほぼ意味はありません
表範囲に無いA1セルに対してメソッドを実行しているのに全く問題ありません
もちろんこんな書き方は推奨しません

このメソッドをマクロの記録によりコード作成すると表範囲が指定されています
これを行う必要はありません、そもそもこのメソッド自体がRangeに対してのメソッドなのも不思議なのですが、フィルタ機能はワークシートにつき1つしか作成されません

つまり、このセルを含むワークシートに対してフィルタを実行しているので問題が発生していないわけです
しかし全くの無意味でもありません、フィルタの矢印を設定する範囲を指定することが出来るからです

なので、それを設定する段階ではない画像のような場面ではまったく問題が起きていないのです
問題ないからそれでいいや、とは言わずにせめて表範囲の始点セルくらいは指定するようにしてください

シートのフィルタ設定を取得する

フィルタの適用状態を取得するプロパティ、使用時に注意点があります

'アクティブシートのフィルタ設定状態を取得
Debug.Print ActiveSheet.AutoFilterMode

シートにフィルタが設定適用されているかどうかを調べるには「AutoFilterMode」プロパティを使用します
このプロパティがTrueを返すと適用されており、Falseが返されると適用されていない状態となります

プロパティで設定状態を取得しているときの動き
プロパティ取得の動き

基本的には、フィルタの適用を解除したい場合に解除する前に適用されているかを調べる時に使用します

ちなみに、このプロパティでは設定を行うことも可能ですがFalseだけです
Trueを設定すると実行時エラーが発生します
Falseを代入するとフィルタの適用を解除できます
なので、こちらで解除を行えば十分に感じるかもしれませんがExcelでよく使用されるテーブル使用時に注意点が出てきます

テーブル使用時の注意点

このプロパティはWorksheetオブジェクトのメンバーです
対してテーブルはListObjectというオブジェクトになります
そのため、このプロパティが無効となってしまいます

テーブル使用時にうまくプロパティが取得できていない時の動き
テーブル時の動き

画像を確認してください
この画像ではコードもデータ内容も全く同じものを使用しています

シートのデータ範囲がテーブルに変換されている点が違います

ですが、こちらではフィルタの矢印が表示されていても、いなくてもFalseが返されています
これがこのプロパティでの大きな注意点です

フィルタをかけるメソッド自体は、テーブルかどうかに関わらず使用できます
なので非適用の状態からフィルタを適用する状態にすることは出来ています

ですが、このプロパティは無効となってしまいます
そのため、上記で解説したFalseを設定することによるフィルタの適用解除も使用できません

フィルタの操作自体はテーブルに関わらないので、問題になることはありませんが
AutoFilterModeプロパティ使用時はテーブルの存在にも注意してください