处理大量插入到Access的ODBC链接表时,这种情况并不罕见。在以下Access查询中的情况如下:
INSERT INTO METER_DATA (MPO_REFERENCE)
SELECT MPO_REFERENCE FROM tblTempSmartSSP
其中[METER_DATA]是ODBC链接表,[tblTempSmartSSP]是本地(原生)Access表。由于Access数据库引擎必须能够适应各种目标数据库的能力差异,因此对ODBC链接表的处理受到一定限制。不幸的是,尽管单个Access SQL语句可以完成操作,但实际上发送到远程(链接)数据库的内容可能是每行都有一个独立的INSERT(或等效的操作)。可以理解的是,如果本地表包含大量行,则这可能会导致非常缓慢。
选项1:将数据原生批量插入到远程数据库
所有数据库都有一种或多种原生机制用于批量加载数据:Microsoft SQL Server具有“bcp”和BULK INSERT
,Oracle具有“SQL*Loader”。这些机制针对批量操作进行了优化,并通常提供显着的速度优势。事实上,如果需要将数据导入Access并在传输到远程数据库之前进行“修改”,则将修改后的数据转储回文本文件,然后批量导入到远程数据库仍然可能更快。
选项2(a):使用Python和pandas
使用fast_executemany=True
的pyodbc可以比在链接表上使用INSERT INTO … SELECT …
更快地上传行。有关详细信息,请参见此答案。
选项2(b):在Access中使用透传查询
如果原生批量导入机制不可行,则另一种可能性是在Access中构建一个或多个透传查询,使用可以一次插入多行的INSERT语句上传数据。
例如,如果远程数据库是SQL Server(2008或更高版本),则我们可以运行一个Access透传(T-SQL)查询,如下所示:
INSERT INTO METER_DATA (MPO_REFERENCE) VALUES (1), (2), (3)
使用一个INSERT语句插入三行数据。
根据这里对另一个早期问题的回答,Oracle的相应语法为:
INSERT ALL
INTO METER_DATA (MPO_REFERENCE) VALUES (1)
INTO METER_DATA (MPO_REFERENCE) VALUES (2)
INTO METER_DATA (MPO_REFERENCE) VALUES (3)
SELECT * FROM DUAL;
由于我没有访问Oracle数据库,因此我使用SQL Server对本方法进行测试,使用了一个包含10,000行的[tblTempSmartSSP]本地表。 代码...
Sub LinkedTableTest()
Dim cdb As DAO.Database
Dim t0 As Single
t0 = Timer
Set cdb = CurrentDb
cdb.Execute _
"INSERT INTO METER_DATA (MPO_REFERENCE) " & _
"SELECT MPO_REFERENCE FROM tblTempSmartSSP", _
dbFailOnError
Set cdb = Nothing
Debug.Print "Elapsed time " & Format(Timer - t0, "0.0") & " seconds."
End Sub
在我的测试环境中,这段代码执行大约需要100秒。
相比之下,以下代码使用了微软所称的表值构造函数来构建多行插入语句...
Sub PtqTest()
Dim cdb As DAO.Database, rst As DAO.Recordset
Dim t0 As Single, i As Long, valueList As String, separator As String
t0 = Timer
Set cdb = CurrentDb
Set rst = cdb.OpenRecordset("SELECT MPO_REFERENCE FROM tblTempSmartSSP", dbOpenSnapshot)
i = 0
valueList = ""
separator = ""
Do Until rst.EOF
i = i + 1
valueList = valueList & separator & "(" & rst!MPO_REFERENCE & ")"
If i = 1 Then
separator = ","
End If
If i = 1000 Then
SendInsert valueList
i = 0
valueList = ""
separator = ""
End If
rst.MoveNext
Loop
If i > 0 Then
SendInsert valueList
End If
rst.Close
Set rst = Nothing
Set cdb = Nothing
Debug.Print "Elapsed time " & Format(Timer - t0, "0.0") & " seconds."
End Sub
Sub SendInsert(valueList As String)
Dim cdb As DAO.Database, qdf As DAO.QueryDef
Set cdb = CurrentDb
Set qdf = cdb.CreateQueryDef("")
qdf.Connect = cdb.TableDefs("METER_DATA").Connect
qdf.ReturnsRecords = False
qdf.sql = "INSERT INTO METER_DATA (MPO_REFERENCE) VALUES " & valueList
qdf.Execute dbFailOnError
Set qdf = Nothing
Set cdb = Nothing
End Sub
相比之下,产生相同结果需要花费1到2秒的时间。
(T-SQL表值构造函数一次只能插入1000行,所以上述代码比通常情况下更加复杂。)