Оптимизация цикла через базу данных Access

Мне нужна помощь с массивным циклом по постоянно расширяющейся базе данных Access, состоящей примерно из 280 000 строк данных. Процедура добавляет 3000 строк данных каждую неделю, поэтому время выполнения макросов только увеличивается. На выполнение уходит около часа.

Каков оптимальный способ завершения моей процедуры? У меня есть опыт работы с VBA, но знания SQL ограничены.

Обобщенная проблема заключается в том, что оператор If, расположенный в разделе «Необходима помощь», проходит через 280 000 строк данных для 3000 компаний.

Цель состоит в том, чтобы в JQHistory учитывались свежие еженедельные оценки компании, но при этом необходимо учитывать дату запуска макроса.

Примечание. Все, кроме «Здесь нужна помощь», я оптимизировал в другом макросе. Я оставил это, чтобы, надеюсь, улучшить контекст проблемы.

Вот неоптимизированный макрос:

Sub OpdaterKvant()
Dim wb As Workbook
Dim ws As Worksheet
Dim DatoIn As Date
Set db = New ADODB.Connection

Set DbEQ = New ADODB.Connection

'The location of the database is determined outside the macro'
strConn = ConnectionString
db.Open strConn

Set wb = Workbooks.Open("My File Location")
Set ws = wb.Worksheets(1)

n = ws.UsedRange.Rows.Count

DateIn = Right(ws.Cells(1, 1), 2) & "-" & Mid(ws.Cells(1, 1), 5, 2) & "-" & Left(ws.Cells(1, 1), 4)

Dato = Format(DateIn, "mm-dd-yyyy")

db.Execute ("DELETE * FROM JQScores")

For i = 3 To n
    Sedol = Replace(ws.Cells(i, 1), " ", "")
    Company = Left(Replace(ws.Cells(i, 2), "'", ""), Len(Replace(ws.Cells(i, 2), "'", "")) - 1)
    Country = Replace(ws.Cells(i, 3), " ", "")
    Region = Replace(ws.Cells(i, 4), " ", "")
    Sector = Replace(ws.Cells(i, 5), " ", "")
    MarketCap = Replace(Replace(ws.Cells(i, 6), " ", ""), ",", ".")
    JQRank = Replace(ws.Cells(i, 7), " ", "")
    ValueRank = Replace(ws.Cells(i, 8), " ", "")
    QualityRank = Replace(ws.Cells(i, 9), " ", "")
    MomentumRank = Replace(ws.Cells(i, 10), " ", "")
    JQScore = Replace(Replace(ws.Cells(i, 11), " ", ""), ",", ".")

    'Inserts the information into the Access database.'
    Sql = "Insert into JQScores (Sedol, Company, Region, Sector, MarketCapUSD, JQ_Rank, Value_Rank, Quality_Rank, Momentum_Rank, JQ_Score, Country) VALUES ('" & Sedol & "','" & Company & "', '" & Region & "', '" & Sector & "', " & MarketCap & ", '" & JQRank & "', '" & ValueRank & "', '" & QualityRank & "', '" & MomentumRank & "', " & JQScore & ", '" & Country & "')"
    db.Execute (Sql)

'*** HELP NEEDED IN THIS SECTION'

    If db.Execute("Select Count(Id) as NumId from JQHistory where Sedol='" & Sedol & "' and history_date=#" & Dato & "#")("NumId") = 0 Then
    Sql = "Insert into JQHistory (History_date, Sedol, Selskabsnavn, JQScore, JQ_Rank, Value_Rank, Momentum_Rank, Quality_Rank, Marketcap) VALUES (#" & Dato & "#, '" & Sedol & "','" & Company & "'," & JQScore & ", '" & JQRank & "', '" & ValueRank & "', '" & MomentumRank & "', '" & QualityRank & "', " & MarketCap & ")"
    db.Execute (Sql)

    Else
    Sql = "Update JQHistory set MarketCap = " & MarketCap & ", Selskabsnavn='" & Company & "' , JQ_Rank='" & JQRank & "', Value_Rank='" & ValueRank & "', Quality_Rank='" & QualityRank & "', Momentum_Rank='" & MomentumRank & "', JQScore = " & JQScore & " WHERE SEDOL='" & Sedol & "' and History_Date=#" & Dato & "#"
    db.Execute (Sql)
    End If

'***'

Next i

db.Close
wb.Close

Хм, не похоже, что вы можете сделать несколько операторов вставки с доступом: stackoverflow.com/questions/62504/…. Неужели нужно удалять 100% данных в таблице, а потом каждый день вставлять по 280к+ строк? Есть ли лучший способ структурировать вашу информацию или данные, который не включает этот шаг?

rvictordelta 13.06.2019 15:10

Вы неправильно поняли мою попытку улучшить контекст. Он чередуется между базой данных под названием «JQScores» и «JQHistory». JQScores состоит из 3 000 еженедельно обновляемых строк, где JQHistory представляет собой временной ряд, состоящий из 280 000 строк JQScores, которые еженедельно добавляются, чтобы показать эволюцию с течением времени :)

piele 13.06.2019 15:26

Вместо выполнения SQL откройте базу данных Access с помощью ДАО, а затем используйте методы Добавить новое и Обновлять для добавления записей. Это будет намного быстрее. Или переверните его вверх дном и используйте Access для импорта электронной таблицы.

Gustav 13.06.2019 15:45

Спасибо. Знаете ли вы способ отфильтровать столбец по определенной дате с помощью DAO?

piele 13.06.2019 16:31

Execute не будет работать с SELECT, только с действиями SQL (DELETE, INSERT, UPDATE). В Access используйте совокупность доменов, например DCount, DLookup или DSum. В Excel откройте объект набора записей и поле ссылки.

June7 13.06.2019 21:30

@Gustav Я пытался изучить возможность использования DAO, но у меня возникли трудности, и многие рекомендуют ADO, называя DAO глючным. Можете ли вы объяснить разницу?

piele 14.06.2019 08:31

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

Gustav 14.06.2019 10:02
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
1
7
175
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Оптимальным путем оказалось использование опций DAO.Recordset и DAO.Database, а также множество настроек для оптимизации.

Самым большим сокращением было использование «Набор записей.FindFirst», чтобы определить, следует ли только добавлять данные (занимает 22 секунды) или обновлять данные с идентичной датой (занимает 12 минут). Хотя в основном будет происходить сценарий на 22 секунды.

Сценарий на 12 минут не оптимизирован, так как встречается редко.

Полное решение:

Sub OpdaterKvant()

Dim wb As Workbook
Dim wbOp As Workbook
Dim ws As Worksheet
Dim wsOp As Worksheet
Dim i, n As Integer

Dim db As DAO.Database
Dim rsScores As DAO.Recordset
Dim rsHistory As DAO.Recordset

StartTime = Timer

Call PERFORMANCEBOOST(False)

Set PB = CREATEPROGRESSBAR
    With PB
        .SetStepCount (4)
        .Show
        End With

    Set wbOp = ThisWorkbook
    Set wsOp = wbOp.ActiveSheet

'Step 1: Open JQGCLE
    Set wb = Workbooks.Open("Location", ReadOnly:=True)
    Set ws = wb.Worksheets(1)
        ws.Activate

    n = ws.UsedRange.Rows.Count

    DateIn = Right(ws.Cells(1, 1), 2) & "-" & Mid(ws.Cells(1, 1), 5, 2) & "-" & Left(ws.Cells(1, 1), 4)

'Step 2: Optag værdier i Excel
    PB.Update "Data hentes fra JQGLCE-ark"

    ReDim Sedol(3 To n) As String
    ReDim Company(3 To n) As String
    ReDim Country(3 To n) As String
    ReDim Region(3 To n) As String
    ReDim Sector(3 To n) As String
    ReDim MarketCap(3 To n) As String 'Tal
    ReDim MarketCapSQL(3 To n) As String 'Tal
    ReDim JQRank(3 To n) As String
    ReDim ValueRank(3 To n) As String
    ReDim QualityRank(3 To n) As String
    ReDim MomentumRank(3 To n) As String
    ReDim JQScore(3 To n) As String 'Tal
    ReDim JQScoreSQL(3 To n) As String 'Tal

    For i = 3 To n

        Sedol(i) = Trim(ws.Cells(i, 1))
        Company(i) = Left(Replace(ws.Cells(i, 2), "'", ""), Len(Replace(ws.Cells(i, 2), "'", "")) - 0) 'Stod tidligere på minus 1 - Hvorfor?
        Country(i) = Trim(ws.Cells(i, 3))
        Region(i) = Trim(ws.Cells(i, 4))
        Sector(i) = Trim(ws.Cells(i, 5))
        MarketCap(i) = ws.Cells(i, 6) 'Til DAO
        MarketCapSQL(i) = Replace(ws.Cells(i, 6), ",", ".") 'Til SQL
        JQRank(i) = Trim(ws.Cells(i, 7))
        ValueRank(i) = Trim(ws.Cells(i, 8))
        QualityRank(i) = Trim(ws.Cells(i, 9))
        MomentumRank(i) = Trim(ws.Cells(i, 10))
        JQScore(i) = ws.Cells(i, 11) 'Til DAO
        JQScoreSQL(i) = Replace(ws.Cells(i, 11), ",", ".") 'Til SQL

        'DAO og SQL bliver behandlet forskelligt ift. komma

        Next i

'Step 3: Indsæt værdier i Access-database
    Set acc = New Access.Application
    Set db = acc.DBEngine.OpenDatabase("Location", 1, 0)

    'Step 3.1: JQScores
        PB.Update "JQScores indsættes i databasen"

        Set rsScores = db.OpenRecordset(Name: = "JQScores", Type:=RecordsetTypeEnum.dbOpenDynaset)
        db.Execute "DELETE * FROM JQScores"

        For i = 3 To n

            With rsScores
                .AddNew
                !Sedol = Sedol(i)
                !Company = Company(i)
                !Region = Region(i)
                !Sector = Sector(i)
                !MarketCapUSD = MarketCap(i)
                !JQ_Rank = JQRank(i)
                !Value_Rank = ValueRank(i)
                !Quality_Rank = QualityRank(i)
                !Momentum_Rank = MomentumRank(i)
                !JQ_Score = JQScore(i)
                !Country = Country(i)
                .Update

                End With

            Next i

            rsScores.Close
        Set rsScores = Nothing

    'Step 3.2: JQHistory
        Set rsHistory = db.OpenRecordset(Name: = "JQHistory", Type:=RecordsetTypeEnum.dbOpenDynaset)

        With rsHistory

        If .RecordCount <> 0 Then

        i = 3

        .FindFirst "History_Date = '" & DateIn & "'"
            If .NoMatch = True Then
            'Hvis datoen ikke er i datasættet, bliver dataen tilføjet

                PB.Update "Hurtig: JQHistory indsættes i databasen"

                For i = 3 To n
                    .AddNew
                    !History_Date = DateIn
                    !Sedol = Sedol(i)
                    !Selskabsnavn = Company(i)
                    !MarketCap = MarketCap(i)
                    !JQ_Rank = JQRank(i)
                    !Value_Rank = ValueRank(i)
                    !Quality_Rank = QualityRank(i)
                    !Momentum_Rank = MomentumRank(i)
                    !JQScore = JQScore(i)
                    .Update

                    Next i

                Else
                'Hvis datoen allerede er der, skal den opdateres
                    PB.Update "Langsom: JQHistory indsættes i databasen"

                    For i = 3 To n

                        db.Execute ("UPDATE JQHistory SET MarketCap = " & MarketCapSQL(i) & ", Selskabsnavn='" & Company(i) & "', JQ_Rank='" & JQRank(i) & "', Value_Rank='" & ValueRank(i) & "', Quality_Rank='" & QualityRank(i) & "', Momentum_Rank='" & MomentumRank(i) & "', JQScore = " & JQScoreSQL(i) & " WHERE SEDOL='" & Sedol(i) & "' and History_Date='" & DateIn & "'")

                        Next i

                End If

            End If
            End With

            rsHistory.Close
        Set rsHistory = Nothing

'Step 4: Færdiggørelse

    acc.DoCmd.Quit acQuitSaveAll 'Lukker og gemmer database
    Set db = Nothing

    wsOp.Activate
    wsOp.Range("B7").Value = "Seneste data benyttet: " & DateIn
    wb.Close SaveChanges:=False

    Call PERFORMANCEBOOST(True)

    Unload PB

    MinutesElapsed = Format((Timer - StartTime) / 86400, "hh:mm:ss")

    MsgBox "Opdatering fuldført. Proceduren tog " & MinutesElapsed & "."

End Sub

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

ComputerVersteher 14.06.2019 17:04

Помимо структуры, вы можете запросить диапазоны Excel с помощью SQL (например, SELECT * FROM [Sheet1$A1: CV5000]), а затем присоединиться, чтобы определить, существуют ли данные или нет, что приводит к 2 запросам (вставьте, где объединенное поле равно NULL, обновите то, что нет). Или создайте набор записей (например: Диапазон для набора записей без установления соединения вместо создания массива для циклического просмотра данных excel.

ComputerVersteher 14.06.2019 17:15

1. Обновление структуры данных; Невозможно в моей ситуации из-за людей, зависящих от данных. 2. Объединение данных; Позволит ли это сократить обновление до 1 запроса? Несмотря на то, что сценарий обновления встречается редко, было бы целесообразно сократить время выполнения до секунд вместо 12 минут.

piele 15.06.2019 18:33

1: Изменение структуры данных — это долгосрочная цель, которую нельзя сделать сразу, но об этом следует помнить. 2: Если вы извлекаете данные Excel с помощью SQL Select, напримерSELECT * FROM [Sheet1$A1: CV5000], вы можете присоединиться к ним в других таблицах. Просто адаптируйте лист и диапазон и используйте его как подзапрос.

ComputerVersteher 15.06.2019 18:39

Другие вопросы по теме