La forma más rápida de transferir datos de tablas de Excel a SQL 2008R2

¿Alguien sabe la forma más rápida de obtener datos de una tabla de Excel (VBA Array) en una tabla en SQL 2008? sin utilizando una utilidad externa (es decir, bcp)? Tenga en cuenta que mis conjuntos de datos suelen tener entre 6500 y 15000 150 filas y entre 250 y 20 columnas; y termino transfiriendo alrededor de 150-XNUMX de ellos durante un script por lotes automatizado de VBA.

He probado varios métodos para obtener grandes cantidades de datos de una tabla de Excel (VBA) a SQL 2008. Los he enumerado a continuación:

Método 1. Pase la tabla a VBA Array y envíela al procedimiento almacenado (ADO): el envío a SQL es LENTO

Método 2. Cree un conjunto de registros desconectado, cárguelo y luego sincronícelo. -- Envío a SQL MUY LENTO

Método 3. Coloque la tabla en la matriz de VBA, recorra la matriz y concatene (usando delimitadores) y luego envíela al procedimiento almacenado. -- Envío a SQL LENTO, pero más rápido que el Método 1 o 2.

Método 4. Coloque la tabla en la matriz VBA, recorra la matriz y concatene (usando delimitadores), luego coloque cada fila con el comando ADO recordset .addnew. --Enviando a SQL muy RÁPIDO (alrededor de 20 veces más rápido que los métodos 1-3), pero ahora necesitaré dividir esos datos usando un procedimiento separado, lo que agregará un tiempo de espera significativo.

Método 5. Coloque la tabla en la matriz VBA, serialice en XML, envíe al procedimiento almacenado como VARCHAR y especifique XML en el procedimiento almacenado. --Enviar a SQL INCREÍBLEMENTE LENTO (alrededor de 100 veces más lento que los métodos 1 o 2)

¿Algo que me estoy perdiendo?

preguntado el 22 de mayo de 12 a las 19:05

6 Respuestas

No existe una única forma más rápida, ya que depende de una serie de factores. Asegúrese de que los índices en SQL estén configurados y optimizados. Muchos índices eliminarán el rendimiento de inserción/actualización, ya que cada inserción deberá actualizar el índice. Asegúrese de hacer una sola conexión a la base de datos y no la abra ni la cierre durante la operación. Ejecute la actualización cuando el servidor tenga una carga mínima. El único otro método que no ha probado es usar un objeto de comando ADO y emitir una instrucción INSERT directa. Cuando utilice el método 'AddNew' del objeto de conjunto de registros, asegúrese de ejecutar solo un comando 'UpdateBatch' al final de las inserciones. Aparte de eso, el VBA solo puede ejecutarse tan rápido como el servidor SQL que acepta las entradas.

EDITAR: Parece que lo has intentado todo. También existe lo que se conoce como modo de recuperación de "registro masivo" en SQL Server, que reduce la sobrecarga de escribir tanto en el registro de transacciones. Podría ser algo que valga la pena investigar. Puede ser problemático ya que requiere jugar un poco con el modelo de recuperación de la base de datos, pero podría ser útil para usted.

contestado el 23 de mayo de 12 a las 15:05

-El índice no es un problema porque estoy cargando en una tabla temporal, pero gracias, me había olvidado de eso. Definitivamente usando solo una conexión. En cuanto al objeto de comando ADO, lo intenté pero descubrí que no es diferente en velocidad que pasar a un sp. Y sí, emití solo un lote de actualización de ADO después de recorrer el add new. El nuevo método .add fue y sigue siendo, con mucho, el más rápido, pero solo cuando se usa junto con el concat, que tendría que analizarse más tarde. - cshenderson

Veré el modo de recuperación de registro masivo. - cshenderson

El siguiente código transferirá miles de datos en solo unos segundos (2-3 segundos).

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

Respondido el 12 de diciembre de 13 a las 10:12

Esto parece estar escribiendo datos en Excel desde SQL Server, no al revés, como se hace la pregunta. - jueves friki

@thursdaysgeek no, esto hace exactamente lo que hace la pregunta. - SaiKiran Mandhala

Con mucho, la forma más rápida de hacer esto es a través de T-SQL BULK INSERT.

Hay algunas advertencias.

  • Es probable que primero necesite exportar sus datos a un csv (es posible que pueda importar directamente desde Excel; mi experiencia es pasar de Access .mdbs a SQL Server, lo que requiere el paso intermedio a csv).
  • La máquina de SQL Server necesita tener acceso a ese csv (cuando ejecutas el BULK INSERT comando y especifique un nombre de archivo, recuerde que el nombre de archivo se resolverá en la máquina donde se ejecuta SQL Server).
  • Es posible que deba modificar el valor predeterminado FIELDTERMINATOR y ROWTERMINATOR valores para que coincidan con su CSV.

Me costó un poco de prueba y error configurar esto inicialmente, pero el aumento del rendimiento fue fenomenal en comparación con cualquier otra técnica que había probado.

contestado el 22 de mayo de 12 a las 21:05

Gracias, pero BCP no es una opción. Trato con miles de formatos sobre la marcha y bcp no me ha dado más que problemas con los suficientes para causar un desastre cada vez. Necesito algo que pueda controlar con comentarios de error durante el ciclo por lotes; y eso elimina la mayoría de los programas masivos... especialmente bcp. - cshenderson

No me di cuenta de BCP == BULK INSERT. Dicho esto, tuve problemas similares al trabajar con BCP inicialmente. No conozco los detalles de su situación, pero resolví los problemas que tenía usando terminadores de fila y campo personalizados y "masajeando" los datos cuando los exporté a csv. Probé variaciones de la mayoría de lo que enumeró y el rendimiento nunca estuvo cerca del Bulk Insert. Estoy de acuerdo en que Bulk Insert es "quisquilloso" (por decir lo menos) y que la retroalimentación de errores durante el ciclo por lotes es casi imposible (sin recurrir a algún tipo de chapuza), pero creo que vale la pena echarle otro vistazo. ¡La mejor de las suertes! - mwolfe02

funciona bastante bien, por otro lado, para mejorar la velocidad, aún podemos modificar la consulta:

En lugar: Source = "SELECT * FROM " & Table

Nosotros podemos usar: Source = "SELECT TOP 1 * FROM " & Table

Aquí solo necesitamos los nombres de las columnas. Por lo tanto, no es necesario realizar una consulta para toda la tabla, lo que extiende el proceso siempre que se importen nuevos datos.

Respondido 12 ago 15, 07:08

Por lo que recuerdo, puede crear un servidor vinculado al archivo de Excel (siempre que el servidor pueda encontrar la ruta; es mejor colocar el archivo en el disco local del servidor) y luego usar SQL para recuperar datos de él.

respondido 15 mar '17, 16:03

Habiendo probado algunos métodos, volví a uno relativamente simple pero rápido. Es rápido porque hace que el servidor SQL haga todo el trabajo, incluido un plan de ejecución eficiente.

Simplemente construyo una cadena larga que contiene un script de instrucciones 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

Respondido el 21 de diciembre de 17 a las 01:12

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.