Dim strSQL As String
If Not IsNull(Me!change_Date) Then strSQL = ", [完成日付]=" & Format(Me!change_Date, "#yyyy/mm/dd#")
If IsDate(Me!change_Kokyaku) Then strSQL = strSQL & ", [顧客ID]=" & Me!change_Kokyaku
If Not IsNull(Me!change_Firm) Then strSQL = strSQL & ", [firmID]=" & Me!change_Firm
If Not IsNull(Me!change_Tanto) Then strSQL = strSQL & ", [製造担当者ID]=" & Me!change_Tanto
If Not IsNull(Me!change_Biko) Then strSQL = strSQL & ", [備考]='" & Me!change_Biko & "'"
If strSQL = "" Then Exit Sub
strSQL = "UPDATE [T_製造履歴] SET " & Mid(strSQL, 3) & " WHERE [check]=True;"
'フォーム[F_改修履歴に追加設定]のフォームモジュールから実行する場合
Dim dbTarget As DAO.Database
Dim qdfTemp As DAO.QueryDef
Dim lngInsertedCount As Long
'カレントデータベースの参照
Set dbTarget = CurrentDb
'一時的なクエリを作成する
Set qdfTemp = dbTarget.CreateQueryDef("", strSQL)
With qdfTemp
'各パラメータに値を渡す
.Parameters("Forms![F_改修履歴に追加設定]![tx受付日付代入用]").Value = Me![tx受付日付代入用].Value
.Parameters("Forms![F_改修履歴に追加設定]![tx改修内容代入用]").Value = Me![tx改修内容代入用].Value
.Parameters("Forms![F_改修履歴に追加設定]![tx完了日付代入用]").Value = Me![tx完了日付代入用].Value
.Parameters("Forms![F_改修履歴に追加設定]![tx_memo代入用]").Value = Me![tx_memo代入用].Value
'クエリを実行する
.Execute dbFailOnError
'クエリによって影響を受けたレコードの件数を取得する
lngInsertedCount = qdfTemp.RecordsAffected
End With
Set qdfTemp = Nothing
Set dbTarget = Nothing
If lngInsertedCount >0 Then
MsgBox "[T_登録中継用_改修履歴]に "& lngInsertedCount &" 件のレコードが追加されました。", _
vbInformation, _
"実行完了"
Else
MsgBox "追加対象となるレコードがありません。", _
vbInformation, _
"対象レコードなし"
End If
Dim strSQL As String
If Not IsNull(Me!change_Date) Then strSQL =", [完成日付]="&Format(Me!change_Date,"\#yyyy/mm/dd\#")
If IsDate(Me!change_Kokyaku) Then strSQL = strSQL &", [顧客ID]="& Me!change_Kokyaku
If Not IsNull(Me!change_Firm) Then strSQL = strSQL &", [firmID]="& Me!change_Firm
If Not IsNull(Me!change_Tanto) Then strSQL = strSQL &", [製造担当者ID]="& Me!change_Tanto
If Not IsNull(Me!change_Biko) Then strSQL = strSQL &", [備考]='"& Me!change_Biko &"'"
If strSQL ="" Then Exit Sub
strSQL ="UPDATE [T_製造履歴] SET "&Mid(strSQL,3)&" WHERE [check]=True;"
CurrentDb.Execute strSQL
なな、なんと。そんなやり方ありましたか。
確認させていただきます。
ですよね。
フォームウィザードでデータシートビューで作成すれば1分もかからずに作成できますからね。
現状のロジックも参照先を変更するだけでそのまま使えるはずですし。
あー、なるほど、そういう仕組みありましたねぇ。使わないとパッと出てこないです
サブフォームを作る労力とどのくらい差があるかなぁとも思っていしまいますし
そういうことでしたか。
ソースオブジェクトにテーブルやクエリを設定した場合、メインフォームを開いたときに自動でサブフォームオブジェクトが生成されますので、それにイベントを設定することができます。
Accessのフォームの場合、イベントプロパティにFunctionを設定できますので、それを利用すればご希望のことは実現可能です。
まず、標準モジュールに下記のFunctionを記述してください。
CodeContextObject はFunctionが呼び出されたオブジェクトを返します。
メインフォームの読み込み時イベントに下記のコードを設定してください。
クエリにはIDフィールドがあり、メインフォームには「テキスト1」というテキストボックスがあるという前提です。
メインフォームを開いてサブフォームでレコード移動してみてください。
イベントが発生しているのが確認できるでしょう。
hatenaさん ありがとうございます。
確かにSQLの文法と、VBAの文法がごっちゃになってました。それを意識しながら進めます。多分DAOが何なのかが理解できてないのですね。色々勉強していきます。自分のコードが妥当なのかどうか自信ない事が多いので・・・
今回も大変お世話になりました。
なるほどです、クリック時のイベントでフラグを立てるというのは思ってもいませんでした。
貴重な情報、ありがとうございます。
はやりそうですよね、当初の理解通りではありました。
現状、ボタンクリック時に呼び込みはできているので、
このためだめにサブフォームを作って、ロジックも変えないといけない工数が大きいので、
暫くは様子をみます。
なるほど、把握しました
ソースオブジェクトにフォームを指定しない場合は、明示的にイベントを発生させることができないので基本的には無理ですね
なるほどです。すみません、説明不足でした。
サブフォームコントロールのソースオブジェクトは、サブフォームではなく、
クエリ(データシート)を設定しています。
サブフォームは作っていなくて、そもそもこれを作って設定すれば、
カレントのイベントが取れるというのは、冒頭でもお話した通り、理解はしております。
サブフォームコントロールにクエリ(データシート)を設定した場合、
代替案はないでしょうか?というのが冒頭での質問の本意です。
サブフォームコントロールにはサブフォームが埋め込まれているので、そのサブフォームのイベントに設定すればご希望のことは可能です。
サブフォームに設定したレコード移動イベントは、メインフォームを開いてサブフォームでレコード移動させれば発生します。
まずはやってみてうまくいかないなら、どのようにしたのか、記述したコード、どのようにうまくいかないのか説明してください。
ありがとうごいざいます。
そういうことですね。つまりは、サブフォームコントロールではできないってことですか。
サブフォームとか単純なイメージコントロールが使えればこの問題はないのですが、
画像ファイルがクラウド上にありまして、ActiveXのwebbrowserコントロールを使う想定です。
画像を縮小したりもするので、イベントで制御しないと、と感じております。
メインフォームは閉じておいて、サブフォームをデザインビューで開いて、そのレコード移動時にイベントプロシージャを設定すればいいでしょう。
もし、メインフォームのイメージコントロールに顔写真を表示させていて、サブフォームには画像ファイルのパスが格納されているなら、
VBAを使うわなくても、イメージコントロールのコントロールソースにサブフォームの画像パスフィールドを参照するようにすればご希望のことは実現できると思います。
自己解決済みのようですが、下記で紹介している方法が参考になるかもしれません。
はい、フィールドのデータ型がテキスト型の場合、それでほぼ問題ないです。
非連結テキストボックスは未入力の場合はNull値になりますので。
ただ、VBAで
""
を代入したり、""
を手入力した場合、Null値ではなく空文字列になりますので、注意が必要です。この辺はいろいろ複雑なので説明しだすときりがなくより混乱しそうなので、とりあえずフィールドのプロパティで「空文字列の許可」を「いいえ」にしておいて、エラーが出たらその時に対応するということでいいでしょう。
これは、SQLの文法と、VBAの文法をごっちゃにしているのが、理解が進まない原因でしょう。
SQL文はVBAコード内では単なる文字列です。それをAccessやDAOがSQLとして解釈して実行します。単なる文字列ですから型指定は文字列内で型指定文字(
'
や”
)でします。例えば下記のように
'
で囲んで文字列だと指定します。これはSQL文法です。DAOで更新する場合は、
rs![検査]
はVBAオブジェクトなのでVBA文法にそって記述します。ですので'
で囲こむ必要はないです。コードを読むときやネット記事を読むときは、それがSQL文法なのか、VBA文法なのか意識して読むと理解がすすむと思います。
下記の記事もVBA上でSQLを扱う場合の注意点について説明していますので目を通しておくことをお勧めします。
Access上のコード内で引用符(")と単引用符(')の使い分けについて - hatena chips
ありがとうございます。
すみません、全く理解できておりませんで。
情報が不足しておりましたので追記させていただくと、メインフォームは非連結フォームです。
>サブフォームそのものでレコード移動時のイベントが発生します
初歩的なご質問で恐縮です、このイベントにコードを書くのに、どのように呼び出す(表示)できますか。
サブフォームコントロールのイベントではなく、サブフォームそのものでレコード移動時のイベントが発生します
サブフォームからは
Me.Parent
で親フォームが参照できますhatenaさん ありがとうございます。
NullがOKなら変数宣言時に Dim kensa As Variant としておくと rs![検査] = kensa の右辺には特に何もする必用ないという事ですね? この右辺を rs![検査] = ’ & kensa & ' としてもエラーになるので、どう設定するのかなと思ってたのです。
ネットで調べても右辺固定値の場合は型指定がすべきとあるのですがフォーム値の場合は特にデータ型設定してないので、分からなかったのです。DAOは勉強し始めでルールがあまり分かってないもので。
場合分けせずに、デフォルトで設定するのはやめて、
キーワードが入ったら、コンボボックスにdistinct likeを使って該当組織を表示させることとしました。
フィールドのデータ型と変数のデータ型は合わせた方がいいでしょう。
ただ、フィールドがNull値を許容する設定で、Null値を代入することがあるならVariant型かな。
日付型の場合は、変数に代入する前に日付として正しいかどうかIsDate関数でチェックするようにした方がいいですね。
hatenaさん ありがとうございます。
それの方が楽に出来そうですね。Recordset、RecordsetCloneも試して様子みます。
あと教えて頂きたいのですが、DAOでレコード追加(rs.AddNew)についてです。
非連結フォームのフィールド値を変数にしてテーブル追加させる場合、そのデータ形式も設定した方がいいのでしょうか?
例えば下記の右辺がその変数で文字列の場合です。それはどう記述するといいでしょうか? また日付型の場合は?
rs![検査] = kensa
知識不足ですみません、宜しくお願いします。
フォームに表示されているレコードはRecordsetプロパティで取得できますので、それを対象に更新すればいいですね。
この方法はフォームに現在表示されているレコードを対象に更新しますので、SQLのようにWhere条件とか考慮せずに記述できるので楽です。
また、このコードだと、カレントレコードが先頭から最後まで移動しながら更新していきます。
レコード移動させたくない場合は、
Set rs = Me.Recordset
の部分をSet rs = Me.RecordsetClone
とします。こうすればレコード移動せずに更新できます。ただ、SQLで更新するのと比べると処理速度は遅くなります。ただし、フォームに表示されている件数が多くなければ体験できる差はでないでしょう。
この辺の詳細は興味があれば下記を参照して研究してみてください。
フォームの Recordset, RecorsetClone, RecordSet.Clone の違いとは? - hatena chips
すみません。解決しました。ありがとうございました。
hatenaさん ありがとうございます。
UPDATE [フォームソースのクエリ名] にしたら正常に対象分(表示されているレコードリスト)のみYesに出来ました!
クエリを更新する事でソースであるテーブルを更新させてもいいのですね。
[T_製造履歴]には完成年フィールドがなかったのでフィルターが出来なかったのですね。前回投稿と同じ様な事例でした。
まだ完全には理解出来てないと思いますがパラメータになってしまうというのが大分イメージ出来ました。
自分では絶対解決出来ない事でしたので本当に助かりました。
因みにフォームの表示分を対象に色々する場面が多いのですが、SQL使わずDAOだけでも可能でしょうか? DAOは余り理解出来てないのですがフォームの表示レコードをレコードセットにするイメージで。
ちなみに、「WHEREのMe.filterが効いてなく全レコードがチェックON」になるのは、
Accessは[完成年]をパラメータとして判断して、まずはアクティブフォームから探します。
フォームには[完成年]というフィールドがありますから、その値をパラメータ値として取得します。フォームの[完成年]を参照するとカレントレコードの値を取得します。
カレントレコードの完成年が2025年で、[cb完成年]で2025年を選択していればすべてのレコードの[check]がオンになります。
[T_製造履歴]テーブルには[完成年]フィールドはないということですよね。
ならば、[完成年]はパラメータとして判断されますので、
”実行エラー3061 パラメータが少なすぎます。1を指定して下さい”
というエラーになります。
SQLの[T_製造履歴]の部分をフォームのレコードソースのクエリに変更するか、
下記のようにサブクエリにして[完成年]、[完成月]の演算フィールドを追加すればいいでしょう。
hatenaさん ありがとうございます。
試した分のイミディエイトウィンドウには UPDATE [T_製造履歴] SET [check] = -1 WHERE ([製品ID]=377 AND [完成年]=2015); と表示されました。
フォームのレコードソースは「T_製造履歴」を基にして他のテーブルを連結させたクエリとしてます。
「完成年」フィールドは日付型の完成日がT_製造履歴にあり、それを使ってYear([完成日付])をそのクエリに組み込んでます。フォームの抽出コンボ完成年のソースも同じクエリから取ってきてます。
とりあえず上の回答のデバッグコードを試してみて、出力されたSQL文を提示してください。
あと、下記を確認ください。
フォームのレコードソースは「T_製造履歴」で間違いないですか。
「完成年」フィールドのデータ型は数値型ですか。
状況の追記です。この一覧フォームのフィルター用コンボボックスには製品ID、顧客ID、完成年、完成月、完成日があります。製品IDが未選択時はリスト表示出来ない様にしてます。各コンボ選択(更新後)ではフィルター(投稿したコードを各コンボからCall)が効いてフォームのリスト表示は問題ありません。リスト表示分のみcheckをYesに更新させるボタン(投稿したコード)で更新させる形です。その後、DoCmd.RunSQL strSQL を CurrentDb.Execute strSQL にして試しして下さいみると製品IDと顧客IDは問題なくフォーム表示分のみYesになります。完成年コンボで抽出後ではフォーム表示は問題ないのですがYesに更新実行すると”実行エラー3061 パラメータが少なすぎます。1を指定して下さい”となります(前回投稿と同様)。因みにコンボ完成月、完成日は完成年が未選択では不可にしてます。DoCmd.RunSQL strSQL では実行されるのですが完成年条件が無視されて指定の製品ID分の全レコードがcheck Yesになります。
何かコードに不備があるのでしょうか?
下記のようにデバッグ用のコードを埋め込んで実行してみてください。
イミディエイトウィンドウにstrSQLに格納されているSQL文が出力されますので、それが正しいものになっているか確認しましょう。
skさん ありがとうございます。フィールドの名前とも一致しない識別子の事も大変重要ですね。今まで認識してなかったので勉強になりました。ACCESSは名前で判断されるので注意が必要ですね。
確かに「I'm sorry.」ではエラー出ましたのでReplace・・・にしたらOKでした。
細かい設定をしないと、どこでエラーになるか分かりませんね、いい経験となりました。ACCESSは奥が深く大変ですが、コツコツするしかありません。色々な事を教えて頂けて大変有り難いです。
本当に感謝です。
変数 strSQL に格納された SQL 文の中に「パラメータと解釈し得る要素」が含まれておらず、
また SQL 全体において構文上の不備がないからです。
この場合の「パラメータと解釈し得る要素」とは、主に次のようなものです。
データベース上のどのテーブル/クエリの名前とも一致しない識別子
クエリから呼び出されたテーブル/クエリのどのフィールドの名前とも一致しない識別子
件の INSERT INTO 文 においては、
Forms![F_改修履歴に追加設定]![tx受付日付代入用]
などの記述はFROM 句で呼び出されている[T_製造履歴]のどのフィールドの名前とも一致しないため、
暗黙的に「そういう名前のパラメータである」と解釈されます。
上記の場合は非連結テキストボックス[change_Date]の値が
「 SQL 内における日時リテラル」として組み込まれるよう
文字列の整形と文字列連結が行われており、最終的に
UPDATE 文の中に「パラメータと解釈し得る要素」が
含まれないようになっています。
ちなみに、この時の非連結テキストボックス[change_Biko]の値が
「I'm sorry.」という文字列だった場合、SQL 上の構文エラーが発生します。
文字列リテラルの囲み記号としてシングルクォーテーションを使用する場合は、
文字としてのシングルクォーテーションを次のようにエスケープした方がよいでしょう。
SQL 文のチェック作業を行なう際には、
以上のようにしておき、生成された SQL 文が実際に
どのようになっているかを確認してみることをお奨めします。
hirotonさん skさん 詳細な回答ありがとうございました。なりほどそういう事だったのですね。
因みに前回質問分でhirotonさんとhatenaさんから回答頂いた下記はCurrentDb.Execute strSQLでいけたのですが、これは何故でしょうか? 更新と追加の違いはありますが(完全に理解出来てなくすみません)
Dim strSQL As String
If Not IsNull(Me!change_Date) Then strSQL = ", [完成日付]=" & Format(Me!change_Date, "#yyyy/mm/dd#")
If IsDate(Me!change_Kokyaku) Then strSQL = strSQL & ", [顧客ID]=" & Me!change_Kokyaku
If Not IsNull(Me!change_Firm) Then strSQL = strSQL & ", [firmID]=" & Me!change_Firm
If Not IsNull(Me!change_Tanto) Then strSQL = strSQL & ", [製造担当者ID]=" & Me!change_Tanto
If Not IsNull(Me!change_Biko) Then strSQL = strSQL & ", [備考]='" & Me!change_Biko & "'"
If strSQL = "" Then Exit Sub
strSQL = "UPDATE [T_製造履歴] SET " & Mid(strSQL, 3) & " WHERE [check]=True;"
なお、任意のコントロールの値を「 SQL 内におけるリテラル」として文字列連結させる手法もよく見られますが、以下のようなケースに備えてエスケープ処理を行うことが望ましいでしょう。
コントロールの値(文字列)にシングルクォーテーション( ' )やダブルクォーテーション(")、改行(などの制御文字)が含まれている場合。
コントロールの値が Null である場合。
DAO.QueryDef オブジェクトを使用してパラメータの値渡しを直接行なうのであれば、エスケープ処理は不要です。
変数 strSQL に格納されている SQL がパラメータクエリになっているからです。
以上の 4 箇所がそれぞれ暗黙的なパラメータとして認識されています。
そして DAO.Database オブジェクトの Execute メソッドでは、
パラメータへの値渡しを行うことが出来ません。
この場合は、次のいずれかの方法を用いるのが妥当です。
後者の方法は、主に以下のようなケースにおいて用いるのが適しています。
特定のインターフェース(この場合はフォーム)に依存せず、パラメータに任意の値を直接渡したい場合
実行されたクエリ/SQLによって影響を受けたレコードの件数を取得したい場合
DAO.Workspace オブジェクトと連携してトランザクション処理を行いたい場合
DoCmd
は「ACCESS」の機能を呼び出します「ACCESS」は、自身がどんなフォームがあるだとか、そこにどんなコントロールがあるだとか認識できます。なので、クエリ実行時(DoCmd.RunSQL)に
Forms![F_改修履歴に追加設定]![tx受付日付代入用]
が何者か判断できますCurrentDb.Execute
は「DAO」の機能を呼び出します「DAO」はACCESSの構文を解釈する機能はないので、
Forms![F_改修履歴に追加設定]![tx受付日付代入用]
を一つの、そのような名称のパラメータ(変数)として解釈しようとします。結果、そのようなパラメータを用意していないDAO
を実行しようとして、パラメータが少なすぎますとエラーを返してきていますCurrentDb.Execute
を使うのであれば、DAOで実行する前に、VBAで値の変換を済ませておく(strSQLの中にフィールド指定の構文を入れない)必要があります参考
パラメータクエリを含むSQLをVBAから実行する(hatena chipsさん)
英語でのエラーだと、たとえば
のようになるようです。ネイティブならすんなり意味が分かるんでしょうか。日本語にするなら
隠れた文字が表現されているといいと思いますし、意訳して「パラメータが1つ不足しています」とかなるとわかりやすいんじゃないかと
hirotonさん ありがとうございます。大変勉強になります。
""では上手く検出出来ませんでしたが、よく考えると全角スペースには有効ではありませんでした。この非連結テキストボックスには入力し易い様にプロパティに ひらがな 設定してます。なので全角状態になっているからでした。なのでフォーカス喪失後イベントに下記を記述しました。他の変更用非連結部は全てコンボボックスからの選択ですので大丈夫です。
確かに「備考を削除する」の想定もありますね。様子みて作成していきます。
Nullと""、" " の違いは難しいですね。本当にありがとうございました。
通常、非連結のテキストボックスに
""
(長さ0の文字列)が入ることはありませんフォーム上での人の操作で「何か入力」→「Delete等で削除」を行うと、非連結のテキストボックスの内容は
NULL
になります。(レコードソースに連結されたコントロールの場合、設定によっては""
が入る場合があります)VBAによる操作等で、直接
Me!テキストボックス = ""
のような処理を行うと、非連結のテキストボックスでもその値が""
になったりはしますそもそも気にしていなかったのですが、逆に、「備考を削除したい」という場合はないのでしょうか?
「NULLなので更新対象外」と、「内容削除のために
""
が入力されている」は実作業の上では判別できないので、「備考を削除する」チェックボックスを追加で用意するとかになると思いますまた、とりあえず、回答では
IsNull()
を使っていますが、NULL
も""
同じように扱われればよいならMe!テキストボックス & "" = ""
で判定できますそのほかのコントロールについても、データ型が文字列型以外になるのならば
IsDate()
やIsNumeric()
等で判定してあげるとより良いと思いますhirotonさん hatenaさん ありがとうございます。
なるほどです! 複数FilterのWHERE組立の様にしたらいいのですね(,の件も確かにそうですね)。
コマンドボタンにコード記述したら上手くいきました。
あと一つだけアイデア頂きたいのですがchange_Bikoに誤って""(長さ0の文字列)だけが入力されて、それが更新に反映されるのを防ぎたいのですが、それはどうすればいいでしょうか?
★この履歴の更新ですが登録ミスの変更や仕様変更等が発生した場合の変更をまとめてする為のものです(最終的な正しい履歴にする為の)。
宜しくお願いします。
あー、そうですね。訂正ありがとうございます
フォームに見えてるデータならDAOでやるかなぁとか考えていたらろくにテストもせず投稿してしまいました
前者のコードだと、Me!change_Biko がNullのとき、
WHERE
の直前に,
が来ることになりSQLエラーになりますね。例えば、直前に
,
を付けておいて最後に先頭の,
を削除してSQL文を完成させるようにすればいいでしょう。後者のコードはカレントレコードの値で上書きしてしまいますね。
「元の値と同じ値で書き換える」なら下記のような感じになるかと。