Need help optimizing vba display function - excel

I have been doing some database work with excel both as the database and the database driver via macros with vba. I built a function that should parse through a list of database records with testID fields. I wanted to display each test only once based on it's testID, but the way the database is set up means I have to eliminate duplicate testID's. I do this by iterating through the recordset and checking the current test against the previous one before showing in the list. The problem I'm having is that the function is excruciatingly slow. For only 12 tests in the database, it takes about 3 seconds to display them in the view spreadsheet. I'd love to hear some ideas on how to optimize the run-time. Here's the function:
Public Function showAllTests()
Dim conn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim cstring, sql As String
Dim r, c As Integer
Dim testsAr As Variant
Dim inAr As Boolean
cstring = "Provider = Microsoft.ACE.OLEDB.12.0; Data Source=I:\DBtrials.xlsx; Extended Properties=""Excel 12.0 Xml; HDR=YES;ReadOnly=False"";"
sql = "SELECT [TestID], [Status], [PFIBox], [WireType], [StartingDia], [Customer], [numSamples], [Assigned] FROM [Tests$]"
Set conn = New ADODB.Connection
Set rs = New ADODB.Recordset
Call conn.Open(cstring)
Set rs = conn.Execute(sql)
If rs.EOF Then
Range("C6:J1000").ClearContents
End If
r = 6
count = 0
'Iterates through the recordset, eliminating duplicates and populating cells in the tests sheet
While Not rs.BOF And Not rs.EOF
Dim prevID, currID As String
Dim currCell As Range
inAr = False
If Not count = 0 Then
prevID = ActiveWorkbook.Sheets("Tests").Cells(r - 1, 3).Value
currID = CStr(rs(0))
If prevID = currID Then
inAr = True
End If
End If
For c = 3 To (rs.Fields.count + 2)
Set currCell = ActiveWorkbook.Sheets("Tests").Cells(r, c)
If Not IsNull(rs(c - 3).Value) And inAr = False Then
currCell.Value = CStr(rs(c - 3))
ElseIf IsNull(rs(c - 3).Value) Then currCell.Value = ""
Else:
Exit For
End If
Next c
If inAr = False Then
r = r + 1
End If
rs.MoveNext
count = count + 1
Wend
conn.Close
Set conn = Nothing
End Function

use GROUP BY
sql = "SELECT [TestID], [Status], [PFIBox], [WireType], [StartingDia], [Customer], [numSamples], [Assigned] FROM [Tests$] GROUP BY [TestID]"
Also some of these drivers - Microsoft.ACE.OLEDB.12.0 etc have terrible perfomance in VBA. sometimes i get better perforamance from OBDC 6.2 than ADO

Related

Single Result from recordset returning w/o being transposed

I am working on a project where I'm using SQL to return records from a database into a listbox using userinput. Things are fine when there's more than one result, the listbox reads properly. However, when there's only one record, it doesn't transpose it like it does with all the others. Multiple Returns Single Return
I've tried to do an "if employeeoutput.recordcount = 1 then don't transpose" type of thing, but that still sends me with the Single Return.
Private Sub cmdSearch_Click()
Stop
'connect to database
'make dynamic code
'profit
'set up DB and Recordset
Dim calldbconn As ADODB.Connection
Dim EmployeeOutput As ADODB.Recordset
Dim EmpData As Variant
'its a special tool we're going to use later
Dim InContactInput As String
Dim FNameInput As String
Dim LNameInput As String
Dim TeamNameInput As String
Dim TeamNumberInput As Integer
Dim SQLString As String
'set it up for a new recordset
Set calldbconn = New ADODB.Connection
Set EmployeeOutput = New ADODB.Recordset
'set the vars
InContactInput = txtInContactNum.Value
FNameInput = txtFirstName.Value
LNameInput = txtLastName.Value
TeamNameInput = cboTeamName.Value
TeamNumberInput = cboTeamName.ListIndex + 1
'---------A bunch of stuff here to build the SQL String----------
'----------------------------------------------------------------
'Time to connect
calldbconn.ConnectionString = StrCallDB
calldbconn.Open
On Error GoTo CloseConnection
With EmployeeOutput
.ActiveConnection = calldbconn
.Source = SQLString
.LockType = adLockOptimistic
.CursorType = adOpenKeyset
.Open
EmpData = .GetRows
On Error GoTo CloseRecordset
End With
'And now, the list box
With lstResults
.Clear
.List = Application.Transpose(EmpData)
End With
'close and reset
CloseRecordset:
EmployeeOutput.Close
CloseConnection:
calldbconn.Close
Set EmployeeOutput = Nothing
Set calldbconn = Nothing
End Sub

Adding Records to Access Table (Inconsistent Results)

I am working on a VBA project in Excel as a record management tool. In one case, a record(s) need to be added to 2 tables within the same Access DB with varying field info. I am able to successfully add to the first table (Tours_Items) which can add multiple records by looping through the available rows. However, I cannot get the following records to add to their respective table (Tours_Tours). When the code is executed I get the following error: "Run-time error'438': Object doesn't support this property or method".
The VBA for executing on Tours_Items is just about identical to Tours_Tours outside of the table names and the looping feature that is required for Tours_Items so I am just utterly bamboozled as to why it will not execute correctly when the more complicated one will. The subs run one after the other; Tours_Items and then Tours_Tours. I have even tried just executing Tours_Tours and I still get the same error.
The fields correctly match the Access DB fields (I have triple checked), and even if they did not, VBA would not throw the object error. The Access DB fields are appropriately configured to accept the types of data that are being added to the record.
Code:
Sub DBAddTours_Items()
Dim DBFile, Tbl As String
Dim SQL As String
Dim j, NxtRowItems, NxtItemRw As Integer
Dim con, rs As Object
Dim wsToursTtl As Worksheet
Set wsToursTtl = Worksheets("Tour Subtotal")
'Fill Items to DB
'Connect to the Database
Set con = CreateObject("ADODB.connection")
'Set File and tables
DBFile = ThisWorkbook.path & "\" & "DailyInfo.accdb"
con.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & DBFile
'Define
NxtRowItems = WorksheetFunction.CountA(wsToursTtl.Range("B:B"))
NxtItemRw = WorksheetFunction.CountA(Sheets("Tours_Items").Range("A:A")) + 1
'Create the ADODB recordset object.
Set rs = CreateObject("ADODB.Recordset")
'Define Table and SQL
Tbl = "Tours_Items"
SQL = "SELECT * FROM " & Tbl
'Set the necessary recordset properties.
With rs
.CursorType = 1 'adOpenKeyset on early binding
.LockType = 3 'adLockOptimistic on early binding
'Open the recordset.
.Open SQL, con
'Loop through and add all items
For j = 2 To NxtRowItems
.AddNew
.Fields("ItemID") = (j - 3) + NxtItemRw
.Fields("TourID") = wsToursTtl.Cells(j, 1)
.Fields("ItemNum") = wsToursTtl.Cells(j, 2)
.Fields("Admission") = wsToursTtl.Cells(j, 3)
.Fields("Ticket") = wsToursTtl.Cells(j, 4)
.Fields("Premium/BAT") = wsToursTtl.Cells(j, 5)
.Fields("Tour") = wsToursTtl.Cells(j, 6)
.Fields("Price") = wsToursTtl.Cells(j, 7)
.Update
Next j
'Close Recordset
.Close
End With
'Close the connection.
con.Close
'Release Variables
Set rs = Nothing
Set con = Nothing
SQL = ""
Tbl = ""
End Sub
Sub DBAddTours_Tours()
Dim DBFile, Tbl As String
Dim SQL As String
Dim con, rs As Object
Dim wsToursTtl As Worksheet
Set wsToursTtl = Worksheets("Tour Subtotal")
'Fill Tour info Tours_Tours
'Connect to the Database
Set con = CreateObject("ADODB.connection")
'Set File and tables
DBFile = ThisWorkbook.path & "\" & "DailyInfo.accdb"
con.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & DBFile
'Create the ADODB recordset object.
Set rs = CreateObject("ADODB.Recordset")
'Define Table and SQL
Tbl = "Tours_Tours"
SQL = "SELECT * FROM " & Tbl
'Set the necessary recordset properties.
With rs
.CursorType = 1 'adOpenKeyset on early binding
.LockType = 3 'adLockOptimistic on early binding
'Open the recordset.
.Open SQL, con
'Add new record to table
.AddNew
.Field("TourID") = wsToursTtl.Range("TourID_Dest").Value
.Field("TourDate") = wsToursTtl.Range("TourDate_Dest").Value
.Field("TourTime") = wsToursTtl.Range("TourTime_Dest").Value
.Field("Premium/Bat/Disc") = Me.PremiumBAT_Lbl.Caption
.Field("TourType") = wsToursTtl.Range("TourType_Dest").Value
.Field("GuestName") = wsToursTtl.Range("GuestName_Dest").Value
.Field("GuestAddress") = wsToursTtl.Range("GuestAddress_Dest").Value
.Field("GuestPhone") = wsToursTtl.Range("GuestPhone_Dest").Value
.Field("GuestEmail") = wsToursTtl.Range("GuestEmail_Dest").Value
.Field("GuestMember") = wsToursTtl.Range("GuestMem_Dest").Value
.Field("TourComments") = wsToursTtl.Range("Comments_Dest").Value
.Field("SaleDate") = wsToursTtl.Range("SaleDate_Dest").Value
.Field("TransactionDetails") = wsToursTtl.Range("Trxn_Dest").Value
.Field("SoldBy") = wsToursTtl.Range("SoldBy_Dest").Value
.Field("PaymentType") = wsToursTtl.Range("PayType_Dest").Value
.Field("BookedNum") = 2
.Field("LastModified") = Now
.Field("ModifiedBy") = Application.UserName
.Update
'Close Recordset
.Close
End With
'Close the connection.
con.Close
'Release Variables
Set rs = Nothing
Set con = Nothing
SQL = ""
Tbl = ""
End Sub
Any help is much appreciated!
The correct collection to access a field is .Fields plural.
So DBAddTours_Items is correct where in your other procedure, DBAddTours_Tours you called upon .Field singular. Which doesn't exist.
Documentation found here: https://learn.microsoft.com/en-us/sql/ado/guide/data/the-fields-collection?view=sql-server-ver15

Import formula from MS Access database to MS Excel

I've been fighting for a long time with import formula record in database (=45+5 or =SUM(D20:D23)) to Excel worksheet. My problem is when I import formula from database in Excel cell is raw text from database record (=45+5 or =SUM(D20:D23)).
It's fine, but I'm not able to calculate this formula. I've tried options in VBA after importing from database like:
ActiveSheet.Calculate, Worksheets(1).Calculate, ActiveWorkbook.Calculate
Only manual one manual option works. When I set to cell with formula and hit ENTER.
But I want to automate this process.
----EDIT----
Original code:
Sub GetDataFieldInfo()
Dim conn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim address_complete As String
Dim address_column As Integer
Dim address_row As Integer
Dim j As Integer
db_file = ThisWorkbook.Path & "\Example_db.mdb"
Set conn = New ADODB.Connection
conn.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;" & _
";Data Source=" & db_file & _
";Persist Security Info=False"
conn.Open
Set rs = conn.Execute("SELECT * FROM Students")
For i = 0 To rs.Fields.Count - 1
address_complete = findCellAddress(rs(i).Name)
address_row = Range(address_complete).Row
address_column= Range(address_complete).Column
rs.MoveFirst
j = 0
Do Until rs.EOF
If InStr(1, rs.Fields(rs(i).Name), "=", 1) Then
Cells(address_row + 1 + j, address_column).Formula = rs.Fields(rs(i).Name).Value
Else
Cells(address_row + 1 + j, address_column).Value = rs.Fields(rs(i).Name)
End If
j = j + 1
rs.MoveNext
Loop
Next
rs.Close
End Sub
You are probably doing Range("A1") = "=SUM(D20:D23)".
Instead of this you should do Range("A1").value = "=SUM(D20:D23)".
If this is not the case, please show us the VBA code you are using to export from msaccess to excel. It will help a lot.

excel vba run sql query on entire array

I have an sql query that is running in a loop in excel. It works while the array is small. However the array is now up to around 4000 items and it causes excel to crash. Is there a better way to do this, am I missing some easy way to do this
For k = 0 To ubound(sqlarray)
Dim conn As ADODB.Connection
Dim recst As ADODB.Recordset
Dim sqlstring As String
Set conn = New ADODB.Connection
Set recst = New ADODB.Recordset
conn.ConnectionString = "Driver={Client Access ODBC Driver (32-
bit)};System=mysys ;NAM=1;CurrentSchema=myschema;"
conn.Open
Sql = "Select ACCOUNT_NBR, ASSIGNED_TO from prodlib.gr_exp_account where
account_nbr in ('" & sqlarray(k) & "' )"
Set recst.ActiveConnection = conn
recst.Open Sql, conn
Sheets("recst").Range("A" & k).CopyFromRecordset recst
conn.Close
Application.ScreenUpdating = True
Next k
End Sub
As noted in my comment above you should be submitting a single SQL statement and getting a single result set back. Looping through every customer and submitting SQL for it is bad.
Consider something like the following:
Sub whatever(sqlArray as string)
'initialize these outside your loop next time too
Dim conn As ADODB.Connection
Dim recst As ADODB.Recordset
Dim sqlstring As String
Set conn = New ADODB.Connection
Set recst = New ADODB.Recordset
conn.ConnectionString = "Driver={Client Access ODBC Driver (32-
bit)};System=mysys ;NAM=1;CurrentSchema=myschema;"
conn.Open
'All the accounts in one list in one statement. Use array function 'Join'
'to make the array a comma delimited string (or comma with quotes as a delim)
Sql = "Select ACCOUNT_NBR, ASSIGNED_TO from prodlib.gr_exp_account where
account_nbr in ('" & Join(sqlArray, "','") & "' )"
Set recst.ActiveConnection = conn
recst.Open Sql, conn
'Drop the entire recordset in a sheet starting at "A1"
Sheets("recst").Range("A1").CopyFromRecordset recst
conn.Close
Application.ScreenUpdating = True
End Sub
If JOIN() has a problem with the size of the array, or your database is complaining about the number of accounts you are sending in at a time, you could still loop, but using bigger chunks:
Sub whatever(sqlArray As String)
'initialize these outside your loop next time too
Dim conn As ADODB.Connection
Dim recst As ADODB.Recordset
Dim sqlstring As String
Set conn = New ADODB.Connection
conn.ConnectionString = "Driver={Client Access ODBC Driver (32-bit)};System=mysys ;NAM=1;CurrentSchema=myschema;"
conn.Open
Dim k As Integer
Dim chunk As Integer
'Set this to how many accout numbers you can squeeze through in a single request safely
chunk = 400
For k = 1 To UBound(sqlArray) Step chunk
'reset this object in the loop
Set recst = New ADODB.Recordset
'All the accounts in one list in one statement. Use array function 'Join'
'to make the array a comma delimited string (or comma with quotes as a delim)
Sql = "Select ACCOUNT_NBR, ASSIGNED_TO from prodlib.gr_exp_account where account_nbr in ('" & Join_With_Limit(sqlArray, "','", chunk, k) & "' )"
Set recst.ActiveConnection = conn
recst.Open Sql, conn
Sheets("recst").Range("A" & k).CopyFromRecordset recst
'close this for the next loop
recst.close
Next k
conn.Close
Application.ScreenUpdating = True
End Sub
Function Join_With_Limit(inArray As Variant, strDelim As String, intLimit As Integer, intOffset As Offset) As String
Dim i As Integer
For i = intOffset To intOffset + intLimit
'make sure we are below the ubound of the array
if i > uBound(inArray) Then Exit For
'If this isn't the first thing we are adding, then stick the delimiter
'before dropping in the value
If i <> intOffset Then Join_With_Limit = Join_With_Limit + strDelim
'Add the next item
Join_With_Limit = Join_With_Limit + inArray(i)
Next i
End Function

Parse ADO Recordset to Individual Excel Sheets

I'm trying to take an ADO Recordset and then loop through it to parse individual rows to different worksheets within an Excel workbook. Unfortunately, as I step through my code, I get the following error: Run-time error '13': Type mismatch. This occurs when I call the sub in my code - it never actually steps into the sub. I'm wondering if I'm somehow not passing the Recordset in correctly or if it's a problem somewhere within my loop.
Regardless, here's my code - any help is greatly appreciated!
Sub SplitData(ByVal rs As ADODB.Recordset)
' Instantiate count variables for each result type
' Start at 2 to give room for Table headers on sheets
Dim NewAppsCount, BadLogCount, MatNotesCount, ZeroBalCount As Integer
NewAppsCount , BadLogCount, MatNotesCount, ZeroBalCount = 2
' Row Counter
Dim Count As Long
Count = 0
' Loop through the recordset and parse rows to appropriate worksheets
Do While Not rs.EOF
If CStr(rs.Fields("Maturity Date")) = "" Then
If CStr(rs.Fields("Log_Date")) = "" Then
' Applications that have not been properly logged
Sheet4.Range("A" & CStr(BadLogCount)) = rs.Fields(Count).Value
Count = Count + 1
BadLogCount = BadLogCount + 1
Else
' New Applications
Sheet6.Range("A" & CStr(NewAppsCount)) = rs.Fields(Count).Value
Count = Count + 1
NewAppsCount = NewAppsCount + 1
End If
Else
If Month(rs.Fields("Maturity Date")) < Month(Date) Then
' Maturing Notes with Zero Outstanding Balance
Sheet7.Range("A" & CStr(ZeroBalCount)) = rs.Fields(Count).Value
Count = Count + 1
ZeroBalCount = ZeroBalCount + 1
Else
' Maturing Notes
Sheet8.Range("A" & CStr(MatNotesCount)) = rs.Fields(Count).Value
Count = Count + 1
MatNotesCount = MatNotesCount + 1
End If
End If
rs.MoveNext
Loop
End Sub
Here is the sub that call GetData:
Sub GetData(ByVal Update As Boolean)
Dim conn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim query As String
Dim path As String
Dim prompt, result As Integer
Dim day, today As String
' ...skipping stuff not related to the issue...
' Set the UNC Path
path = "\\this\is\the\path"
' Instantiate ADO Objects
Set conn = New ADODB.Connection
Set rs = New ADODB.Recordset
' Connect to data source
conn.Open "Provider=Microsost.JET.OLEDB.4.0;Data Source=" & path & ";"
' The Query
query = "This is a big 'ol query that I won't repost here"
'Run the query and populate the Recordset object
rs.CursorLocation = adUseClient
rs.Open query, conn, adOpenStatic, adLockReadOnly
'Parse contetns of Recordset to worksheet
Application.ScreenUpdating = False
Me.SplitData(rs)
'Close the ADO Objects, set them to null, and exit sub
rs.Close
conn.Close
Set rs = Nothing
Set conn = Nothing
Exit Sub
End Sub
Try changing:
Me.SplitData(rs)
to:
Me.SplitData rs
Unnecessary parentheses often cause problems in VBA.
(NB I'm assuming that the two Sub shown are in a context where Me makes sense - e.g. class module, ThisWorkbook module, worksheet module, backing a UserForm etc)

Resources