Microsoft Access 掲示板

CurrentDb.Execute strSQLでは何故かエラー

6 コメント
views

毎回お世話になってます。
非連結フォームの値をテーブルに追加したいのですが、CurrentDb.Execute strSQLでは”実行時エラー'3061'
パラメータが少なすぎます。4を指定して下さい。”となります。CurrentDb部を DoCmd.RunSQL strSQL にすると正常に動作します。理由が分からないのです(私があまり理解してないからだと思ってますが)。もちろん参照設定はDAOにしてます。
実行したいSQLは下記になります。CurrentDb.Executeでは出来ない場合があるのでしょうか?

      Dim strSQL As String
            strSQL = _
                    "INSERT INTO [T_登録中継用_改修履歴] " & _
                          "([製造履歴ID], [受付日付], [改修内容], [完了日付], [MEMO]) " & _
                    "SELECT " & _
                          "[T_製造履歴].[製造履歴ID], " & _
                          " Forms![F_改修履歴に追加設定]![tx受付日付代入用], " & _
                          " Forms![F_改修履歴に追加設定]![tx改修内容代入用], " & _
                          " Forms![F_改修履歴に追加設定]![tx完了日付代入用], " & _
                          " Forms![F_改修履歴に追加設定]![tx_memo代入用] " & _
                    "FROM [T_製造履歴] " & _
                    "WHERE [T_製造履歴].[check]=True;"
      ```
beginner
作成: 2025/04/14 (月) 12:19:31
通報 ...
1
hiroton 2025/04/14 (月) 14:09:42 c632e@f966d

DoCmdは「ACCESS」の機能を呼び出します
「ACCESS」は、自身がどんなフォームがあるだとか、そこにどんなコントロールがあるだとか認識できます。なので、クエリ実行時(DoCmd.RunSQL)にForms![F_改修履歴に追加設定]![tx受付日付代入用]が何者か判断できます

CurrentDb.Executeは「DAO」の機能を呼び出します
「DAO」はACCESSの構文を解釈する機能はないので、Forms![F_改修履歴に追加設定]![tx受付日付代入用]を一つの、そのような名称のパラメータ(変数)として解釈しようとします。結果、そのようなパラメータを用意していないDAOを実行しようとして、パラメータが少なすぎますとエラーを返してきています

CurrentDb.Executeを使うのであれば、DAOで実行する前に、VBAで値の変換を済ませておく(strSQLの中にフィールド指定の構文を入れない)必要があります

strSQL = _
    " INSERT INTO T_登録中継用_改修履歴" & _
          " (製造履歴ID, 受付日付, 改修内容, 完了日付, MEMO)" & _
    " SELECT" & _
          " 製造履歴ID" & _
          ", #" & Forms![F_改修履歴に追加設定]![tx受付日付代入用] & "#" & _
          ", '" & Forms![F_改修履歴に追加設定]![tx改修内容代入用] & "'" & _
          ", #" & Forms![F_改修履歴に追加設定]![tx完了日付代入用] & "#" & _
          ", '" & Forms![F_改修履歴に追加設定]![tx_memo代入用] & "'" & _
    " FROM T_製造履歴" & _
    " WHERE check=True;"

参考
パラメータクエリを含むSQLをVBAから実行する(hatena chipsさん)


英語でのエラーだと、たとえば

"Run-time error '3061'. Too few parameters. Expected 1."

のようになるようです。ネイティブならすんなり意味が分かるんでしょうか。日本語にするなら

”実行時エラー'3061'
パラメータが少なすぎます。パラメータが1つ必要です。”

隠れた文字が表現されているといいと思いますし、意訳して「パラメータが1つ不足しています」とかなるとわかりやすいんじゃないかと

2

実行時エラー'3061'
パラメータが少なすぎます。4を指定して下さい。

変数 strSQL に格納されている SQL がパラメータクエリになっているからです。

Forms![F_改修履歴に追加設定]![tx受付日付代入用]

Forms![F_改修履歴に追加設定]![tx改修内容代入用]

Forms![F_改修履歴に追加設定]![tx完了日付代入用]

Forms![F_改修履歴に追加設定]![tx_memo代入用]

以上の 4 箇所がそれぞれ暗黙的なパラメータとして認識されています。

そして DAO.Database オブジェクトの Execute メソッドでは、
パラメータへの値渡しを行うことが出来ません。

この場合は、次のいずれかの方法を用いるのが妥当です。

'Access のシステムメッセージの表示を無効にする
DoCmd.SetWarnings False
'Access の[SQL の実行]アクションを呼び出す
DoCmd.RunSQL strSQL
'Access のシステムメッセージの表示を有効にする
DoCmd.SetWarnings 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

後者の方法は、主に以下のようなケースにおいて用いるのが適しています。

  • 特定のインターフェース(この場合はフォーム)に依存せず、パラメータに任意の値を直接渡したい場合

  • 実行されたクエリ/SQLによって影響を受けたレコードの件数を取得したい場合

  • DAO.Workspace オブジェクトと連携してトランザクション処理を行いたい場合

3

なお、任意のコントロールの値を「 SQL 内におけるリテラル」として文字列連結させる手法もよく見られますが、以下のようなケースに備えてエスケープ処理を行うことが望ましいでしょう。

  • コントロールの値(文字列)にシングルクォーテーション( ' )やダブルクォーテーション(")、改行(などの制御文字)が含まれている場合。

  • コントロールの値が Null である場合。

DAO.QueryDef オブジェクトを使用してパラメータの値渡しを直接行なうのであれば、エスケープ処理は不要です。

4
beginner 2025/04/15 (火) 08:57:51 61dd6@e1025

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;"

5

前回質問分でhirotonさんとhatenaさんから回答頂いた下記は
CurrentDb.Execute strSQLでいけたのですが、これは何故でしょうか?

変数 strSQL に格納された SQL 文の中に「パラメータと解釈し得る要素」が含まれておらず、
また SQL 全体において構文上の不備がないからです。

この場合の「パラメータと解釈し得る要素」とは、主に次のようなものです。

  • データベース上のどのテーブル/クエリの名前とも一致しない識別子

  • クエリから呼び出されたテーブル/クエリのどのフィールドの名前とも一致しない識別子

件の INSERT INTO 文 においては、Forms![F_改修履歴に追加設定]![tx受付日付代入用]などの記述は
FROM 句で呼び出されている[T_製造履歴]のどのフィールドの名前とも一致しないため、
暗黙的に「そういう名前のパラメータである」と解釈されます。

strSQL = ", [完成日付]=" &  Format(Me!change_Date, "#yyyy/mm/dd#")

上記の場合は非連結テキストボックス[change_Date]の値が
「 SQL 内における日時リテラル」として組み込まれるよう
文字列の整形と文字列連結が行われており、最終的に
UPDATE 文の中に「パラメータと解釈し得る要素」が
含まれないようになっています。

strSQL & ", [備考]='" & Me!change_Biko & "'"

ちなみに、この時の非連結テキストボックス[change_Biko]の値が
「I'm sorry.」という文字列だった場合、SQL 上の構文エラーが発生します。

文字列リテラルの囲み記号としてシングルクォーテーションを使用する場合は、
文字としてのシングルクォーテーションを次のようにエスケープした方がよいでしょう。


If Not IsNull(Me!change_Biko) Then strSQL = strSQL & ", [備考]='" & Replace(Me!change_Biko, "'", "''", 1, -1, vbBinaryCompare) & "'"

SQL 文のチェック作業を行なう際には、

'イミディエイトウィンドウに変数 strSQL の値( SQL 文)を出力
Debug.Print strSQL
'変数 strSQL に格納された SQL 文を実行
CurrentDb.Execute strSQL

以上のようにしておき、生成された SQL 文が実際に
どのようになっているかを確認してみることをお奨めします。

6
beginner 2025/04/15 (火) 15:50:21 61dd6@e1025

skさん ありがとうございます。フィールドの名前とも一致しない識別子の事も大変重要ですね。今まで認識してなかったので勉強になりました。ACCESSは名前で判断されるので注意が必要ですね。
確かに「I'm sorry.」ではエラー出ましたのでReplace・・・にしたらOKでした。
細かい設定をしないと、どこでエラーになるか分かりませんね、いい経験となりました。ACCESSは奥が深く大変ですが、コツコツするしかありません。色々な事を教えて頂けて大変有り難いです。
本当に感謝です。