Самый быстрый способ переноса данных таблицы Excel в SQL 2008R2

Кто-нибудь знает самый быстрый способ получить данные из таблицы Excel (массив VBA) в таблицу на SQL 2008 без с помощью внешней утилиты (например, bcp)? Имейте в виду, что мои наборы данных обычно состоят из 6500-15000 строк и примерно 150-250 столбцов; и я в конечном итоге передаю около 20–150 из них во время автоматического пакетного сценария VBA.

Я пробовал несколько методов для получения больших объемов данных из таблицы Excel (VBA) в SQL 2008. Я перечислил их ниже:

Метод 1. Передать таблицу в массив VBA и отправить в хранимую процедуру (ADO) - отправка в SQL выполняется МЕДЛЕННО.

Способ 2. Создайте отключенный RecordSet, загрузите его, затем синхронизируйте. - Отправка в SQL ОЧЕНЬ МЕДЛЕННАЯ

Метод 3. Поместите таблицу в массив VBA, выполните цикл по массиву и объедините (используя разделители), а затем отправьте в хранимую процедуру. - Отправка в SQL SLOW, но быстрее, чем метод 1 или 2.

Метод 4. Поместите таблицу в массив VBA, выполните цикл по массиву и объедините (используя разделители), затем поместите каждую строку с помощью команды ADO recordset .addnew. - Отправка в SQL очень БЫСТРАЯ (примерно в 20 раз быстрее, чем методы 1-3), но теперь мне нужно будет разделить эти данные с помощью отдельной процедуры, что приведет к значительному увеличению времени ожидания.

Метод 5. Поместите таблицу в массив VBA, сериализуйте в XML, отправьте в хранимую процедуру как VARCHAR и укажите XML в хранимой процедуре. - Отправка в SQL НЕВЕРОЯТНО МЕДЛЕННАЯ (примерно в 100 раз медленнее, чем методы 1 или 2)

Что-то мне не хватает?


person cshenderson    schedule 22.05.2012    source источник


Ответы (6)


Не существует единственно быстрого способа, поскольку он зависит от ряда факторов. Убедитесь, что индексы в SQL настроены и оптимизированы. Большое количество индексов снизит производительность вставки / обновления, поскольку каждая вставка должна будет обновлять индекс. Убедитесь, что вы устанавливаете только одно соединение с базой данных и не открываете / не закрываете ее во время операции. Запускайте обновление при минимальной нагрузке на сервер. Единственный другой метод, который вы не пробовали, - это использовать объект ADO Command и выполнить прямую инструкцию INSERT. При использовании метода «AddNew» объекта набора записей обязательно введите только одну команду «UpdateBatch» в конце вставок. Если не считать этого, VBA может работать только с той скоростью, с которой сервер SQL принимает входные данные.

РЕДАКТИРОВАТЬ: Похоже, вы все перепробовали. В SQL Server есть также так называемый режим восстановления с неполным протоколированием, который снижает накладные расходы на запись в журнал транзакций. Может быть, стоит посмотреть. Это может быть проблематично, поскольку требует немного поработать с моделью восстановления базы данных, но это может быть полезно для вас.

person Fink    schedule 22.05.2012
comment
-Index не проблема, потому что я загружаюсь во временную таблицу - но спасибо, я забыл об этом. Определенно используя только одно соединение. Что касается объекта команды ADO, я пробовал это, но обнаружил, что он не отличается по скорости от передачи sp. И да, я выпустил только один пакет обновлений ADO после цикла добавления нового. Новый метод .add был и остается самым быстрым, но только при использовании в сочетании с concat, который нужно будет проанализировать позже. - person cshenderson; 23.05.2012
comment
Я посмотрю в режим восстановления с неполным протоколированием. - person cshenderson; 24.05.2012

Следующий код передаст тысячи данных всего за несколько секунд (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
person SaiKiran Mandhala    schedule 12.12.2013
comment
Это похоже на запись данных в Excel из SQL Server, а не наоборот, как задается вопрос. - person thursdaysgeek; 08.05.2017
comment
@thursdaysgeek нет, это именно то, что задает вопрос. - person SaiKiran Mandhala; 13.08.2017

Безусловно, самый быстрый способ сделать это - использовать BULK INSERT T-SQL.

Есть несколько предостережений.

  • Скорее всего, вам сначала потребуется экспортировать свои данные в CSV (вы можете импортировать напрямую из Excel; мой опыт заключается в переходе с Access .mdbs на SQL Server, что требует промежуточного шага в CSV) .
  • Компьютер с SQL Server должен иметь доступ к этому CSV (, когда вы запускаете команду BULK INSERT и указываете имя файла, помните, что имя файла будет разрешено на машине, на которой запущен SQL Server).
  • Возможно, вам придется настроить значения по умолчанию FIELDTERMINATOR и ROWTERMINATOR, чтобы они соответствовали вашему CSV.

Мне потребовались некоторые методы проб и ошибок, чтобы настроить эту настройку изначально, но повышение производительности было феноменальным по сравнению с любыми другими методами, которые я пробовал.

person mwolfe02    schedule 22.05.2012
comment
Спасибо, но BCP не вариант. Я работаю с тысячами форматов на лету, и bcp не принес мне ничего, кроме проблем с достаточным количеством форматов, каждый раз приводившим к катастрофе. Мне нужно что-то, что я могу контролировать с помощью обратной связи об ошибках во время пакетного цикла; и это устраняет большинство массовых программ ... особенно bcp. - person cshenderson; 23.05.2012
comment
Я не понимал BCP == BULK INSERT. Тем не менее, у меня самого изначально были похожие проблемы, когда я работал с BCP. Я не знаю специфики вашей ситуации, но я решил проблемы, которые у меня были, используя настраиваемые терминаторы строк и полей и массируя данные, когда я экспортировал их в csv. Я пробовал вариации большинства из того, что вы перечислили, и по производительности ни разу не приблизился к Bulk Insert. Я согласен с тем, что Bulk Insert привередлив (мягко говоря), и обратная связь об ошибках во время пакетного цикла практически невозможна (без использования какого-то кладжа), но я думаю, что на это стоит еще раз взглянуть. Удачи! - person mwolfe02; 23.05.2012

работает довольно хорошо, с другой стороны, для повышения скорости мы все же можем изменить запрос:

Вместо этого: Source = "SELECT * FROM " & Table

Мы можем использовать: Source = "SELECT TOP 1 * FROM " & Table

Здесь нам нужны только имена столбцов. Таким образом, нет необходимости создавать запрос для всей таблицы, которая расширяет процесс до тех пор, пока импортируются новые данные.

person FKSP    schedule 12.08.2015

Насколько я помню, вы можете создать связанный сервер с файлом Excel (если сервер может найти путь; лучше всего поместить файл на локальный диск сервера), а затем использовать SQL для извлечения данных из него.

person darlove    schedule 15.03.2017

Попробовав несколько методов, я вернулся к относительно простому, но быстрому. Это быстро, потому что заставляет 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
person Mind Driver    schedule 20.12.2017