excel vba run sql query on entire array - excel

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

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

Multiple-step OLE DB Operation generated errors. Check each OLE DB Status Value

Please help me. Excel VBA is throwing this error: Multiple-step OLE DB Operation generated errors
on the line : cmd.Parameters("[days]") = ActiveWorkbook.Sheets("MABI_Extracts").Range("B18").Value
I'm trying to run a query in access named "AGBA_Conversion" with an integer parameter. How do I resolve? thanks in advance :)
Sub RECT_MBTCLeads2_Extracts()
Dim cmd As New ADODB.Command, rs As ADODB.Recordset
Dim sht As Worksheet
Dim b As String
Dim d As Long
Dim a As String
Set sht = ActiveWorkbook.Sheets("AGBA_Conversion")
sht.Range("B1").ClearContents
cmd.ActiveConnection = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Users\nemberga\OneDrive - AXA\Documents\Automation\MBTC Conversion.accdb"
cmd.CommandType = adCmdText
cmd.CommandText = "AGBA_Conversion" '<<<--here is where you write your query sql
cmd.Parameters("[days]") = ActiveWorkbook.Sheets("MABI_Extracts").Range("B18").Value
Options = adCmdTable
Set rs = cmd.Execute
sht.Range("A1").CopyFromRecordset rs '--bulk copy to the Excel sheet
rs.Close
cmd.ActiveConnection.Close
MsgBox "All data were successfully retrieved from the queries!", vbInformation, "Done"
End Sub
Parameters is a collection of Parameter. You need first to create the object using CreateParameter, then to add it to the collection using Append.
Replace
cmd.Parameters("[days]") = ActiveWorkbook.Sheets("MABI_Extracts").Range("B18").Value
By
Dim p
Set p = cmd.CreateParameter("days", adInteger, adParamInput)
p.Value = ActiveWorkbook.Sheets("MABI_Extracts").Range("B18").Value
cmd.Parameters.Append p
See https://learn.microsoft.com/fr-fr/sql/ado/reference/ado-api/append-and-createparameter-methods-example-vb?view=sql-server-ver15

Need help optimizing vba display function

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

When I call a query from Access using VBA, I can't know how many records are on that query. Why?

I did a query on MS Access 2010, with the Query Builder, now I'm calling it from VBA. But when I try to use the recordcount to know how many records are on this query, the result is -1.
The code:
Public Function teste(Optional ByVal consulta As String, Optional ByVal cbAR_valor As String, Optional ByVal cbAR_valor2 As String)
Dim DB As ADODB.Connection
Dim RST As ADODB.Recordset
Dim Query As ADODB.Command
Dim parametro1 As ADODB.Parameter
Dim parametro2 As ADODB.Parameter
On Error GoTo trataErro
DB_Dir = ThisWorkbook.Path & "\" & "DB2.accdb"
Set DB = New ADODB.Connection
DB.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & DB_Dir & ";Persist Security Info=False"
Set Query = New ADODB.Command
Set Query.ActiveConnection = DB
Query.CommandText = consulta
Query.CommandType = adCmdStoredProc
' Pego o valor do param. e adiciono no command
Set parametro1 = Query.CreateParameter("AR1", adChar, adParamInput, 255)
Query.Parameters.Append parametro1
parametro1.Value = cbAR_valor
' Pego o valor do param. e adiciono no command
Set parametro2 = Query.CreateParameter("AR2", adChar, adParamInput, 255)
Query.Parameters.Append parametro2
parametro2.Value = cbAR_valor2
Set RST = Query.Execute
a = RST.RecordCount
linha = 1
coluna = 1
ActiveWorkbook.Sheets("Plan2").Cells(CInt(linha), CInt(coluna)).CopyFromRecordset RST
DB.Close
Set DB = Nothing
Exit Function
trataErro:
MsgBox ("Erro: " & Err.Description)
End Function
RecordCount is tricky. Depending on the provider, data source type, and options, it may not be reliable.
For an ADO recordset, you are more likely to get a reliable RecordCount with a client-side cursor:
Set DB = New ADODB.Connection
DB.CursorLocation = adUseClient '<- add this
DB.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & DB_Dir & ";Persist Security Info=False"
I assumed your procedure's consulta argument is a SQL statement. If that is correct, use adCmdText (instead of adCmdStoredProc) for CommandType:
Set Query = New ADODB.Command
'Set Query.ActiveConnection = DB ' Set not needed here ...
Query.ActiveConnection = DB
Query.CommandText = consulta
'Query.CommandType = adCmdStoredProc
Query.CommandType = adCmdText
If those changes were not sufficient to get you a reliable RecordCount, use MoveLast to ensure the recordset is fully populated before asking for RecordCount:
Set RST = Query.Execute
RST.MoveLast
MsgBox "RecordCount: " & RST.RecordCount
Add RST.MoveFirst afterward if you need the recordset pointer to be on the first row in preparation for your remaining operations.
When I've had trouble with counting records before, the issue was with the way the recordset was opened, not the connection. The cursor type needs to be static for it to work. This may be very similar to what HansUp was referring to with the cursorlocation in the connection object, though.
rec.Open querystr, con, 3
rec.movelast
cnt = rec.RecordCount
rec.movefirst

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