Цикл запроса Excel VBA ADO для слишком большого количества строк

Я пытаюсь выполнить запрос на листе Excel, как делал много раз, но теперь данные содержат более 70 тыс. строк. Обычно я получаю сообщение о том, что он не может найти таблицу, если это так, чего и следовало ожидать, поскольку я думаю, что она перестает работать примерно на 65 тыс. строк или около того.

Итак, вместо этого я пытаюсь выполнить цикл, в котором в первой части цикла я запускаю первые 60 000 строк, и на каждой итерации цикла выполняется еще одна партия из 60 000, пока не закончится последний набор. Цикл создает новый лист с данными для работы, поэтому я могу иметь заголовки столбцов с набором данных. Кажется, он работает до той части, где он запускает новый запрос к данным из нового листа. Это дает мне ошибку, что «Ядро базы данных Microsoft Access не может найти объект» (имя моей таблицы)... и т. д.

В моем конкретном примере это таблица «Sheet1$A1:N12790», где 12790 — это остаточное количество строк из более чем 70 тыс. строк, а Sheet1 — это лист, который создается при запуске кода.

Итак, я совершенно не понимаю, почему он выдает эту ошибку, когда обычно это происходит только в том случае, если строк слишком много или таблица определенно не существует.

Я попытался запустить простой Select * from [Sheet1$A1:N12790] с отдельным сабвуфером, и он отлично работает. Это наводит меня на мысль, что, возможно, у excel не хватает памяти, возможно, после выполнения первого? Но я понятия не имею, что с этим делать, и в Интернете очень мало информации об этом, поскольку это настолько специфично и редко, поскольку большинство людей на данный момент просто используют обычную базу данных.

Спасибо!

ОБНОВЛЕНИЕ: я тестировал много вещей. Я попытался создать тестовый модуль для обработки нового листа (как описано выше), и он работает при отдельном запуске, но если я попытаюсь заставить основной модуль выйти из цикла раньше, а затем вызвать новый тестовый модуль для запуска того, что я хочу это сделать, это дает мне ту же ошибку. Итак, опять же, оба сабвуфера отлично работают по отдельности, но я не могу использовать один для вызова другого. Показывает мне больше доказательств того, что речь идет не столько о кодировании, сколько о каком-то усложнении обработки, но я все еще просто выдвигаю теории.

Обновление 2: Спасибо за все идеи и предложения до сих пор (20.06.18). Вот скриншот того, что говорит ошибка, когда она запускается во второй раз и пытается запустить MySQL:

Сообщение об ошибке:

Сообщение об ошибке

Вот мой код ниже, если это полезно:

Sub Risk_Init_Pivot(FA_PQ, Risk_Init, SubChannel, MyMonth As String)

    Application.ScreenUpdating = False

    Dim SheetRange1 As Range, SheetRange2 As Range, SheetRange3 As Range, MyRange As Range
    Dim TargetSheetTable As String, SheetTable1 As String
    Dim SR1_LastRow As Double, SR1_LastColumn As Double, NewRowCount As Double, SR1_FirstRow As Double
    Dim i As Integer, j As Integer, MyLoop As Integer
    Dim Table1 As String, MySQL As String
    Dim MySheet1 As Worksheet, MySheet2 As Worksheet
    Dim MyConn As ADODB.Connection
    Dim MyRecordSet As ADODB.Recordset

    TargetSheetTable = "Risk Init Pivot"
    SheetTable1 = "Fanned File"

    'Initiate
    ActiveWorkbook.Sheets(TargetSheetTable).Activate

    If ActiveSheet.AutoFilterMode Then
        If ActiveSheet.FilterMode Then ActiveSheet.ShowAllData
    End If

    ActiveSheet.Cells.ClearContents

    'Find Range Coordinates Dynamically
    ActiveWorkbook.Sheets(SheetTable1).Activate

    If ActiveSheet.AutoFilterMode Then
        If ActiveSheet.FilterMode Then ActiveSheet.ShowAllData
    End If

    Range("A1").Select
    Selection.End(xlDown).Select
    SR1_LastRow = Selection.Row
    ActiveCell.SpecialCells(xlLastCell).Select
    SR1_LastColumn = Selection.Column
    Range("A1").Select

    MyLoop = WorksheetFunction.RoundUp(SR1_LastRow / 60000, 0)

    NewRowCount = 0

    For j = 1 To MyLoop

        'Set Up Connection Details
        Set MyConn = New ADODB.Connection
        MyConn.CommandTimeout = 0
        Set MyRecordSet = New ADODB.Recordset

        MyConn.Open "Provider = Microsoft.ACE.OLEDB.12.0;" & _
        "Data Source = " & Application.ThisWorkbook.FullName & ";" & _
        "Extended Properties = ""Excel 12.0 Xml;HDR=YES;IMEX=1"";"
        Set MyRecordSet.ActiveConnection = MyConn

        'First Time
        If SR1_LastRow > 60000 Then
            NewRowCount = SR1_LastRow - 60000
            SR1_LastRow = 60000
            SR1_FirstRow = 1

            'Set the tables equal to the respective ranges
            Set SheetRange1 = ActiveWorkbook.Sheets(SheetTable1).Range("A" & SR1_FirstRow & ":" & Cells(SR1_LastRow, SR1_LastColumn).Address)

            'Pass the table address to a string
            Table1 = SheetRange1.Address

            'Convert the string into a query table - have to get rid of dollar signs for it to work
            Table1 = "[" & SheetTable1 & "$" & Replace(Table1, "$", "") & "]"

        'Does this until NewRowCount falls into last time
        ElseIf NewRowCount > 60000 Then
            NewRowCount = NewRowCount - 60000
            SR1_FirstRow = SR1_LastRow + 1
            SR1_LastRow = SR1_LastRow + 60000

            Set MySheet1 = Sheets(SheetTable1)
            Sheets.Add After:=MySheet1
            Set MySheet2 = ActiveSheet

            MySheet1.Activate
            Rows("1:1").Select
            Selection.Copy
            MySheet2.Activate
            Rows("1:1").Select
            Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks:=False, Transpose:=False

            MySheet1.Activate
            ActiveSheet.Range("A" & SR1_FirstRow & ":" & Cells(SR1_LastRow, SR1_LastColumn).Address).Copy
            MySheet2.Activate
            ActiveSheet.Range("A2").PasteSpecial xlPasteValues
            Range("A1").Select
            Range(Selection, Selection.End(xlDown)).Select
            Range(Selection, Selection.End(xlToRight)).Select
            Set MyRange = Selection

            'Set the tables equal to the respective ranges
            Table1 = Selection.Address

            'Convert the string into a query table - have to get rid of dollar signs for it to work
            Table1 = "[" & MySheet2.Name & "$" & Replace(Table1, "$", "") & "]"

        'Last Time
        ElseIf (NewRowCount > 0) And (NewRowCount <= 60000) Then
            SR1_FirstRow = SR1_LastRow + 1
            SR1_LastRow = SR1_LastRow + NewRowCount
            NewRowCount = 0


            Set MySheet1 = Sheets(SheetTable1)
            Sheets.Add After:=MySheet1
            Set MySheet2 = ActiveSheet

            MySheet1.Activate
            Rows("1:1").Select
            Selection.Copy
            MySheet2.Activate
            Rows("1:1").Select
            Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks:=False, Transpose:=False

            MySheet1.Activate
            ActiveSheet.Range("A" & SR1_FirstRow & ":" & Cells(SR1_LastRow, SR1_LastColumn).Address).Copy
            MySheet2.Activate
            ActiveSheet.Range("A2").PasteSpecial xlPasteValues
            Range("A1").Select
            Range(Selection, Selection.End(xlDown)).Select
            Range(Selection, Selection.End(xlToRight)).Select

            'Set the tables equal to the respective ranges
            Table1 = Selection.Address

            'Convert the string into a query table - have to get rid of dollar signs for it to work
            Table1 = "[" & MySheet2.Name & "$" & Replace(Table1, "$", "") & "]"

        'Does this the first time if under 60k rows
        Else
            SR1_FirstRow = 1

            'Set the tables equal to the respective ranges
            Set SheetRange1 = ActiveWorkbook.Sheets(SheetTable1).Range("A" & SR1_FirstRow & ":" & Cells(SR1_LastRow, SR1_LastColumn).Address)

            'Pass the table address to a string
            Table1 = SheetRange1.Address

            'Convert the string into a query table - have to get rid of dollar signs for it to work
            Table1 = "[" & SheetTable1 & "$" & Replace(Table1, "$", "") & "]"

        End If

        'SQL Statement
        MySQL = Sheets("Control Sheet").Range("C14").Value          
        MySQL = Replace(MySQL, "@Table1", Table1)           
        MySQL = Replace(MySQL, "@Year", Sheets("Control Sheet").Range("C5").Value)          
        MySQL = Replace(MySQL, "@FA_PQ_Input", FA_PQ)           
        MySQL = Replace(MySQL, "@SubChannel", SubChannel)           
        MySQL = Replace(MySQL, "@MyMonth", MyMonth)

        MsgBox MySQL

        'Run SQL
        MyRecordSet.Open MySQL, MyConn, adOpenKeyset, adLockOptimistic

        'Paste Data with headers to location
        ActiveWorkbook.Sheets(TargetSheetTable).Activate
        ActiveSheet.Range("A" & 1 + SR1_FirstRow).CopyFromRecordset MyRecordSet

        For i = 0 To MyRecordSet.Fields.Count - 1
            ActiveSheet.Cells(1, i + 1) = MyRecordSet.Fields(i).Name
            With ActiveSheet.Cells(1, i + 1)
                .Font.Bold = True
                .Font.Size = 10
            End With
        Next i

        MyRecordSet.Close
        Set MyRecordSet = Nothing

        MyConn.Close
        Set MyConn = Nothing
    Next j

    ''Putting Nulls in the blanks
    'ActiveSheet.Cells.Replace What:="", Replacement:="NULL", LookAt:=xlWhole, SearchOrder:=xlByRows, MatchCase:=True, _
    '                          SearchFormat:=False, ReplaceFormat:=False

    'Tidying the sheet
    ActiveSheet.Cells.AutoFilter                
    ActiveSheet.Columns.AutoFit
    ActiveSheet.Range("A1").Select              
    Sheets("Control Sheet").Activate

    Application.ScreenUpdating = True
End Sub

person Rick W.    schedule 19.06.2018    source источник
comment
Первое, что бросается в глаза, это то, что вы объявили свои переменные как целые числа, я почти уверен, что вместо этого их следует объявлять как длинные, поскольку целые числа не могут содержать значения, превышающие около 32 тыс.   -  person Xabier    schedule 19.06.2018
comment
@Xabier Я понимаю, что вы имеете в виду, но если вы присмотритесь, вы увидите, что переменные, заданные как целые числа, не содержат сверхогромных значений. На самом деле те, которые объявлены как двойные, и, кажется, отлично справляются со всеми моими другими запросами. Как я пытался упомянуть в сообщении выше, это отлично работает, когда на листе меньше 65 тыс. строк или около того...   -  person Rick W.    schedule 19.06.2018
comment
Чтобы избежать проблемы XY и изучения этого большого количества кода, предоставьте нам полный фон с образцами данных ввода и желаемого вывода.   -  person Parfait    schedule 19.06.2018
comment
Кроме того, рассмотрите возможность использования реальной базы данных. Обратите внимание: Excel не является базой данных. И да, у вас есть доступ к MS Access (т. е. его движок), хотя у вас может быть установленная программа .exe (которая на самом деле представляет собой просто консоль с графическим интерфейсом для движка). Таким образом, вы можете создавать и использовать базы данных Access.   -  person Parfait    schedule 19.06.2018
comment
VBA определенно находится в модуле кода, а не на листе кода?   -  person Alan    schedule 19.06.2018
comment
Кроме того, как упоминал Парфе, рассматривали ли вы перенос функции в Access, а затем вызов функции из Excel, а не прямой запрос данных?   -  person Alan    schedule 19.06.2018
comment
Скорее всего, одна или несколько ваших операций работают не с тем листом или диапазоном, что очень сложно отследить со всеми этими Select и Activate. Мой совет: избавьтесь от них. Посмотрите здесь, чтобы получить помощь по этому поводу, и тогда вы, вероятно, сами найдете ошибку. Если нет, обновите здесь Q-код без Select и Activate.   -  person chris neilsen    schedule 20.06.2018
comment
@Parfait Я знаю, что excel - это не база данных ... Пользователь очень привязан к excel, и он берет файл excel и переносит его со многими vlookups с бесчисленных листов, чтобы привести в порядок свои данные и создать сводку. Я просто пытаюсь сократить время их усилий. Что касается того, что вы говорите о XY, я дал предысторию и только дополнил код по мере необходимости. Я хочу иметь возможность запрашивать рабочий лист с более чем 65 тыс. строк и ищу решение, которое работает в Excel, но я объяснил это выше.   -  person Rick W.    schedule 20.06.2018
comment
@Alan, определенно в модуле. Я мог бы попытаться переместить его из Excel в доступ и обратно в Excel для пользователя, но все это, кажется, слишком много в моей голове. Возможно, есть ссылки на посты, упрощающие этот процесс?   -  person Rick W.    schedule 20.06.2018
comment
@ Крис Нильсен, я определенно на правильном листе. Я очень тщательно разрабатываю свой код в том месте, где он выполняется. Когда я вывожу запрос в текст, который я могу проверить, он всегда извлекается из ожидаемого листа. Я еще раз повторю, что этот код отлично работает, когда меньше 65 тыс. строк.   -  person Rick W.    schedule 20.06.2018
comment
@ Рик, использующий Select и Activate, как вы здесь, на мой взгляд, несовместим с большой осторожностью. Мы видим много вопросов, использующих те, которые легко решаются после их удаления. Вы пришли в SO за помощью в решении вашей проблемы, если вы хотите проигнорировать данный совет, это ваше дело.   -  person chris neilsen    schedule 20.06.2018
comment
Тот факт, что он работает нормально, когда строк менее 65 тыс., предполагает ошибку в среднем или последнем блоке. Я бы предложил разбить ваш код на разные функции. Это позволит вам протестировать каждый компонент отдельно. Например. вызов базы данных должен быть в своей собственной подпрограмме, а затем вы можете проверить, что он извлекает значения более 65 КБ независимо от чего-либо еще. Это позволит вам сузить область проблемы. Вызовы базы данных могут быть дорогими, но 65 000 строк — это не обязательно много, если вы используете 64-разрядный Excel.   -  person Alan    schedule 20.06.2018
comment
И если ему не хватило памяти, Excel должен выдать вам сообщение о том, что ему не хватает памяти. Я уже видел всплывающее окно в 32-битном Excel. Я видел неподтвержденные комментарии о том, что если вы скажете Excel сохранять файл в конце каждого цикла, то он освободит всю рабочую память - это может помочь, если вы используете 32-разрядную версию.   -  person Alan    schedule 20.06.2018
comment
@chrisneilsen смотрите ниже, где кто-то сделал именно то, что вы говорите, и это не изменило результат. Я не пытался игнорировать ваш совет, я просто пытался найти решение. Я просто пытался дать вам понять, что я на правильном листе, и то, что вы говорили, не было решением. Спасибо за ваше время.   -  person Rick W.    schedule 20.06.2018
comment
@Alan хорошие мысли, и я согласен с вашей логикой, и я попытаюсь обработать ее, чтобы увидеть, смогу ли я найти проблему. Что касается попытки сохранить его в конце цикла, это, к сожалению, не сработало.   -  person Rick W.    schedule 20.06.2018


Ответы (4)


Я считаю, что в вашем коде есть ряд проблем, и это не обязательно ответ на вашу проблему, но я попытался привести ваш код в порядок и удалить все операторы Select & Activate, поскольку они на самом деле не нужны и иногда могут вызывать ошибки. когда у вас активированы другие листы и т. д.

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

Sub Risk_Init_Pivot(FA_PQ, Risk_Init, SubChannel, MyMonth As String)

    Application.ScreenUpdating = False

    Dim SheetRange1 As Range, SheetRange2 As Range, SheetRange3 As Range, MyRange As Range
    Dim SR1_LastRow As Double, SR1_LastColumn As Double, NewRowCount As Double, SR1_FirstRow As Double
    Dim i As Long, j As Long, MyLoop As Long
    Dim Table1 As String, MySQL As String
    Dim MySheet2 As Worksheet
    Dim MyConn As ADODB.Connection
    Dim MyRecordSet As ADODB.Recordset
    Dim wsFanned As Worksheet, wsTarget As Worksheet
    Set wsTarget = Sheets("Risk Init Pivot")
    Set wsFanned = Sheets("Fanned File")

    'Initiate
    wsTarget.Cells.Delete

    'Find Range Coordinates Dynamically
    If wsFanned.AutoFilterMode Then
        If wsFanned.FilterMode Then wsFanned.ShowAllData
    End If

    SR1_LastRow = wsFanned.Cells(wsFanned.Rows.Count, "A").End(xlUp).Row
    SR1_LastColumn = wsFanned.Cells(SR1_LastRow, wsFanned.Columns.Count).End(xlToLeft).Column

    MyLoop = WorksheetFunction.RoundUp(SR1_LastRow / 60000, 0)

    NewRowCount = 0

    For j = 1 To MyLoop

        'Set Up Connection Details
        Set MyConn = New ADODB.Connection
        MyConn.CommandTimeout = 0
        Set MyRecordSet = New ADODB.Recordset

        MyConn.Open "Provider = Microsoft.ACE.OLEDB.12.0;" & _
        "Data Source = " & Application.ThisWorkbook.FullName & ";" & _
        "Extended Properties = ""Excel 12.0 Xml;HDR=YES;IMEX=1"";"
        Set MyRecordSet.ActiveConnection = MyConn

        'First Time
        If SR1_LastRow > 60000 Then
            NewRowCount = SR1_LastRow - 60000
            SR1_LastRow = 60000
            SR1_FirstRow = 1

            'Set the tables equal to the respective ranges
            Set SheetRange1 = wsFanned.Range("A" & SR1_FirstRow & ":" & Cells(SR1_LastRow, SR1_LastColumn).Address)

            'Pass the table address to a string
            Table1 = SheetRange1.Address

            'Convert the string into a query table - have to get rid of dollar signs for it to work
            Table1 = "[" & wsFanned.Name & "$" & Replace(Table1, "$", "") & "]"

        'Does this until NewRowCount falls into last time
        ElseIf NewRowCount > 60000 Then
            NewRowCount = NewRowCount - 60000
            SR1_FirstRow = SR1_LastRow + 1
            SR1_LastRow = SR1_LastRow + 60000

            Sheets.Add After:=wsFanned
            Set MySheet2 = ActiveSheet

            wsFanned.Rows("1:1").Copy
            MySheet2.Rows("1:1").PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks:=False, Transpose:=False

            wsFanned.Range("A" & SR1_FirstRow & ":" & Cells(SR1_LastRow, SR1_LastColumn).Address).Copy
            MySheet2.Range("A2").PasteSpecial xlPasteValues
            Set MyRange = MySheet2.UsedRange

            'Set the tables equal to the respective ranges
            Table1 = MyRange.Address

            'Convert the string into a query table - have to get rid of dollar signs for it to work
            Table1 = "[" & MySheet2.Name & "$" & Replace(Table1, "$", "") & "]"

        'Last Time
        ElseIf (NewRowCount > 0) And (NewRowCount <= 60000) Then
            SR1_FirstRow = SR1_LastRow + 1
            SR1_LastRow = SR1_LastRow + NewRowCount
            NewRowCount = 0


            Sheets.Add After:=wsFanned
            Set MySheet2 = ActiveSheet

            wsFanned.Rows("1:1").Copy
            MySheet2.Rows("1:1").PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks:=False, Transpose:=False

            wsFanned.Range("A" & SR1_FirstRow & ":" & Cells(SR1_LastRow, SR1_LastColumn).Address).Copy
            MySheet2.Range("A2").PasteSpecial xlPasteValues

            'Set the tables equal to the respective ranges
            Table1 = MySheet2.UsedRange
            'Convert the string into a query table - have to get rid of dollar signs for it to work
            Table1 = "[" & MySheet2.Name & "$" & Replace(Table1, "$", "") & "]"

        'Does this the first time if under 60k rows
        Else
            SR1_FirstRow = 1

            'Set the tables equal to the respective ranges
            Set SheetRange1 = wsFanned.Range("A" & SR1_FirstRow & ":" & Cells(SR1_LastRow, SR1_LastColumn).Address)

            'Pass the table address to a string
            Table1 = SheetRange1.Address

            'Convert the string into a query table - have to get rid of dollar signs for it to work
            Table1 = "[" & SheetTable1 & "$" & Replace(Table1, "$", "") & "]"

        End If

        'SQL Statement
        MySQL = Sheets("Control Sheet").Range("C14").Value
        MySQL = Replace(MySQL, "@Table1", Table1)
        MySQL = Replace(MySQL, "@Year", Sheets("Control Sheet").Range("C5").Value)
        MySQL = Replace(MySQL, "@FA_PQ_Input", FA_PQ)
        MySQL = Replace(MySQL, "@SubChannel", SubChannel)
        MySQL = Replace(MySQL, "@MyMonth", MyMonth)

        MsgBox MySQL

        'Run SQL
        MyRecordSet.Open MySQL, MyConn, adOpenKeyset, adLockOptimistic

        'Paste Data with headers to location
        wsTarget.Range("A" & 1 + SR1_FirstRow).CopyFromRecordset MyRecordSet

        For i = 0 To MyRecordSet.Fields.Count - 1
            wsTarget.Cells(1, i + 1) = MyRecordSet.Fields(i).Name
            With wsTarget.Cells(1, i + 1)
                .Font.Bold = True
                .Font.Size = 10
            End With
        Next i

        MyRecordSet.Close
        Set MyRecordSet = Nothing

        MyConn.Close
        Set MyConn = Nothing
    Next j

    ''Putting Nulls in the blanks
    'ActiveSheet.Cells.Replace What:="", Replacement:="NULL", LookAt:=xlWhole, SearchOrder:=xlByRows, MatchCase:=True, _
    '                          SearchFormat:=False, ReplaceFormat:=False

    'Tidying the sheet
    ActiveSheet.Cells.AutoFilter
    ActiveSheet.Columns.AutoFit
    ActiveSheet.Range("A1").Select
    Sheets("Control Sheet").Activate

    Application.ScreenUpdating = True
End Sub
person Xabier    schedule 20.06.2018
comment
Повторяющееся определение wsFanned. Кроме того, подумайте, что MySheet2.UsedRange является избыточным, поскольку сразу за ним следует Set MyRange = MySheet2.UsedRange. Иначе удивительно - как это надо писать! - person Alan; 20.06.2018
comment
@ Алан, я обновил его, чтобы отразить ваши комментарии, спасибо! Я просто сделал это быстро, чтобы попытаться показать ОП, как они могут избежать выбора/активации. :) - person Xabier; 20.06.2018
comment
@Xabier Я ценю время, которое ты потратил, чтобы привести его в порядок. Я самоучка и поэтому не всегда знаю все приемы, поэтому было интересно увидеть другой способ ведения дел. После настройки нескольких незначительных вещей я протестировал его, и он действительно работает. К сожалению, он работает так же, как и мой код, и по-прежнему дает тот же результат. Я ценю ваше время и усилия, чтобы показать мне некоторые новые идеи. Спасибо. - person Rick W.; 20.06.2018
comment
@РикВ. Показав нам ошибку, которую вы получаете, я немного обновил код, не уверен, что он будет работать должным образом, но, думаю, стоит попробовать... - person Xabier; 20.06.2018
comment
@Xabier, честно говоря, я не могу сказать, что вы изменили, но, возможно, это потому, что вы обновили некоторые вещи, которые я уже обновил с моей стороны ... может быть, если вы уточните обновление, я увижу это. - person Rick W.; 20.06.2018
comment
@РикВ. Я обновил следующую строку Table1 = "[" & SheetTable1 & "$" & Replace(Table1, "$", "") & "]", так как это строка, показанная в ошибке, не уверен, что это решит проблему, но это определенно шаг в правильном направлении... (я считаю) - person Xabier; 20.06.2018
comment
@Xabier да, это была одна из вещей, которые я изменил, чтобы реализовать ваш код. Я знаю, что у вас нет реального способа тестирования, поэтому я не думаю, что стоит упоминать об этом. Было несколько мест, которые нуждались в некоторых изменениях, которые вы, без сомнения, увидели бы, если бы смогли запустить его в моей среде. В конечном итоге это был не ответ :( - person Rick W.; 20.06.2018

Excel считает, что ваш набор записей пуст.

Это не ошибка памяти.

С 80 тыс. строк ваш код входит в блок ElseIf (NewRowCount > 0) And (NewRowCount <= 60000) Then. Когда он пытается вызвать связанный набор записей, происходит сбой.

Вы можете проверить это поведение, изменив эту строку кода:

MyRecordSet.Open MySQL, MyConn, adOpenKeyset, adLockOptimistic

to:

On Error Resume Next
MyRecordSet.Open MySQL, MyConn, adOpenKeyset, adLockOptimistic
If MyRecordSet.EOF Then MsgBox "null"

Код работает правильно на первой итерации, на второй итерации вы получаете нулевое предупреждение.

Почему он не возвращает записи, я не могу вам сказать. Но вот ваша ошибка.

person Alan    schedule 20.06.2018
comment
Эй, это хорошая мысль. Я пробовал это и думал об этом, но у меня есть пара проблем с этим. 1. Ошибка возникает, когда он пытается запустить MySQL, сообщая, что не может найти таблицу, из которой я хочу извлечь данные. В результате он ничего не вернет, и по умолчанию MyRecordset будет пустым в результате проблемы, а не как причина проблемы (если я правильно думаю). Поэтому сообщение msgbox, возвращающее null, на самом деле ничего не доказывает. - person Rick W.; 20.06.2018
comment
2. Тот факт, что я могу заставить его работать с помощью отдельной подпрограммы после того, как оригинал закрывается, используя то же строковое значение для MySQL, дает мне понять, что запуск этого SQL будет давать результаты на нормальной основе. Таким образом, я могу заставить Excel создать набор записей, используя тот же MySQL, если он не использует одну и ту же подпрограмму :( Мне понравилось, что у вас была идея попробовать, и спасибо, что нашли время подумать об этом, но я не думаю, что это действительно в этом случае. Я думаю, что может быть некоторое недопонимание того, как выглядит ошибка. Было бы полезно, если бы я сделал снимок экрана с ошибкой? - person Rick W.; 20.06.2018
comment
Вероятно, это потому, что он не может найти таблицу. Не только это, как показано в msgbox, отображающем текст sql, который на первый взгляд кажется нормальным. Точка проверки того, является ли набор записей пустым или нет, была частью отслеживания ошибок, чтобы выяснить, была ли ошибка при выборе и вставке, захвате набора записей и т. д. - person Alan; 20.06.2018
comment
было бы полезно, если бы я сделал скриншот ошибки? Всегда. Это не та ошибка, которую я видел в своем макете. Я увидел ошибку автоматизации. На какую версию ADO вы ссылаетесь? И вы вызываете это из другой книги? - person Alan; 20.06.2018
comment
Сначала я упомянул, в чем была ошибка, если оглянуться назад, но моей ошибкой было то, что я не сделал скриншот. В моих справочниках есть ADO 6.0 и ADO Recordset 2.8. Кроме того, я звоню только из той же книги. Кроме того, что касается вашей точки зрения, он не может найти таблицу, это также неверно, как я упоминал в своем посте и комментариях, потому что тот же SQL работает при использовании в отдельной подпрограмме, когда я ничего не изменил. Таким образом, у него нет проблем с его поиском, я думаю, что есть основная проблема, из-за которой кажется, что у него проблемы с его поиском (например: › ~ 65 тыс. строк) - person Rick W.; 20.06.2018
comment
это тоже неверно - неверно. Тот факт, что это работает, когда вы делаете что-то еще, не исключает автоматически, что это неправильно в исходном случае. - person Alan; 20.06.2018
comment
не уверен, что вы имеете в виду ... Если я успешно запускаю один и тот же фрагмент кода таким же образом, то как это не исключает проблему. Возможно, вам придется привести пример, где эта логика ошибочна... Я на 99% уверен, что, поскольку я воспроизвел способ выполнения SQL, воспроизвел SQL и сделал то, что нужно, это значит работает. Если я попытаюсь вызвать внешний сабвуфер (где он работает) внутри исходного сабвуфера, он не сработает. Подобно тому, как он не работает в оригинальном сабвуфере сам по себе. - person Rick W.; 20.06.2018
comment
Я думаю, что есть основная проблема, из-за которой кажется, что у него проблемы с поиском. Да нет. Когда выполняется первая итерация (до 65 КБ), она работает с существующей таблицей (Разветвленный файл). При переходе на вторую итерацию соединение ADO не распознает добавление нового рабочего листа и, следовательно, не находит таблицу. Excel сам находит таблицу, о чем, надеюсь, свидетельствуют всплывающие окна сообщений. Когда вы запускаете команду в отдельном подразделе, лист существует с самого начала и, следовательно, не возникает проблем. - person Alan; 20.06.2018
comment
Почему набор записей не находит таблицу, у меня недостаточно информации для отслеживания. В моем макете я получаю другую ошибку «Сбой автоматизации», а не таблицу не найден. - person Alan; 20.06.2018
comment
Помните, что вы не сравниваете аналогичный код с похожим, хотя может показаться, что итерация один отличается от итерации два, отличается от конкретного фрагмента кода, выполняющего код непосредственно в другом Sub. - person Alan; 20.06.2018
comment
Я бы предложил трехэтапный процесс: (1) модульность вашего кода, чтобы облегчить отслеживание каждой функции (2) изучить добавление всех возвращаемых данных из набора записей непосредственно в TargetSheetTable (Risk Init Pivot) - что будет проще с модульным кодом и (3) Планирование в конечном итоге переноса всего хранилища данных в базу данных (т.е. Access), куда будет намного проще обращаться с запросами из Excel. - person Alan; 20.06.2018
comment
о, я бы хотел иметь его в Access или SQL Server, но это не зависит от меня. Итак, если я вас правильно понял, вы говорите, что соединение ADO почему-то не видит, что лист есть, хотя Excel его видит. Это интересная мысль. Если это то, что вы говорите, и это проблема, как я могу зарегистрировать новый лист в ADO? Я попытался сделать (1), вызвав сработавшую подсистему, но я попытаюсь разбить часть соединения на другую подпрограмму и посмотреть, что там происходит. (2) уже является частью плана для этого кода. Я сообщу, что происходит. - person Rick W.; 20.06.2018
comment
да. Я попытался сказать ему обновить книгу и сохранить книгу, но ни один из них не увенчался успехом. Я заставил его работать без объявления ошибки, скопировав инициализацию соединения и набора записей и дублируя его как часть каждой итерации. Однако это все еще не скопировало второй набор данных - он остановился на 59 КБ. - person Alan; 20.06.2018
comment
Может быть, стоит подумать о том, чтобы пойти в другом направлении? Вместо динамического добавления нового рабочего листа создайте несколько рабочих листов с результатами (например, «Результаты1 — Результаты10»). Скрыть их - скрыть или очень скрыть. В идеале также иметь фиксированные исходные листы и разделить данные на фрагменты по 60 тыс. Затем вы можете жестко закодировать все свои вызовы — Source1 -> Results1 и т. д. Устраняет необходимость циклического просмотра результатов, вы можете просто получить доступ к UsedRange для каждого используемого листа (и вы можете отслеживать использованные листы в своем контрольном листе). - person Alan; 20.06.2018
comment
Я проверил то, что вы сказали, и я думаю, что это вызвано тем, что ADO не распознает это. Я прогнал его, заставив использовать таблицу, которая существовала до запуска, и он сделал то, что хотел. Итак, я попытаюсь превратить это в решение, похожее на то, от чего вы уклоняетесь, и я опубликую решение здесь, как только оно будет завершено. Спасибо Алан! Я ценю время, которое вы потратили на это, помогая мне понять это. - person Rick W.; 20.06.2018
comment
Рад, что смог помочь. Разочаровывает незнание того, почему ADO не видит его, хотя Excel явно его видит. OTOH любое решение, которое работает, является правильным решением. - person Alan; 20.06.2018

Спасибо Xabier и Alan за их вклад в решение.

Xabier за более чистый код. Алану за выявление основной проблемы.

Проблема в том, что когда исходная таблица разбивается на новый лист для учета лишних строк, даже если лист существует, ADO еще не распознает его. Он распознает его только после того, как вы покинете текущую подпрограмму (по крайней мере, это мое понимание из всех обсуждений, тестирования и, в конечном итоге, моего решения).

Итак, в качестве резюме высокого уровня:

  1. Чтобы учесть слишком много строк и получить сообщение об ошибке «Доступ не может найти вашу таблицу», я бы позволил первым 60 КБ работать на текущем листе, а затем скопировал следующие 60 КБ (или меньше) на новый лист.

  2. Чтобы ADO распознал только что созданный лист, я поместил функциональность соединения и набора записей в отдельную подпрограмму и вызвал ее из своей исходной подпрограммы, передав любые параметры, которые мне нужны для ее успешного запуска.

  3. Затем я вернулся к своему исходному подразделу, удалил только что созданный лист, а затем снова повторил этот процесс, пока не учёл весь исходный лист.

Так, например, 140 тыс. строк будут выполнять первые 60 тыс. строк на исходном листе, следующие 60 тыс. — на новом листе, а последние 20 тыс. — на другом новом листе.

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

Спасибо за все входные данные, и вот мой код ниже, если вы заинтересованы. Обратите внимание, что код будет похож (с некоторыми изменениями) на чистую версию, опубликованную Xabier.

Sub Risk_Init_Pivot(FA_PQ As String, Risk_Init As String, SubChannel As String, MyMonth As String)

Application.ScreenUpdating = False


Dim SheetRange1 As Range, MyRange As Range
Dim SR1_LastRow As Double, SR1_LastColumn As Double, NewRowCount As Double, SR1_FirstRow As Double
Dim i As Integer, j As Integer, MyLoop As Integer
Dim Table1 As String, MySQL As String
Dim wsOrigin As Worksheet, wsTarget As Worksheet, MySheet As Worksheet
Set wsTarget = Sheets("Risk Init Pivot")
Set wsOrigin = Sheets("Fanned File")

'Initiate
wsTarget.Cells.ClearContents

'Find Range Coordinates Dynamically
If wsOrigin.AutoFilterMode Then
    If wsOrigin.FilterMode Then wsOrigin.ShowAllData
End If

SR1_LastRow = wsOrigin.Cells(wsOrigin.Rows.Count, "A").End(xlUp).Row
SR1_LastColumn = wsOrigin.Cells(SR1_LastRow, wsOrigin.Columns.Count).End(xlToLeft).Column


MyLoop = WorksheetFunction.RoundUp(SR1_LastRow / 60000, 0)

NewRowCount = 0

For j = 1 To MyLoop


    'First Time
    If SR1_LastRow > 60000 Then
        NewRowCount = SR1_LastRow - 60000
        SR1_LastRow = 0
        SR1_EndRow = 60000
        SR1_FirstRow = 1

        'Set the tables equal to the respective ranges
        Set SheetRange1 = wsOrigin.Range("A" & SR1_FirstRow & ":" & Cells(SR1_EndRow, SR1_LastColumn).Address)

        'Pass the table address to a string
        Table1 = SheetRange1.Address

        'Convert the string into a query table - have to get rid of dollar signs for it to work
        Table1 = "[" & wsOrigin.Name & "$" & Replace(Table1, "$", "") & "]"



    'Does this until NewRowCount falls into last time
    ElseIf NewRowCount > 60000 Then
        NewRowCount = NewRowCount - 60000
        SR1_FirstRow = SR1_EndRow + 1
        SR1_EndRow = SR1_FirstRow + 59999

        Sheets.Add After:=wsOrigin
        Set MySheet = ActiveSheet

        wsOrigin.Rows("1:1").Copy
        MySheet.Rows("1:1").PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks:=False, Transpose:=False

        wsOrigin.Range("A" & SR1_FirstRow & ":" & Cells(SR1_EndRow, SR1_LastColumn).Address).Copy
        MySheet.Range("A2").PasteSpecial xlPasteValues
        Set MyRange = MySheet.UsedRange

        'Set the tables equal to the respective ranges
        Table1 = MyRange.Address

        'Convert the string into a query table - have to get rid of dollar signs for it to work
        Table1 = "[" & MySheet.Name & "$" & Replace(Table1, "$", "") & "]"


    'Last Time
    ElseIf (NewRowCount > 0) And (NewRowCount <= 60000) Then
        SR1_FirstRow = SR1_EndRow + 1
        SR1_EndRow = SR1_FirstRow + NewRowCount
        NewRowCount = 0

        Sheets.Add After:=wsOrigin
        Set MySheet = ActiveSheet

        wsOrigin.Rows("1:1").Copy
        MySheet.Rows("1:1").PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks:=False, Transpose:=False

        wsOrigin.Range("A" & SR1_FirstRow & ":" & Cells(SR1_EndRow, SR1_LastColumn).Address).Copy
        MySheet.Range("A2").PasteSpecial xlPasteValues
        Set MyRange = MySheet.UsedRange

        'Set the tables equal to the respective ranges
        Table1 = MyRange.Address
        'Convert the string into a query table - have to get rid of dollar signs for it to work
        Table1 = "[" & MySheet.Name & "$" & Replace(Table1, "$", "") & "]"



    'Does this the first time if under 60k rows
    Else
        SR1_FirstRow = 1

        'Set the tables equal to the respective ranges
        Set SheetRange1 = wsOrigin.Range("A" & SR1_FirstRow & ":" & Cells(SR1_LastRow, SR1_LastColumn).Address)

        'Pass the table address to a string
        Table1 = SheetRange1.Address

        'Convert the string into a query table - have to get rid of dollar signs for it to work
        Table1 = "[" & wsOrigin.Name & "$" & Replace(Table1, "$", "") & "]"


    End If


    Call MyRecordset(Table1, FA_PQ, SubChannel, MyMonth, wsTarget)

    If Not MySheet Is Nothing Then
    Application.DisplayAlerts = False
    MySheet.Delete
    Application.DisplayAlerts = True
    End If

Next j

'Tidying the sheet
wsTarget.Cells.AutoFilter
wsTarget.Columns.AutoFit
Sheets("Control Sheet").Activate

Application.ScreenUpdating = True

End Sub

Sub MyRecordset(Table1 As String, FA_PQ As String, SubChannel As String, MyMonth As 
String, wsTarget As Worksheet)


    Dim MyConn As ADODB.Connection
    Dim MyRecordset As ADODB.RecordSet
    Dim i As Integer
    Dim LastRow As Double


    'Set Up Connection Details
    Set MyConn = New ADODB.Connection
    MyConn.CommandTimeout = 0
    Set MyRecordset = New ADODB.RecordSet

    MyConn.Open "Provider = Microsoft.ACE.OLEDB.12.0;" & _
    "Data Source = " & Application.ThisWorkbook.FullName & ";" & _
    "Extended Properties = ""Excel 12.0 Xml;HDR=YES;IMEX=1"";"
    Set MyRecordset.ActiveConnection = MyConn

    'SQL Statement
    MySQL = Sheets("Control Sheet").Range("C14").Value
    MySQL = Replace(MySQL, "@Table1", Table1)
    MySQL = Replace(MySQL, "@Year", Sheets("Control Sheet").Range("C5").Value)
    MySQL = Replace(MySQL, "@FA_PQ_Input", FA_PQ)
    MySQL = Replace(MySQL, "@SubChannel", SubChannel)
    MySQL = Replace(MySQL, "@MyMonth", MyMonth)

    'Run SQL

    MyRecordset.Open MySQL, MyConn, adOpenKeyset, adLockOptimistic


    'Paste Data with headers to location
    If wsTarget.Range("A2").Value = "" Then
    wsTarget.Range("A2").CopyFromRecordset MyRecordset
    Else
    LastRow = wsTarget.Cells(wsTarget.Rows.Count, "A").End(xlUp).Row
    wsTarget.Range("A" & LastRow + 1).CopyFromRecordset MyRecordset
    End If

    For i = 0 To MyRecordset.Fields.Count - 1
        wsTarget.Cells(1, i + 1) = MyRecordset.Fields(i).Name
        With wsTarget.Cells(1, i + 1)
            .Font.Bold = True
            .Font.Size = 10
        End With
    Next i

    MyRecordset.Close
    Set MyRecordset = Nothing

    MyConn.Close
    Set MyConn = Nothing



    'Putting Nulls in the blanks
    wsTarget.Cells.Replace What:="", Replacement:="0", LookAt:=xlWhole, SearchOrder:=xlByRows, MatchCase:=True, SearchFormat:=False, ReplaceFormat:=False


End Sub
person Rick W.    schedule 21.06.2018

Вам не нужно разбивать запросы, потому что у вас более 60 000 строк... есть обходной путь.

См. здесь: https://stackoverflow.com/a/51402496/1274820

Вместо ссылки на диапазон просто укажите ссылку на лист.

Это относится и к именованным диапазонам (что не удастся).

Например, если ваши данные находятся в диапазоне Sheet1 A1:N152679, просто используйте SELECT SomeData FROM [Sheet1$] — ограничений нет.

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

Таким образом Excel может обрабатывать до 1,048,576 строк.

person user1274820    schedule 13.09.2018