如何通过 MS ACCESS 表执行 SQL Server 表的批量更新

Posted

技术标签:

【中文标题】如何通过 MS ACCESS 表执行 SQL Server 表的批量更新【英文标题】:How to execute bulk update of SQL Server table by MS ACCESS table 【发布时间】:2014-10-09 17:04:09 【问题描述】:

我需要从 MS ACCESS 表更新 SQL Server 中的数百万条记录。 我使用 MS ACCESS 作为前端,使用 SQL 作为链接表。 因此,据我了解,我需要创建传递查询来执行以下操作:

更新 SQLtbl SET SQLtbl.col1 = MDBtbl.Col1 从 SQLtbl 内部联接 MDBtbl ON SQLtbl.ID = MDBtbl.ID WHERE SQLtbl.col1 != MDBtbl.Col1

它工作得非常慢,所以我需要以某种方式将 int 转换为 BULK UPDATE 请指教, 谢谢

【问题讨论】:

【参考方案1】:

与最近的一个相关问题 here 类似,这是另一种情况,将 ODBC 链接表视为本机 Access 表会导致性能下降。

对于名为 [SQLtbl](ODBC 链接到 SQL Server)和 [MDBtbl](本机 Access)的两个相同的 Access 表,每个表有 9999 行,执行以下代码大约需要 5.5 分钟:

Sub UpdateViaJoin()
    Dim con As ADODB.Connection
    Dim t0 As Single

    Set con = CurrentProject.Connection
    con.CommandTimeout = 0
    t0 = Timer
    con.Execute _
            "UPDATE " & _
                "SQLtbl INNER JOIN MDBtbl " & _
                    "ON SQLtbl.ID = MDBtbl.ID " & _
            "SET SQLtbl.Col1 = MDBtbl.Col1"
    Debug.Print Format(Timer - t0, "0.0") & " seconds"
    Set con = Nothing
End Sub

为了查看 JOIN 本身是否有问题,我运行了以下命令,只用了 5 分钟多的时间就完成了:

Sub UbdateViaDLookup()
    Dim cdb As DAO.Database
    Dim t0 As Single

    Set cdb = CurrentDb
    t0 = Timer
    cdb.Execute _
            "UPDATE SQLtbl SET Col1 = DLookup(""Col1"", ""MDBtbl"", ""ID="" & ID)"
    Debug.Print Format(Timer - t0, "0.0") & " seconds"
    Set cdb = Nothing
End Sub

另一方面,以下使用直通查询和原生 T-SQL 准备语句的代码始终在 2 秒内运行(即快 100 倍以上):

Sub UpdateViaPassThroughQuery()
    Dim cdb As DAO.Database, rst As DAO.Recordset, qdf As DAO.QueryDef
    Dim SQL As String, statementHandle As Long, i As Long, updateList As String
    Dim t0 As Single

    Set cdb = CurrentDb
    t0 = Timer

    SQL = "SET NOCOUNT ON;"
    SQL = SQL & "DECLARE @statementHandle int;"
    SQL = SQL & "EXEC sp_prepare @statementHandle OUTPUT, N'@P1 nvarchar(50), @P2 int', N'UPDATE SQLtbl SET Col1=@P1 WHERE ID=@P2';"
    SQL = SQL & "SELECT @statementHandle;"
    Set qdf = cdb.CreateQueryDef("")
    qdf.Connect = cdb.TableDefs("SQLtbl").Connect
    qdf.SQL = SQL
    qdf.ReturnsRecords = True
    Set rst = qdf.OpenRecordset(dbOpenSnapshot)
    statementHandle = rst(0).Value
    rst.Close

    Set rst = cdb.OpenRecordset("SELECT ID, Col1 FROM MDBtbl", dbOpenSnapshot)
    i = 0
    updateList = ""
    Do Until rst.EOF
        i = i + 1
        updateList = updateList & "EXEC sp_execute " & statementHandle & ", N'" & Replace(rst!Col1, "'", "''") & "', " & rst!id & ";"
        If i = 1000 Then
            qdf.SQL = updateList
            qdf.ReturnsRecords = False
            qdf.Execute
            i = 0
            updateList = ""
        End If
        rst.MoveNext
    Loop
    If i > 0 Then
        qdf.SQL = updateList
        qdf.ReturnsRecords = False
        qdf.Execute
    End If
    rst.Close
    Set rst = Nothing

    qdf.SQL = "EXEC sp_unprepare " & statementHandle & ";"
    qdf.ReturnsRecords = False
    qdf.Execute
    Set qdf = Nothing
    Debug.Print Format(Timer - t0, "0.0") & " seconds"
    Set cdb = Nothing
End Sub

编辑

要调整上述代码以处理 Null,您需要更新该行 ...

updateList = updateList & "EXEC sp_execute " & statementHandle & ", N'" & Replace(rst!Col1, "'", "''") & "', " & rst!id & ";"

...到...

updateList = updateList & "EXEC sp_execute " & statementHandle & ", " & _
        FormatArgForPrepStmt(rst!Col1) & ", " & _
        rst!id & ";"

...并添加一些类似这样的格式化功能:

Private Function FormatArgForPrepStmt(item As Variant) As String
    If IsNull(item) Then
        FormatArgForPrepStmt = "NULL"
    Else
        Select Case VarType(item)
            Case vbString
                FormatArgForPrepStmt = "N'" & Replace(item, "'", "''") & "'"
            Case vbDate
                FormatArgForPrepStmt = "N'" & Format(item, "yyyy-mm-dd Hh:Nn:Ss") & "'"
            Case Else
                FormatArgForPrepStmt = CStr(item)
        End Select
    End If
End Function

【讨论】:

非常感谢!真的很有帮助!但是如何处理这种更新呢?更新 SQLtbl INNER JOIN MDBtbl ON SQLtbl.ID = MDBtbl.ID SET SQLtbl.col1 = MDBtbl.Col1 提前谢谢! @user3920056 这正是第三个代码示例所做的。它使用sp_prepare 在 SQL Server 上为 [SQLtbl] 创建 UPDATE 准备语句,循环遍历 [MDBtbl] 中的行以创建和执行 sp_execute 一次调用 1000,然后调用 sp_unprepare 丢弃 T -SQL 准备好的语句。如果您需要更多关于sp_prepare 的背景信息,请查看here。 谢谢,它有效!但这是另一个问题......当我只更新 500 条记录的一列执行大约需要 2 秒时,我刚刚添加了 5 个要更新的字段,执行时间约为 20 秒,例如UPDATE SQLtbl SET Col1=@P1, Col2=@P2,...WHERE ID=@P0 但是我要更新的表包含 100-150 列。实际上,我需要将它们全部添加。有没有办法加快这个过程? 另一件事......一旦我开始添加更多要更新的列,我收到一条消息“系统资源超出”所以我不得不输入 i = 100 或者将来甚至 i = 5 原因到目前为止,我还没有添加所有列。但它肯定比 JOIN 好得多 还有一个......有些列有空值,所以更新在某些行上失败,但适用于 JOIN。有没有办法以某种方式处理具有空值的列?提前谢谢你【参考方案2】:

海报明确要求:

“I understand I need to crate pass-though queries”.

所以我们需要解决如何创建和运行传递查询。

简单的解决方案是将您的查询保存为传递。 事实上,您可以将现有查询“更改”为传递。

在设计视图中显示您现有的查询,然后点击 pass-thought 按钮。

例如:

如果您将查询保存为 pass-though,那么您将运行服务器端 t-sql。

请注意,点击传递按钮,然后这也会显示属性表。我圈出了传递按钮和属性表中所需的 ODBC 设置。实际上,您可以通过剪切 + 粘贴从链接表中复制 odbc 连接字符串。

因此: 无需在代码中设置连接字符串 无需编写 VBA 代码。 事实上,如果需要重新编写 sql,它应该可以像原生 t-sql 代码一样工作。

因此在这里实现了相当理想的目标:

您可以重复使用 EXISTING 查询。

您不必重新编写查询。

您不必采用和编写一大堆代码。

您甚至不必了解 VBA

您不会将全新的 ADO 对象模型库引入现有应用程序。现有应用程序不太可能使用 ADO 对象库。您现在必须将这个全新的对象库引入您现有的应用程序。因此,添加这个新的引用和 VBA 代码将产生破坏现有代码库并将错误引入现有代码库的巨大潜力,如果现有代码不符合 DAO 记录集的条件,情况尤其如此。

我认为运行一次通过式更新,简单地使用一次通过式查询并单击功能区然后引入全新的 ADO 引用和对象库然后引入一个更简单和无风险一堆 VBA ADO 代码来简单地运行一个简单的更新查询。

【讨论】:

这一切都很好,但是如果 [MDBtbl] 是一个本地 Access 表,那么 SQL Server 在运行保存的传递查询时如何“看到”它? (顺便说一句,我的第一个代码示例使用 ADO .Execute 的唯一原因是因为当我尝试使用 DAO .Execute 时,它始终崩溃。至少 ADO .Execute 最终完成了。) 我根据我的代码调整了您的代码,效果非常好。我在一行中更新多达 150 列,这使我可以更新大约。 2,500 条记录/分钟,目前还不错。是否可以修改您的函数以将其用于从 MDBtbl 到不存在的 SQL tbl 的 INSERT 记录?例如通过使用相同的技术。谢谢

以上是关于如何通过 MS ACCESS 表执行 SQL Server 表的批量更新的主要内容,如果未能解决你的问题,请参考以下文章

MS Access 数据库 (2010) 如何从查询设计器创建临时表/过程/视图

使用 SQL 向 MS Access 中的多个表添加列

如何让 ms-access 以其他用户身份连接到 ms-sql?

如何从 SQL Server 读取 MS Access 数据库以更新一个或多个表列中的数据?

当用户在 MS Access 中修改表中的另一列时,如何在 SQL Server 中将列设置为今天的日期 [关闭]

如何使用 SQL 重命名具有关系的 MS Access 表?