1

I'm not sure if what I'm wanting to know is possible or not. But I use the following code to open and connection, and then I query that connection.

Public Sub OpenConnection(Datasource As String, DatabaseName As String)

    Set zConnection = New ADODB.Connection

    zConnection.ConnectionString = "Driver={SQL Server};" & _
                                   "Server=" & Datasource & ";" & _
                                   "Database=" & DatabaseName & ";" & _
                                   "Trusted_connection=yes;"
                               
    zConnection.Open
    
End Sub

zConnection is a global variable

I'm using that connection here to get a record set of everything that returns from the sql code.

Public Function GetQueryRecordset() As ADODB.Recordset
    
    On Error GoTo FUNC_ERR
    
    Dim t As Integer: t = 1
    
GET_RST:
    Dim rst As New ADODB.Recordset
    
    Set rst.ActiveConnection = zConnection
    rst.Source = Me.Code
    rst.Open
    
    Set GetQueryRecordset = rst
    
FUNC_EXIT:
    Exit Function
    
FUNC_ERR:
    If Error = -2147217871 And t < 5 Then
        t = t + 1
        GoTo GET_RST
    Else
        MsgBox "Error Numuber: " & Err.Number & vbLf & Err.Description
        End
    End If
    
End Function

This works great, but I'm considering putting this inside a custom function I can use within an Excel cell. Problem is, it is slow to open the connect every single time. But I'm wondering if there is a way to open that connection, and then keep it open and then grab it every time I need to use it.

Basically, I have no idea how to persistently save and access that connection. so I can use it over and over again without reconnecting.

---------EDIT---------

There were a few questions that came up that I wanted to address. I originally left them out so I wouldn't complicate the question.

So I have created a custom class called sqlClass. This class has useful functions that allow me to put in SQL code into the object, display the code in a readable fashion within the debugger, but also open and close connections, put a table in that is connected to a sql query, or return a record set with that data. So saying it is a global variable isn't accurate, but it is a class variable I have so any action taken in that instance of the object can use whatever connection the object established.

Now that I'm wanting to use that in a cell function, I was wanting to reduce the number of times that connection was opened and closed. I'm actually thinking I'd put code in workbook open and workbook close that would take care of opening and closing the connection.

It sounds like my assumption that Set zConnection = New ADOBD.Connection was the actual connection was wrong. I thought every New ADOBD.Connection was the connection, but from what I'm hearing from the comments is that ADOBD.Connection is more of a "bridge" to where the connections are at and when I make a New this only makes a new bridge not a brand new connection. Feel free to correct me if I'm wrong on this, I will be testing that out next and maybe make another edit if necessary.

---------EDIT 2---------

Here is the complete sqlClass custom class I made.

Option Explicit

'***********************************************************************************
'SqlClass helps hold SQL code and gives convientent functions to call that SQL code.
'Requires Reference: Microsoft ActiveX Data Objects x.x Library
'***********************************************************************************

Private zLines As New Collection
Private zConnection As ADODB.Connection

Public Sub Add(ByVal sqlLine As String)

'**************************************************************************************
'    DESCRIPTION:
'       This will add a line of SQL as a string to the collection
'
'    INPUT VARS:
'       sqlLine: The string of SQL code to add to the bottom of the collection
'**************************************************************************************
    
    Dim addSql As String: addSql = sqlLine
    'Makes sure that the right is always a space since this will not hold SQL code with new paragraphs.
    If Right(addSql, 1) <> " " Then
        addSql = addSql & " "
    End If
    
    zLines.Add addSql

End Sub

Public Sub Blank()

'**************************************************************************************
'    DESCRIPTION:
'       This will add a element to the collection that contains a vbnullstring. This
'       only helps when trying to view the code in a readable form (printsql)
'
'    INPUT VARS:
'       n/a
'**************************************************************************************

    zLines.Add vbNullString
    
End Sub

Public Sub Clear()

'**************************************************************************************
'    DESCRIPTION:
'       This will clear all code from the collection
'
'    INPUT VARS:
'       n/a
'**************************************************************************************
    
    Set zLines = New Collection

End Sub

Public Function Code() As String

'**************************************************************************************
'    DESCRIPTION:
'       This returns a string showing the full SQL code held within this Class instance.
'       NO PARAGRAPHS SHOWN
'
'    INPUT VARS:
'       n/a
'**************************************************************************************
    
    Dim str As String

    Dim i As Integer
    For i = 1 To zLines.Count
        str = str & zLines(i)
    Next
    
    'Remove double spaces, to reduce size of string
    Do Until InStr(str, "  ") = 0
        str = Replace(str, "  ", " ")
    Loop
    
    'Excel can only send a query to the SQL Server of 32,767 or less, this will throw an error on purpose so you know this is what cause the issue.
    If Len(str) > 32767 Then
        Dim xxx As Integer: xxx = 1000000 'errors on purpose
    End If

    Code = str

End Function

Public Sub PrintSql()

'**************************************************************************************
'    DESCRIPTION:
'       Prints SQL code in the Immediate Window, this will show each line as a new line
'       For debug purposes
'
'    INPUT VARS:
'       n/a
'**************************************************************************************
    
    Dim i As Integer
    For i = 1 To zLines.Count
        Debug.Print zLines(i)
    Next

End Sub

Public Sub CreateQueryTable(ws As Worksheet, Datasource As String, initialCatalog As String)

'**************************************************************************************
'    DESCRIPTION:
'       This submits the query to the SQL server and makes the results a table on the
'       selected worksheet
'
'    INPUT VARS:
'       ws:             The worksheet that gets the table
'       DataSource:     The address of the SQL Server
'       initialCatalog: This seems to be used with error message, I use it to say which database inside the server I'm pulling from.
'**************************************************************************************
    
    Dim wkStation As String: wkStation = VBA.Environ("computername")
    
    'Values are largely default, look up this function to learn more about the inputs. Each variable sent is a new element in the array.
    Dim qryTbl As QueryTable: Set qryTbl = ws.ListObjects.Add(SourceType:=xlSrcExternal, _
                                                              Source:=Array("OLEDB;", _
                                                                            "Provider=SQLOLEDB.1;", _
                                                                            "Integrated Security=SSPI;", _
                                                                            "Persist Security Info=True;", _
                                                                            "Data Source=" & Datasource & ";", _
                                                                            "Use Procedure for Prepare=1;", _
                                                                            "Auto Translate=True;", _
                                                                            "Packet Size=4096;", _
                                                                            "Workstation ID=" & wkStation & ";", _
                                                                            "Use Encryption for Data=False;", _
                                                                            "Tag with column collation when possible=False;", _
                                                                            "Initial Catalog=" & initialCatalog), _
                                                              Destination:=ws.Range("A1")).QueryTable
                                                              
    'These are also largely default
    'Refresh BackgroundQerry = false means that the table will not update everytime the workbook is opened or
    'something changes to trigger a refresh.
    'This is ideal if you want data from exactly when it was run, not just always up to date.
    With qryTbl
        .CommandType = xlCmdSql
        .CommandText = Me.Code
        .RowNumbers = False
        .FillAdjacentFormulas = False
        .PreserveFormatting = True
        .RefreshOnFileOpen = False
        .BackgroundQuery = True
        .RefreshStyle = xlInsertDeleteCells
        .SavePassword = False
        .SaveData = True
        .AdjustColumnWidth = True
        .RefreshPeriod = 0
        .PreserveColumnInfo = True
        .Refresh BackgroundQuery:=False
    End With

End Sub

Public Sub OpenConnection(Datasource As String, DatabaseName As String)
    
'**************************************************************************************
'   DESCRIPTION:
'       Opens a connection to the SQL server and database to have Code run off of it
'
'   INPUT VARS:
'       DataSource:   The address of the SQL Server
'       DatabaseName: The database name within the server
'**************************************************************************************

    If zConnection Is Nothing Then Set zConnection = New ADODB.Connection

    zConnection.ConnectionString = "Driver={SQL Server};" & _
                                   "Server=" & Datasource & ";" & _
                                   "Database=" & DatabaseName & ";" & _
                                   "Trusted_connection=yes;"
                                   'Driver defines what type of source it is connecting to
                                   'Server is the address of the SQL
                                   'Database is which database within that Server
                                   'Trusted_connection means use the user that is logged into this PC
                               
    zConnection.Open
    
End Sub

Public Sub CheckConnection()
    
'**************************************************************************************
'   DESCRIPTION:
'       Checks if the connection object exists and creates it if not. Also checks if the
'       database is conenected, if not connects it.
'
'   INPUT VARS:
'       n/a
'**************************************************************************************

    If zConnection Is Nothing Then Set zConnection = New ADODB.Connection
    
    If zConnection.State <> adStateOpen Then
        OpenConnection Datasource:="xxxxxxx", _
                       DatabaseName:="xxxxxxxx"
    End If

End Sub

Public Sub CloseConnection()
    
'**************************************************************************************
'   DESCRIPTION:
'       Closes the connection made by OpenConnection
'
'   INPUT VARS:
'       n/a
'**************************************************************************************

    zConnection.Close
    
End Sub

Public Function GetQueryRecordset() As ADODB.Recordset
    
'**************************************************************************************
'   DESCRIPTION:
'       This will create an ADODB.recordset from the SQL code and server and return it
'       as a recordset object.
'
'   INPUT VARS:
'       n/a
'**************************************************************************************
    
    On Error GoTo FUNC_ERR
    
    Dim t As Integer: t = 1
    
GET_RST:
    Dim rst As New ADODB.Recordset
    CheckConnection
    Set rst.ActiveConnection = zConnection
    rst.Source = Me.Code
    rst.Open
    
    Set GetQueryRecordset = rst
    
FUNC_EXIT:
    Exit Function

FUNC_ERR:
    If Error = -2147217871 And t < 5 Then
        t = t + 1
        GoTo GET_RST
    Else
        MsgBox "Error Numuber: " & Err.Number & vbLf & Err.Description
        End
    End If
    
End Function

This is the customer cell function I have made:

Public Function GET_JDE_PN(custPN As String) As String

    Application.EnableEvents = False
    Dim sql As New sqlClass
    
    With sql
        .Add "SELECT DISTINCT"
        .Add "    Field0"
        .Add "FROM"
        .Add "    [Table_Name]"
        .Add "WHERE"
        .Add "    Field1= '" & custPN & "'"
        .Add "    OR"
        .Add "    Field2= '" & custPN & "'"
    End With
    
    Dim rst As ADODB.Recordset: Set rst = sql.GetQueryRecordset
    
    Dim i As Integer
    Do Until rst.EOF
        i = i + 1
        If i = 2 Then
            GET_JDE_PN = "**Multiple Returns**"
            Exit Function
        End If
        rst.MoveNext
    Loop
    
    rst.MoveFirst
    GET_JDE_PN = rst(0)
    Application.EnableEvents = True

End Function

And here is a screenshot of it being used in the workbook:enter image description here

This does work, however it takes 3-5 seconds to connect to the database each time. And I'd really like to get it to the point that it only connects once and then reuses the existing connection. I'm not sure if I need more code to tell it to leave it open or if the problem is server side. I would like to change the driver from {SQL Server}, but so far I'm not finding a suitable alternative that works with my google searches.

7
  • OpenConnection opens the connection and it will stay open (assuming you've declared it as a Global variable) so all you need to do in your UDF is check whether the connection is already open (use the State property) and call OpenConnection if necessary. Commented Feb 11, 2021 at 1:46
  • Could you give me an example? I was googling and only seeing to do what I've already got. Commented Feb 11, 2021 at 1:47
  • zConnection is a global variable - why? SQL Server has connection pooling, the cost of reconnecting is next to none; by making database connections as short-lived as possible, you avoid the pitfalls of assuming that connection is still there when you need it, because anyone anywhere can close that connection at any time. I mean sure, it's going to work. But I can't shake my impression that it feels sloppy to leave a connection dangling when you don't know when the next command is going to run - let alone whether a next command is going to even happen. Who's closing that connection? When? Commented Feb 11, 2021 at 2:36
  • Side note, that End statement should be Exit Function. The End statement pretty much stops the VBA runtime environment dead on its tracks, ...and flushes all global state... I'd be curious to see what exec sp_who2 turns up on the server then. Commented Feb 11, 2021 at 2:43
  • 2
    Also, consider updating the drivers as well. You're using Driver={SQL Server};" which basically means you are using SQL Server 2000 ODBC driver which has been out of support since 2013. The newer drivers has improvement around handling transient network issues and better support for pooling. Commented Feb 11, 2021 at 3:12

1 Answer 1

1

Very basic example below. You will need something more complex if you need to manage >1 connection.

Dim cnn As ADODB.Connection

Sub CheckConnection(Datasource As String, DatabaseName As String)
    If cnn Is Nothing Then Set cnn = New ADODB.Connection
    If cnn.State <> adStateOpen Then
        cnn.ConnectionString = "Driver={SQL Server};" & _
                               "Server=" & Datasource & ";" & _
                               "Database=" & DatabaseName & ";" & _
                               "Trusted_connection=yes;"
        cnn.Open
    End If
End Sub

Function LookItUp(v)
    Dim rst As New ADODB.Recordset
    CheckConnection "dsNameHere", "DBName here" 'open connection if not already open
    rst.Open "select uname from users where id = " & v, cnn 'use query parameters though...
    If Not rst.EOF Then
        LookItUp = rst.Fields("uname").Value
    Else
        LookItUp = "No such name"
    End If
End Function
Sign up to request clarification or add additional context in comments.

11 Comments

Awesome! Thank you. I'm going to try this out. I only will have the one connection so I'm thinking that'll be all I need.
Hey @Tim Williams, I tried the code you supplied and all it does is say that the connection isn't open and reopens the connection. Do you think this has to do with the server side?
How does it "say" that? Exactly how are you testing it?
I'll update my question with more complete code, maybe that will help.
Okay, its there now Tim, but every single time it runs, the CheckConnection function I have always comes back with a state that doesn't equal adStateOpen
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.