如何通过 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) 如何从查询设计器创建临时表/过程/视图
如何让 ms-access 以其他用户身份连接到 ms-sql?
如何从 SQL Server 读取 MS Access 数据库以更新一个或多个表列中的数据?