仕事でAccessを使っていると以下のようなVBAコードに遭遇しました。
-------
…
Do Until animaltable.EOF
countanimal = countanimal + 1
With animal_data(countanimal)
.animal_number = Nz(animaltable.Fields("animalvariation").Value,0)
End With
countanimal.MoveNext
…
-------
animaltableというのは、
Dim animaltable as DAO.Recordset
で定義されている変数で、動物の数を取得するためのレコードセット?らしいです。
animal_dataというのは、別のモジュールで「Public animal_data() As ANIMAL_INFO '動物の情報」と定義されておりました。
animalvariationというのは、別にanimalSQLという変数がありまして、その変数にはSQL文が格納されています。
そのanimalSQL文の中に「アニマルテーブル.animalの種類 as animalvariation」という文が出てくるので、おそらくアニマルテーブル内にある列「animalの種類」の別名をanimalvariationに設定しているのかと。
Withステートメントは、「オブジェクト名を省略できる」「オブジェクトへの参照回数が減り、実行速度が向上します」とあるので、animal_dataオブジェクトに対し、animal_numberという処理を行っていることはわかります。
ざっくり言うと、オブジェクトの配列から一つずつデータを取り出して処理している、と考えれば良いと思います。
Recordset からの取得とオブジェクトの配列では、以下の違いがあります。
オブジェクトの配列:ループを回す前に、全てデータを取り出してある
Recordset:ループを回している途中で、少しずつデータを取り出す
データベースからデータを取り出す場合には、何百万件というのを相手にすることもあるので、最初に全てデータを取り出すと、無駄なデータまで取り出すかもしれず、遅くなったり、メモリを無駄遣いする、という可能性があります。
なので、「少しずつデータを取り出す」のですけれど、ひとつずつ取り出すと、今度は一発でデータを取り出すよりも遅くなっちゃうので、「いくつかまとめて少しずつデータを取り出す」という戦法を取ります。
この「いくつかまとめて少しずつデータを取り出す」というのが、自分で全部書くと、地味に難易度が高いので、ひとつずつ取り出して処理をしているような書き方にできるのが Recordset です。
配列だと最初にデータの件数が分かっているので、ループも「何件」という終了判定になりますけど、少しずつ取り出す場合には、何喧嘩も分かってないので、終了判定が「データがなくなるまで」=「animaltable.EOF」となります(EOF : End Of File)。
もうひとつ、Nz関数の話。
データベースは、言ってみれば「表」のようなものですけれど、そのひとつのマスには「何も入っていない」という状態を作ることができます。
「何も指定しなければ数値のゼロが入っている」ではなくて「何も入っていない」です。
「何も入っていない」は数値ではないので、扱いが面倒なのですけれど、何も入っていないので、ファイルのサイズを節約できます。
それの扱いを簡単にするのが Nz関数です。
データを作るときに、特に入れる値がなければゼロを入れておく
なのか
データを作るときに、特に入れる値がなければ、何も入ってないままにしておいて、取り出したときにゼロで扱う
のどちらかです。
後者は、「何も入っていなければ」という判定を入れることになるのですが、それを簡略化してくれるのが Nz関数です。
Nz関数を使わなければ、
n = 0 If Not IsNull(animaltable.Fields("animalvariation")) Then n = animaltable.Fields("animalvariation").Value EndIf .animal_number = n
という感じになります(他のやりようも、いくつかあります)。
これを Nz関数を使うと、一行で書ける、ということです。
.animal_number = Nz(animaltable.Fields("animalvariation").Value,0)
ただ、断片的には判るものの、全体として…何をしているのかいまいちピンと把握できておりません。
Nz関数も、「animaltable.Fields("animalvariation").Value」という元の値を、「0」にしている、というのはわかります。
しかしanimaltableというレコードセット?を、.(ドット)でつなげた後のField?というのもよくわからず…Valueプロパティは、オブジェクトの属性(値)を付与する等、本に書かれている内容の一部はなんとなく判りますが、それらが組み合わさったときに何が行われているのかを把握しようとすると、頭の中で整理できなくなります…。
上記コードでは一体、どのような処理がされているのか、どなたか解説いただけますと助かります。
よろしくお願い致します。
■
animal_data() :配列変数(構造体の配列)
配列を使用する (VBA) | Microsoft Docs
https://docs.microsoft.com/ja-jp/office/vba/language/concepts/getting-started/using-arrays
■
animal_data().animal_number
animal_data()は配列だけど、たぶん構造体になっている。
その構造体のanimal_number という変数を上記で指定
構造体およびその他のプログラミング要素 (Visual Basic) | Microsoft Docs https://docs.microsoft.com/ja-jp/dotnet/visual-basic/programming-guide/language-features/data-types/structures-and-other-programming-elements
■
animaltable :レコードセット
クエリ(SQLで条件指定したクエリ)、レコードセット
Recordset オブジェクト (ADO) | Microsoft Docs https://docs.microsoft.com/ja-jp/office/client-developer/access/desktop-database-reference/recordset-object-ado
■
animaltable.Fields("animalvariation").Value
animaltableクエリ(レコードセット)の「animalvariation」フィールドの値
Fields コレクション | Microsoft Docs
https://docs.microsoft.com/ja-jp/office/client-developer/access/desktop-database-reference/the-fields-collection
Field オブジェクト (Access デスクトップデータベースリファレンス) | Microsoft Docs
https://docs.microsoft.com/ja-jp/office/client-developer/access/desktop-database-reference/the-field-object
■
EOF
最後のレコードを超えたかどうか
Recordset.EOF プロパティ (DAO) | Microsoft Docs https://docs.microsoft.com/ja-jp/office/client-developer/access/desktop-database-reference/recordset-eof-property-dao
■
次のレコードに移動
Recordset.MoveNext メソッド (DAO) | Microsoft Docs https://docs.microsoft.com/ja-jp/office/client-developer/access/desktop-database-reference/recordset-movenext-method-dao
■
Do Until文
Do...Loop ステートメント (VBA) | Microsoft Docs https://docs.microsoft.com/ja-jp/office/vba/language/reference/user-interface-help/doloop-statement
■
With ステートメント (VBA) | Microsoft Docs https://docs.microsoft.com/ja-jp/office/vba/language/reference/user-interface-help/with-statement
Do Until animaltable.EOF countanimal = countanimal + 1 With animal_data(countanimal) .animal_number = Nz(animaltable.Fields("animalvariation").Value,0) End With 'countanimal.MoveNext <=これ間違ってるのでは? animaltable.MoveNext <=たぶん、これだと思う Loop
上記から、withを取り除く
Do Until animaltable.EOF countanimal = countanimal + 1 animal_data(countanimal).animal_number = Nz(animaltable.Fields("animalvariation").Value,0) animaltable.MoveNext <=たぶん、これだと思う Loop
こんな感じになる。
■
上記のソースをみると、
animal_dataという配列に、クエリ(レコードセット)のanimalvariationというフィールドの値を、代入している。
1レコード目
animal_data(1).animal_number = 1レコード目のクエリ(レコードセット)のanimalvariationというフィールドの値
こんな感じで、レコード文、配列の構造体のanimal_number に値を代入している
これを、レコード数だけ、DOループで、配列変数(構造体)に代入している
===============
■
追記:
Nz 関数 - Access
https://support.office.com/ja-jp/article/nz-%E9%96%A2%E6%95%B0-8ef85549-cc9c-438b-860a-7fd9f4c69b6c
やっている処理は至極単純で、「animaltable」という表(エクセルのシートみたいなものと考えてください)の、
「animalvariation」という名前の列を上から順に参照しているだけです。
恐らくは、animalvariationには、動物の種類の数が入っているのでしょう。
ただ、なにも記載されていない場合には、「0」として扱いたいためNZ関数を使用しています。
NZ関数は、値が入っていればその値を、何も入っていない(NULL)の場合は、この場合は0を返します。
たとえば、10が入っていれば10を、0が入っていれば0を、何も入っていなければ0を返します。
その結果を、「animal_data」という表の(これはデータベースではない表です)
「animal_number」という項目に入れています。
大したことなっていないけど、言葉にするとややこしいなぁ。
recordsetについては、a-kuma3さんの説明で、少しずつ取り出すという機能的な部分の特徴は判ったのですが。
それがMicrosoftの公式説明にありますような、recordsetオブジェクト、recordsetプロパティ、という呼称になると…少し頭の整理が追い付かなくなります。
プロパティは性質的なものに付けていたような…オブジェクトは、性質ではなくそのクエリそれ自体?と認識しております…。
animal_data、データベースではない表…構造体だからですか…構造体は1度しっかり勉強しないといけませんね。
オブジェクトは、他のオブジェクトのプロパティになることがあります。
AAAオブジェクトの bbb プロパティは BBBオブジェクトである。
そういう場合があります。
そういったケースでは、どちらを主体に文章が書かれているかで書きようが変わります。
AAA を主体に書けば、BBB は AAA のプロパティです。
AAA のことを考えずに BBB を主体に書けば、BBB はオブジェクトという呼び方で記述されます。
質問に書かれているケースだと、Recordsetオブジェクトの Fieldsプロパティは、コレクション(Collection)のオブジェクトです。
Fieldsも、Recordsetに対してはプロパティ、Collectionに対してはオブジェクトになり。
関係性で呼称や役割が変わるんですね。
ありがとうございます<m(__)m>