2012-05-22 58 views
7

有谁知道使用外部实用程序(即bcp)从SQL 2008 而不是获取数据和Excel表格(VBA阵列)的最快方法吗? 请记住,我的数据集通常是6500-15000行,大约150-250列;我最终在一个自动化的VBA批处理脚本中传输了大约20-150个文件。将Excel表格数据传输到SQL 2008R2最快的方法

我已经尝试了获取大量的数据来自于2008年的SQL我在下面列出的Excel表(VBA)的几种方法:

方法1.传表到VBA数组和发送到存储过程( ADO) - 发送到SQL是SLOW

方法2.创建断开连接的RecordSet加载它,然后同步。 - 发送到SQL非常慢

方法3.将表放入VBA数组中,循环虽然数组和连接(使用分隔符)然后发送到存储过程。 - 发送到SQL SLOW,但快于方法1或2。

方法4.将表分成VBA数组,循环虽然阵列和串连(使用分隔符)然后将与ADO记录.addnew命令的每一行。 - 以非常快的速度发送到SQL(大约比方法1-3快20倍),但是现在我需要使用单独的过程分割数据,这会增加大量的等待时间。

方法5.将表放入VBA数组中,序列化为XML,以存储过程的形式发送到VARCHAR并在存储过程中指定XML。 - 发送到SQL INCREDIBLY SLOW(比方法1或2慢100倍)

我失踪了吗?

回答

2

没有一种最快的方法,因为它取决于许多因素。确保SQL中的索引已配置和优化。由于每个插入都需要更新索引,所以很多索引都会导致插入/更新性能不佳。确保您只连接到数据库,并且在操作期间不要打开/关闭它。当服务器负载最小时运行更新。您唯一没有尝试过的其他方法是使用ADO Command对象,并发出直接的INSERT语句。当使用记录集对象的'AddNew'方法时,请确保在插入结束时只发出一个'UpdateBatch'命令。除此之外,VBA只能运行接受输入的SQL服务器。编辑: 似乎你已经尝试了一切。在SQL Server中还有一种称为“Bulk-Logged”恢复模式,可以减少写入事务日志的开销。可能是值得研究的东西。这可能会很麻烦,因为它需要稍微摆弄数据库恢复模型,但它可能对您有用。

+0

-索引不是问题,因为我正在加载到临时表中 - 但是,谢谢,我已经忘记了这一点。一般只使用一个连接。至于ADO命令对象,我已经尝试过了,但发现速度没有传递给sp。是的,在循环添加新代码之后,我只发布了一个ADO更新批处理。 .add新方法过去和现在仍然是最快的,但只能在与concat结合使用时才能使用 - 这将在稍后进行解析。 – cshenderson

+0

我会研究批量记录恢复模式。 – cshenderson

0

到目前为止,最快的方法是通过T-SQL的BULK INSERT

有几点注意事项。

  • 您可能需要将数据导出为CSV第一(您可以直接从Excel导入,我的经验是从Access会.mdbs到SQL Server需要的中间步骤到csv )。
  • SQL Server计算机需要访问该csv(,当您运行BULK INSERT命令并指定文件名时,请记住文件名将在运行SQL Server的计算机上被解析为)。
  • 您可能需要调整默认值FIELDTERMINATORROWTERMINATOR以符合您的CSV。

对于我来说,最初需要一些试验和错误才能完成此设置,但与我尝试过的其他技术相比,性能提高非常显着。

+0

谢谢,但BCP不是一种选择。我在飞行中处理数千种格式,并且bcp给我带来的只是他们中的每一个都造成灾难的麻烦。我需要一些我可以在批处理循环中用错误反馈控制的东西;并消除了大多数批量程序......尤其是bcp。 – cshenderson

+0

我没有意识到BCP =='BULK INSERT'。也就是说,我最初自己也与BCP合作过类似的问题。我不知道你的情况的具体情况,但我解决了我使用自定义行和字段终止符以及在将数据导出到csv时“按摩”数据的问题。我尝试了您列出的大部分变体,并且性能从未接近大容量插入。我同意大容量插入是“挑剔的”(至少可以这么说),并且在批处理循环期间的错误反馈几乎是不可能的(无需诉诸某种混乱),但我认为值得再看一次。祝你好运! – mwolfe02

2

以下代码将在几秒钟(2-3秒)内传输数千个数据。

Dim sheet As Worksheet 
    Set sheet = ThisWorkbook.Sheets("DataSheet")   

    Dim Con As Object 
    Dim cmd As Object 
    Dim ServerName As String 
    Dim level As Long 
    Dim arr As Variant 
    Dim row As Long 
    Dim rowCount As Long 

    Set Con = CreateObject("ADODB.Connection") 
    Set cmd = CreateObject("ADODB.Command") 

    ServerName = "192.164.1.11" 

    'Creating a connection 
    Con.ConnectionString = "Provider=SQLOLEDB;" & _ 
            "Data Source=" & ServerName & ";" & _ 
            "Initial Catalog=Adventure;" & _ 
            "UID=sa; PWD=123;" 

    'Setting provider Name 
    Con.Provider = "Microsoft.JET.OLEDB.12.0" 

    'Opening connection 
    Con.Open     

    cmd.CommandType = 1    ' adCmdText 

    Dim Rst As Object 
    Set Rst = CreateObject("ADODB.Recordset") 
    Table = "EmployeeDetails" 'This should be same as the database table name. 
    With Rst 
     Set .ActiveConnection = Con 
     .Source = "SELECT * FROM " & Table 
     .CursorLocation = 3   ' adUseClient 
     .LockType = 4    ' adLockBatchOptimistic 
     .CursorType = 0    ' adOpenForwardOnly 
     .Open 

     Dim tableFields(200) As Integer 
     Dim rangeFields(200) As Integer 

     Dim exportFieldsCount As Integer 
     exportFieldsCount = 0 

     Dim col As Integer 
     Dim index As Integer 
     index = 1 

     For col = 1 To .Fields.Count 
      exportFieldsCount = exportFieldsCount + 1 
      tableFields(exportFieldsCount) = col 
      rangeFields(exportFieldsCount) = index 
      index = index + 1 
     Next 

     If exportFieldsCount = 0 Then 
      ExportRangeToSQL = 1 
      GoTo ConnectionEnd 
     End If    

     endRow = ThisWorkbook.Sheets("DataSheet").Range("A65536").End(xlUp).row 'LastRow with the data. 
     arr = ThisWorkbook.Sheets("DataSheet").Range("A1:CE" & endRow).Value 'This range selection column count should be same as database table column count. 

     rowCount = UBound(arr, 1)    

     Dim val As Variant 

     For row = 1 To rowCount 
      .AddNew 
      For col = 1 To exportFieldsCount 
       val = arr(row, rangeFields(col)) 
        .Fields(tableFields(col - 1)) = val 
      Next 
     Next 

     .UpdateBatch 
    End With 

    flag = True 

    'Closing RecordSet. 
    If Rst.State = 1 Then 
     Rst.Close 
    End If 

    'Closing Connection Object. 
    If Con.State = 1 Then 
     Con.Close 
    End If 

'Setting empty for the RecordSet & Connection Objects 
Set Rst = Nothing 
Set Con = Nothing 
End Sub 
+0

这似乎是从SQL Server将数据写入Excel,而不是其他方式,因为问题在问。 – thursdaysgeek

+1

@thursdaysgeek不,这正是问题所要求的。 –

0

作品精致漂亮,而另一方面,以提高速度,我们仍然可以修改查询:

相反:Source = "SELECT * FROM " & Table

我们可以使用:Source = "SELECT TOP 1 * FROM " & Table

这里我们只需要列名称。因此,不需要为整个表做一个查询,只要导入新的数据就可以扩展整个表。

0

据我所知,你可以创建一个链接到Excel文件的服务器(只要服务器可以找到路径;最好将文件放在服务器的本地磁盘上),然后使用SQL来检索数据从中。

0

刚刚尝试了一些方法,我回到了一个相对简单但快速的方法。这很快,因为它使SQL服务器能够完成所有工作,包括高效的执行计划。

我只是建立一个包含INSERT语句脚本的长字符串。

Public Sub Upload() 
     Const Tbl As String = "YourTbl" 
     Dim InsertQuery As String, xlRow As Long, xlCol As Integer 
     Dim DBconnection As New ADODB.Connection 

     DBconnection.Open "Provider=SQLOLEDB.1;Password=MyPassword" & _ 
      ";Persist Security Info=false;User ID=MyUserID" & _ 
      ";Initial Catalog=MyDB;Data Source=MyServer" 

     InsertQuery = "" 
     xlRow = 2 
     While Cells(xlRow, 1) <> "" 
      InsertQuery = InsertQuery & "INSERT INTO " & Tbl & " VALUES('" 

      For xlCol = 1 To 6 'Must match the table structure 
       InsertQuery = InsertQuery & Replace(Cells(xlRow, xlCol), "'", "''") & "', '" 'Includes mitigation for apostrophes in the data 
      Next xlCol 
      InsertQuery = InsertQuery & Format(Now(), "M/D/YYYY") & "')" & vbCrLf 'The last column is a date stamp, either way, don't forget to close that parenthesis 
      xlRow = xlRow + 1 
     Wend 

     DBconnection.Execute InsertQuery 'I'll leave any error trapping to you 
     DBconnection.Close 'But do be tidy :-) 
     Set DBconnection = Nothing 
    End Sub 
相关问题