patternMinor
Making repeated ADODB queries from Excel-SQL Server
Viewed 0 times
adodbmakingrepeatedexcelsqlserverqueriesfrom
Problem
I need to run 12 queries on a SQL Server DB. Currently I pass the "SELECT..." string to a function below.
Is it optimal to open and close the connection again and again (as my code is doing), or can I set up an ADODB connection as a global variable and reuse the same connection?
Is it optimal to open and close the connection again and again (as my code is doing), or can I set up an ADODB connection as a global variable and reuse the same connection?
Sub ConnectServer(FileName As String, rngToPrint As Range, strSqlQuery As String)
Dim conn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim sConnString As String
Dim iCols As Long
If Not (rs Is Nothing) Then
If (rs.State And adStateOpen) = adStateOpen Then rs.Close
Set rs = Nothing
End If
sConnString = "Poop;"
Set conn = New ADODB.Connection
Set rs = New ADODB.Recordset
conn.CommandTimeout = 90
conn.Open sConnString
Set rs = conn.Execute(strSqlQuery)
'Add Headers
For iCols = 0 To rs.Fields.Count - 1
Sheets(rngToPrint.Worksheet.Name).Cells(rngToPrint.Row - 1, iCols + 1).Value = rs.Fields(iCols).Name
Next
If Not rs.EOF Then
' Transfer result.
rngToPrint.CopyFromRecordset rs
' Close the recordset
rs.Close
Else
MsgBox "Error: No records returned.", vbCritical
End If
If CBool(conn.State And adStateOpen) Then conn.Close
Set conn = Nothing
Set rs = Nothing
End SubSolution
can I set up an ADODB connection as a global variable and reuse the same connection?
That would be the single worst thing to do. First because you would be making a critical resource globally scoped, and therefore you would lose control over its lifetime.
Second, because ADODB does connection pooling for you anyway, so the "cost" of opening a connection isn't really a thing; if a connection exists in the connection pool and is free to use, that's the connection ADODB will give you - it's there already.
From code perspective, a database connection should always be as short-lived as possible: open, fetch/execute, close.
This is interesting:
That whole block is never going to execute. You just declared
If no error occurred, your connection is always going to be opened here. And if an error occurred, your code isn't even running anymore. Make the
Actually, that
The parentheses are redundant here:
The explicit conversion to
That way you can do
I said earlier:
And if an error occurred, your code isn't even running anymore.
That's quite a problem. You need to handle runtime errors. Database connections drop, a syntax error in
First thing to fix is this:
The
Why retrieve the worksheet from the
Now, handle runtime errors:
With this pattern you close the recordset and connection under the
Your parameters are all passed
Hungarian Notation prefixes are superfluous, and misleading:
Whenever you feel the need to comment "this chunk of code does X", like here:
...you
That would be the single worst thing to do. First because you would be making a critical resource globally scoped, and therefore you would lose control over its lifetime.
Second, because ADODB does connection pooling for you anyway, so the "cost" of opening a connection isn't really a thing; if a connection exists in the connection pool and is free to use, that's the connection ADODB will give you - it's there already.
From code perspective, a database connection should always be as short-lived as possible: open, fetch/execute, close.
This is interesting:
Dim rs As ADODB.Recordset
If Not (rs Is Nothing) Then
If (rs.State And adStateOpen) = adStateOpen Then rs.Close
Set rs = Nothing
End IfThat whole block is never going to execute. You just declared
rs and haven't assigned it yet: it's always going to be Nothing.If CBool(conn.State And adStateOpen) Then conn.Close
Set conn = Nothing
Set rs = NothingIf no error occurred, your connection is always going to be opened here. And if an error occurred, your code isn't even running anymore. Make the
conn.Close unconditional, and get rid of the redundant assignments to Nothing; the objects are going out of scope at End Sub, they're being destroyed anyway.Actually, that
Set rs = Nothing is potentially extending the lifetime of the rs object: without it, there's no in-scope reference to rs beyond rs.Close, and (assuming the VBA runtime can destroy objects while inside a procedure scope) VBA can destroy the object. But because of that Set rs = Nothing, you're keeping a reference around, and VBA cannot destroy the object until it passes that instruction.If Not (rs Is Nothing) ThenThe parentheses are redundant here:
If Not rs Is Nothing evaluates to the same thing. Not (rs Is Nothing) simply forces rs Is Nothing to be evaluated to a value before Not processes (inverts) it, ...but that's exactly what happens either way, because of operator precedence rules.If CBool(conn.State And adStateOpen) ThenThe explicit conversion to
Boolean is redundant here. That And is actually a bitwise operator here; it's not clear how the bitwise operation correctly converts to a Boolean - I'd suggest having a little helper function for this:Private Function HasFlag(ByVal value As Long, ByVal flag As Long) As Boolean
HasFlag = (value And flag) = flag
End FunctionThat way you can do
If HasFlag(conn.State, adStateOpen) Then and have crystal-clear intentions.I said earlier:
And if an error occurred, your code isn't even running anymore.
That's quite a problem. You need to handle runtime errors. Database connections drop, a syntax error in
strSqlQuery is likely (you're taking it as a parameter - who knows if it's legal SQL?), the rngToPrint could be in another workbook - so many things can go wrong!First thing to fix is this:
Sheets(rngToPrint.Worksheet.Name).Cells(...)The
Sheets collection contains charts and other non-worksheet objects. Use the Worksheets collection when you want worksheets. But the biggest issue is that it's implicitly referring to the ActiveWorkbook. Yet nothing says rngToPrint comes from that workbook: if the active workbook has worksheets "Sheet1", "Sheet2" and "Sheet3" and rngToPrint is in another workbook and its Worksheet.Name is "DATA", your code blows up right here.Why retrieve the worksheet from the
Sheets/Worksheets collection, by its name, when you already have a reference to it?rngToPrint.Worksheet.Cells(...)Now, handle runtime errors:
Public Sub DoSomething()
On Error GoTo CleanFail
'code
CleanExit:
'cleanup code
Exit Sub
CleanFail:
'error handling/recovering code
Resume CleanExit
End IfWith this pattern you close the recordset and connection under the
CleanExit label, and because it runs whether or not an error has occurred then you make the clean-up conditional to the state of your object references (e.g. if rs Is Nothing don't try to close it) and/or your connection.Your parameters are all passed
ByRef implicitly, and they could all passed ByVal instead. The name ConnectServer doesn't convey what the procedure is doing at all (it's doing much more than just connecting to a server), and it's implicitly Public as well; making it explicitly Public would be good, or make it Private if it's not meant to be used beyond the scope of the module it's written in.Hungarian Notation prefixes are superfluous, and misleading:
iCols- you're looking at aLong, not an "Integer".
sConnString- the name says "string", what's thesfor?
rngToPrint- a better name would be simplydestination.
FileName- no prefix? Parameter isn't used, remove it.
Whenever you feel the need to comment "this chunk of code does X", like here:
'Add headers...you
Code Snippets
Dim rs As ADODB.Recordset
If Not (rs Is Nothing) Then
If (rs.State And adStateOpen) = adStateOpen Then rs.Close
Set rs = Nothing
End IfIf CBool(conn.State And adStateOpen) Then conn.Close
Set conn = Nothing
Set rs = NothingIf Not (rs Is Nothing) ThenIf CBool(conn.State And adStateOpen) ThenPrivate Function HasFlag(ByVal value As Long, ByVal flag As Long) As Boolean
HasFlag = (value And flag) = flag
End FunctionContext
StackExchange Code Review Q#143895, answer score: 8
Revisions (0)
No revisions yet.