仕事で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という処理を行っていることはわかります。
ただ、断片的には判るものの、全体として…何をしているのかいまいちピンと把握できておりません。
Nz関数も、「animaltable.Fields("animalvariation").Value」という元の値を、「0」にしている、というのはわかります。
しかしanimaltableというレコードセット?を、.(ドット)でつなげた後のField?というのもよくわからず…Valueプロパティは、オブジェクトの属性(値)を付与する等、本に書かれている内容の一部はなんとなく判りますが、それらが組み合わさったときに何が行われているのかを把握しようとすると、頭の中で整理できなくなります…。
上記コードでは一体、どのような処理がされているのか、どなたか解説いただけますと助かります。
よろしくお願い致します。
ざっくり言うと、オブジェクトの配列から一つずつデータを取り出して処理している、と考えれば良いと思います。
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)
■
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
FieldsコレクションのFieldオブジェクト、列に対応しているんですね。
リンクもありがとうございます。
1つ1つ読んで、勉強したいと思います。
構造体…これは骨が折れそうな内容ですね。
またこれについては質問するかもしれませんので、その際はよろしくお願いします<m(__)m>
RecordsetオブジェクトとRecordsetプロパティは区別して用いているようなので、この違いも違うようで…すみませんご紹介いただいたリンクを読んだのですが、いまいち理解できず…具体的に、RecordsetオブジェクトとRecordsetプロパティはどのように違うのでしょうか?
Recordsetオブジェクトはクエリの塊、Recordsetプロパティはクエリの処理内容、のような認識で問題ないでしょうか?
>RecordsetオブジェクトとRecordsetプロパティは区別して用いているようなので
ごめんなさい。間違ってました。
Recordsetオブジェクトです。リンク先も修正しました。
ざっくり言うと、オブジェクトの配列から一つずつデータを取り出して処理している、と考えれば良いと思います。
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)
ありがとうございます、Recordsetについて理解が深まりました!
Nz関数も、本よりも詳しい解説をしていただき、ありがとうございます!!
nullではない(If Not)時は、n = animaltable.Fields("animalvariation").Value ということで、animalvariation列の値を取得しまして。
それ以外の時は、 .animal_number = n すなわち0を入れるということですね。
それをNz関数なら一行で書ける…わかりました。
ありがとうございます、Recordsetについて理解が深まりました!
2019/11/21 22:18:31Nz関数も、本よりも詳しい解説をしていただき、ありがとうございます!!
nullではない(If Not)時は、n = animaltable.Fields("animalvariation").Value ということで、animalvariation列の値を取得しまして。
それ以外の時は、 .animal_number = n すなわち0を入れるということですね。
それをNz関数なら一行で書ける…わかりました。