【问题标题】:Duplicate declaration in current scope error in AccessAccess 中当前范围错误中的重复声明
【发布时间】:2020-09-09 14:59:23
【问题描述】:

我正在编写 VBA 代码以在记录集循环中一一提交记录。遇到这个错误,说有一个“当前范围内的重复声明”。当我不小心有重复的变量时,我曾经遇到过这种情况,但这次我不明白为什么会发生这种情况。我在想它可能与脚本的布局有关,也许它试图重复声明它?也许我可以通过在模块中声明变量来解决它?不确定。

Private Sub submitButton_Click()

Const VmfgConnStr = "Connection string is here"

Dim qdf As QueryDef
Set qdf = CurrentDb.CreateQueryDef("")

Dim sqlString As String
sqlString = "INSERT INTO dbo.TRAINING_RECORDS (EMPLOYEE_ID, DOCUMENT_ID, REVISION, DATE_TRAINED, TRAINED_BY, STATUS, COMPETENCY, APPROVED, TYPE) " & _
"SELECT '" & rst![EMPLOYEE_ID] & "', '" & rst![DOCUMENT_ID] & "', '" & rst![LATEST_REV] & "', '" & dtTrained & "', '" & sprTrained & "', 'T', 'Not Verified', 'NO', 'Internal'"

Set objAD = CreateObject("ADSystemInfo")
Set objUser = GetObject("LDAP://" & objAD.UserName)
strDisplayName = objUser.DisplayName

Dim dtTrainedMsg As String
Dim sprTrainedMsg As String

Dim rst As Recordset

dtTrained = InputBox("Enter date trained as 'mm/dd/yyyy':", "", Format(Date, "mm/dd/yyyy"))
Debug.Print dtTrained

If StrPtr(dtTrained) = 0 Then

Exit Sub

Else

sprTrained = InputBox("Trained By:", "", strDisplayName)
Debug.Print sprTrained

If StrPtr(sprTrained) = 0 Then

Exit Sub

Else

Dim ConfirmMsg, ConfirmStyle, ConfirmTitle, ConfirmResponse
ConfirmMsg = "Continue?"
ConfirmStyle = vbYesNo
ConfirmTitle = " "
ConfirmResponse = MsgBox(ConfirmMsg, ConfirmStyle, ConfirmTitle)

If ConfirmResponse = vbYes Then

recSelect = "SELECT EMPLOYEE_ALL.EMPLOYEE_ID, TRAINING_DOCS_ALL.DOCUMENT_ID, TRAINING_DOCS_ALL.LATEST_REV " & _
"FROM TRAINING_DOCS_ALL, EMPLOYEE_ALL " & _
"WHERE EMPLOYEE_ALL.SELECTED = -1 AND TRAINING_DOCS_ALL.SELECTED = -1"

Set rst = CurrentDb.OpenRecordset(recSelect)
rst.MoveFirst
Do Until rst.EOF
    Debug.Print rst![EMPLOYEE_NAME]; rst![DOCUMENT_ID]
    
    qdf.sql = sqlString
    
    qdf.ReturnsRecords = False
    
    qdf.Connect = VmfgConnStr
    
    qdf.Execute
    
    rst.MoveNext
Loop

CurrentDb.Execute "DELETE * FROM TRAINING_RECORDS"

CurrentDb.Execute "INSERT INTO TRAINING_RECORDS (EMPLOYEE_ID, DOCUMENT_ID, REVISION, DATE_TRAINED, TRAINED_BY, STATUS) " & _
"SELECT * FROM uSysTRAINING_RECORDS " & _
"WHERE EMPLOYEE_ID = '" & EMPLOYEE_ID.Value & "'"

CurrentDb.Execute "DELETE FROM TRAINING_NEEDED " & _
"WHERE EMPLOYEE_ID LIKE '" & EMPLOYEE_ID.Value & "' AND DOCUMENT_ID LIKE '" & DOCUMENT_ID.Value & "'"

Else

End If

End If

End If

End Sub

【问题讨论】:

  • 当您收到此错误时,是否突出显示了代码的特定部分?
  • 您在项目中使用了全局变量吗?此代码本身并不表示已发布错误。
  • @TimWilliams 这一行是我现在得到错误的地方。 “Dim rst As Recordset”我没有使用全局变量。看起来在类对象中设置 Option Explicit 有点帮助。
  • rst 从未初始化时,如何在第一个SQL 字符串中使用rst![EMPLOYEE_ID](和其他)?甚至dtTrainedsprTrained
  • @Parfait 你是对的。订单少了一点。我现在想通了。谢谢大家!

标签: sql vba ms-access


【解决方案1】:

考虑 VBA 和 SQL 的最佳实践,特别是 MS Access:

VBA 技巧

  1. 在每个模块的顶部使用Option Explicit。事实上,在 IDE(工具\选项\需要变量声明)中将其设置为全局设置,该设置在所有 VBA 项目中声明行。如果在 对应的Dim 调用之前使用变量,此选项将引发编译错误,因为发布的代码中出现许多 实例。

    与 SQL 的词法顺序不同于逻辑顺序(即,第一个写入的子句 SELECT,通常是最后一个子句运行)不同,VBA 像许多其他语言一样按照行的编写顺序运行代码。因此,Dim 必须在 Set= 赋值之前。

  2. 与上述相关,将所有Dim 调用作为顶级行放在任何应用程序代码之前。这读起来更好,避免了使用前变量声明的问题。

    Const VmfgConnStr = "Connection string is here"
    
    Dim rst As Recordset
    Dim qdf As QueryDef
    Dim recSelect As String, sqlString As String
    
    Dim dtTrainedMsg As String, dtTrained As String
    Dim sprTrainedMsg As String, sprTrained As String
    
    Dim ConfirmMsg As String, ConfirmStyleAs String 
    Dim ConfirmTitle As String, ConfirmResponse As String
    
    Dim objAd, objUser As Object
    Dim strDisplayName As String
    Dim Employee_ID_value As String, DOCUMENT_ID_value As String
    
  3. 始终使用任何块(例如 IfWithForDo 等)缩进您的代码。这有助于提高可读性和可维护性。

    If StrPtr(dtTrained) = 0 Then
        ...
    Else
        ...        
        If StrPtr(sprTrained) = 0 Then
            ...
        Else
            ...        
            If ConfirmResponse = vbYes Then        
                ...
                Do Until rst.EOF   ' REDUNDANT WITH insert-select
                    ...
                Loop                
                ...        
            Else                   ' REDUNDANT IF EMPTY BLOCK
    
            End If        
        End If       
    End If
    

SQL 提示

  1. 使用表别名来避免重写长标识符。

  2. 尽可能避免循环查询并运行插入选择。 (如果dtTrainedsprTrained 是VBA 变量,是否需要参数化,请参见下文)。

    INSERT INTO dbo.TRAINING_RECORDS (EMPLOYEE_ID, DOCUMENT_ID, REVISION, DATE_TRAINED, 
                                     TRAINED_BY, STATUS, COMPETENCY, APPROVED, TYPE)
    SELECT e.EMPLOYEE_ID, t.DOCUMENT_ID, t.LATEST_REV, 'dtTrained', 'sprTrained', 
          'T', 'Not Verified', 'NO', 'Internal'
    FROM TRAINING_DOCS_ALL t, EMPLOYEE_ALL e
    WHERE e.SELECTED = -1 
      AND e.SELECTED = -1
    
  3. 由于cartesian product 可能对大型表产生性能问题,因此请注意上述使用的交叉连接。 (例如,1,000 行 X 1,000 行 = 1,000,000 个结果行)。

  4. 如果不使用通配符 (*),请不要使用 LIKE 运算符,它运行不同的甚至更慢的查询过程。而是使用相等,=

  5. 避免选择带有SELECT * FROM 的所有列(尤其是在追加查询中)。见Why is SELECT * considered harmful?

MS Access 提示

  • 避免在 VBA 中编写 SQL 字符串脚本。而是使用带有parameterization 的存储查询。这样做可以避免混乱的连接和引号的标点符号。这有助于提高代码的可读性和可维护性,甚至提高效率,因为查询编译器会优化保存的查询,而不是即时运行的字符串查询。

    具体来说,将CurrentDb.Execute ... 替换为以下设置。

    SQL (另存为存储对象 - 没有连接或换行)

    PARAMETERS [e_id_param] TEXT;
    INSERT INTO TRAINING_RECORDS (EMPLOYEE_ID, DOCUMENT_ID, REVISION, 
                                  DATE_TRAINED, TRAINED_BY, STATUS)
    SELECT EMPLOYEE_ID, DOCUMENT_ID, REVISION, DATE_TRAINED, TRAINED_BY, STATUS
    FROM uSysTRAINING_RECORDS
    WHERE EMPLOYEE_ID = [e_id_param]
    
    PARAMETERS [e_id_param] TEXT, [doc_id_param] TEXT;
    DELETE FROM TRAINING_NEEDED 
    WHERE EMPLOYEE_ID = [e_id_param] AND DOCUMENT_ID = [doc_id_param]
    

    VBA (调用 QueryDefs,绑定参数并执行 - 代码中没有 SQL 显示)

    Set qdef = CurrentDb.QueryDefs("mySavedAppendQuery")
    
    qdef![e_id_param] = EMPLOYEE_ID.Value
    qdef.Execute
    
    Set qdef = Nothing
    
    
    Set qdef = CurrentDb.QueryDefs("mySavedDeleteQuery")
    
    qdef![e_id_param] = EMPLOYEE_ID.Value
    qdef![doc_id_param] = DOCUMENT_ID.Value
    qdef.Execute
    
    Set qdef = Nothing
    

【讨论】:

  • 非常感谢您的详细解释和指导!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-08
相关资源
最近更新 更多