Мне нужна помощь с массивным циклом по постоянно расширяющейся базе данных 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
Вы неправильно поняли мою попытку улучшить контекст. Он чередуется между базой данных под названием «JQScores» и «JQHistory». JQScores состоит из 3 000 еженедельно обновляемых строк, где JQHistory представляет собой временной ряд, состоящий из 280 000 строк JQScores, которые еженедельно добавляются, чтобы показать эволюцию с течением времени :)
Вместо выполнения SQL откройте базу данных Access с помощью ДАО, а затем используйте методы Добавить новое и Обновлять для добавления записей. Это будет намного быстрее. Или переверните его вверх дном и используйте Access для импорта электронной таблицы.
Спасибо. Знаете ли вы способ отфильтровать столбец по определенной дате с помощью DAO?
Execute не будет работать с SELECT, только с действиями SQL (DELETE, INSERT, UPDATE). В Access используйте совокупность доменов, например DCount, DLookup или DSum. В Excel откройте объект набора записей и поле ссылки.
@Gustav Я пытался изучить возможность использования DAO, но у меня возникли трудности, и многие рекомендуют ADO, называя DAO глючным. Можете ли вы объяснить разницу?
Просто найдите его. DAO является родным для Access и ни в коем случае не глючит. ADO тоже можно использовать, но не быстрее.
Оптимальным путем оказалось использование опций 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 представляет ваши фактические данные, и вы получите историю вставленных данных, поскольку вы также можете запросить максимальную отметку времени, которая ниже даты, которую вы хотите увидеть данные в.
Помимо структуры, вы можете запросить диапазоны Excel с помощью SQL (например, SELECT * FROM [Sheet1$A1: CV5000]
), а затем присоединиться, чтобы определить, существуют ли данные или нет, что приводит к 2 запросам (вставьте, где объединенное поле равно NULL, обновите то, что нет). Или создайте набор записей (например: Диапазон для набора записей без установления соединения вместо создания массива для циклического просмотра данных excel.
1. Обновление структуры данных; Невозможно в моей ситуации из-за людей, зависящих от данных. 2. Объединение данных; Позволит ли это сократить обновление до 1 запроса? Несмотря на то, что сценарий обновления встречается редко, было бы целесообразно сократить время выполнения до секунд вместо 12 минут.
1: Изменение структуры данных — это долгосрочная цель, которую нельзя сделать сразу, но об этом следует помнить. 2: Если вы извлекаете данные Excel с помощью SQL Select, напримерSELECT * FROM [Sheet1$A1: CV5000]
, вы можете присоединиться к ним в других таблицах. Просто адаптируйте лист и диапазон и используйте его как подзапрос.
Хм, не похоже, что вы можете сделать несколько операторов вставки с доступом: stackoverflow.com/questions/62504/…. Неужели нужно удалять 100% данных в таблице, а потом каждый день вставлять по 280к+ строк? Есть ли лучший способ структурировать вашу информацию или данные, который не включает этот шаг?