How to Catch SQL Error through Excel VBA - sql

I have a program that is going through the subfolders of a folder and running a stored SQL command on each. I am in the process of writing another module that will automatically check the files before being inputted, but I don't like having my company's test data hinging on a program without any way to know if something errored out. Here is the code I currently have:
Sub openConnection()
Set varConnection = New ADODB.Connection
Set varCommand = New ADODB.Command
varConnection.ConnectionString = "PROVIDER=SQLOLEDB;DATA SOURCE=192.168.1.186,1433;INITIAL CATALOG=Test1; INTEGRATED SECURITY=sspi;"
varConnection.Open
varCommand.CommandText = "importFile"
varCommand.CommandType = adCmdStoredProc
varCommand.ActiveConnection = varConnection
Set varParameter = varCommand.CreateParameter("filePath", adChar, adParamInput, Len(varFolderPath), varFolderPath)
varCommand.Parameters.Append varParameter
varCommand.Execute
varConnection.Close
End Sub
On occasion, the command will error out, causing the data to not be uploaded. How can I catch these SQL errors through Excel VBA in order to handle them appropriately without it being treated as though it had gone through like a normal file?
Note: I don't mind rewriting code- this is all still in alpha.

I guess this might be what you are looking for...
MS Access VBA trapping SQL Server Connection Error
Sub OpenConnection()
On Error GoTo ErrorHandler
// Do your stuff here...
ErrorHandler:
Debug.Print Err.Number & " " & Err.Description
' Or else you could log the error in a file, or even display a message box, whatever...'
End Sub
From the answer provided by fionnuala to the above-linked question.

Related

ADODB Connection Timeout in VBA for Excel 2016 - how to check if a connection is still active?

I have developed a small Excel addin using VBA which connects directly to a database. I set up the connection via a DSN. The addin works wonderfully when opening it and going right at it. However, after a while the connection to the database seems to timeout. More precisely, a perfectly valid query returns an error when trying to open the recordset.
My code is something like this:
'Check Connection
If Not MakeConnectionToDB Then
'Connection failed
[do something]
Exit Function
End If
'Active connection - run the query!
If Rs Is Nothing Then Set Rs = New ADODB.Recordset 'make sure its an active object
If Not Rs.State = adStateClosed Then Rs.Close 'make sure its not full (throws error if a query was called before)
Rs.Open strSQLQuery, CON 'Run query
the rs.open statement fails if the application was open but not used for a while. This is despite the MakeConnectionToDB UDF, which looks something like this:
If Not ConIsActive Then 'If there is no active connection, make it so
If CON Is Nothing Then 'Connection may be inactive because the object dropped, or because it timed out, or any other reason - Only recreate the object if the former is the case
Set CON = New ADODB.Connection
End If
On Error Resume Next
CON.Open strCon 'Try to connect - on error resume statement in order to ignore a connection error, that will be caught below
On Error GoTo 0
Err.Clear
MakeConnectionToDB = ConIsActive 'This is where a connection error will be caught if it occurred
Else
MakeConnectionToDB = True 'connection is active already
End If
and ConIsActive looks like:
Private Function ConIsActive() As Boolean
'return TRUE if there is an active connection, false otherwise
Dim blnTemp As Boolean
blnTemp = False
If (Not (CON Is Nothing)) And (Not (CON = "")) Then If CON.State = adStateOpen Then blnTemp = True
ConIsActive = blnTemp
End Function
Basically, I check if the connection is open. My problem: All these checks return TRUE, but the connection isn't open at all. If I connect, then leave the application for a while, then get back to it, all the above will return that the connection is active, but when trying to open the recordset with a new query it will fail, presumably because the server closed the connection or something. I need to find a way to check if the connection is actually able to open a recordset.
Can I ping the server or something? How can I check if the database actually returns a result to my queries? Is there a way that has a higher performance than just sending a test query to the server combined with error handling on the recordset? I suppose that would work, but I need a high performance solution and I don't think doubling the number of queries for a simple connection check is a superior solution...
Any help is appreciated!
Your CON object seems to be globally-scoped, opened once, and then used everywhere in your code, and possibly closed at some point... or not.
Like every single object in any code base written in any language that supports objects, a database connection should be as short-lived as possible.
You open it, you do what you need to do with it, and then you close it. If you don't know what the next command is going to be executed against it and when, then the connection has no business remaining open.
Delete your global-scope CON. Kill it, with fire. A connection should be local to the function or procedure that uses it - it begins in that scope, and ends in that scope.
Or you can encapsulate it in your own object, if that makes things easier for you.
'#Folder("Data.SqlConnection")
Option Explicit
Private Const CONNECTION_STRING As String = "{CONNECTION STRING}"
Private mConnection As ADODB.Connection
Private Sub Class_Initialize()
Set mConnection = New ADODB.Connection
mConnection.Open
End Sub
Private Sub Class_Terminate()
mConnection.Close
Set mConnection = Nothing
End Sub
Public Sub ExecuteNonQuery(ByVal sql As String, ParamArray params())
With New ADODB.Command
Set .ActiveConnection = mConnection
Dim p As ADODB.Parameter
For Each p In params
.Paramaters.Append p
Next
.Execute
End With
End Sub
'...
An instance of that SqlConnection class should also be as short-lived as possible, but now most of the plumbing is abstracted away so your calling code can look like this:
Const sql As String = "exec dbo.LogUserIn #userName=?, #password=?;"
With New SqlConnection
Dim userName As ADODB.Parameter
Set userName = .CreateStringParameter(Environ$("USERNAME"))
Dim password As ADODB.Parameter
Set password = .CreateStringParameter(PromptForPassword)
.ExecuteNonQuery sql, userName, password
End With
The connection begins at New SqlConnection, cleanly ends at End With, and you can tweak that SqlClass as you need, to support transactions, and/or as illustrated above, to abstract away the parameter-creating boilerplate.
But the idea remains: you don't create a database connection and leave it dangling in global scope, not knowing whether some code somewhere might have set it to Nothing, or closed it, or started a transaction that was never committed, or God knows what.
Create
Open
Execute
Close
Always. As tightly-scoped as possible. Then you won't have any object lifetime issues.

How to error handle OLEDBB connection to Excel if SQL statement is bad?

So I have a function that I created that will allow the user to gather data from an excel file using a SQL statement. Below is that function:
Public Function query_excel_file(ByVal filepath As String, ByVal sql_statement As String) As DataTable
On Error Resume Next
Dim m_sConn1 As String = "Provider=Microsoft.ACE.OLEDB.12.0;" & _
"Data Source=" & filepath & ";" & _
"Extended Properties=""Excel 12.0;HDR=Yes;IMEX=1"";"
Dim conn1 As New System.Data.OleDb.OleDbConnection(m_sConn1)
Dim da As New System.Data.OleDb.OleDbDataAdapter(sql_statement, conn1)
Dim dt As DataTable = New DataTable
If da Is Nothing Then
Return Nothing
Exit Function
End If
da.Fill(dt)
conn1.Close()
Return dt
End Function
Now this function works perfectly fine if the sql_statement is a valid sql statement, however if it is not then I get an error like this:
I fully realize that this error comes from a bad sql statement. My question is not how to fix the sql statement but for a way to error handle this so that an error dialog doesn't pop up or crash the program.
What I have tried
I have tried adding the clause shown above:
If da Is Nothing Then
Return Nothing
Exit Function
End If
but the errors still appear. I have tried looking at the properties of my variable da which is a System.Data.OleDb.OleDbDataAdapter to see if there is any property to reference on an error but have had no luck as well.
I have also tried using a simple Try Catch End Try, and On Error Resume Next but the dialog still appears.
That is the "first chance" exception dialog, and it is generated by Visual Studio when an exception occurs, before any other error handling gets to happen.
It's completely separate from your program's error-handling, and it won't happen when running your program outside Visual Studio (since it's generated by Visual Studio).
You can turn off the dialog by unchecking the box. This does not ignore the error, it just stops Visual Studio from pausing when it occurs.
Then, implement proper exception handling, which in this day and age is Try .. Catch (stay far away from On Error Resume Next).
If you put a message box in your Catch block and run your code, you will see that the error is caught properly and not just ignored.

Validate SQL Query Integrity before Executing in VB6

I have a MS Access 2003 Application to port to Visual Basic 6.0 currently. For this purpose, I have written a copy of the usual DLookup commonly used in Access.
Here is the code so far.
Public Function cDLookup(TargetField As String, TargetTable As String, cTCondition As String) As String
'Eigene Implementation von DLookup
Dim result As String
Dim rs As New ADODB.Recordset
Dim SQL As String
On Error GoTo Fehlerbehandlung
'Zusammenbauen der Query
SQL = "SELECT " & TargetField & " FROM " & TargetTable & " WHERE " & cTCondition
Call dbConn
'Initiate Database connection object cn
rs.Open SQL, cn
If (rs.RecordCount = 1) Then
result = cleanString(rs.GetString)
Debug.Print ("[DLOOKUP] Erfolgreich Einen Datensatz gefunden und konvertiert. Output: " & result)
ElseIf (rs.RecordCount > 1) Then
result = "#ErrRC"
Debug.Print ("[DLOOKUP] Es wurden " & CStr(rs.RecordCount) & " Datensätze statt einem festgestellt. Dies ist nicht erlaubt")
Else
result = "#ErrGen"
Debug.Print ("[DLOOKUP] Es ist ein Fehler in der Abfrage aufgetreten")
End If
rs.Close
cDLookup = result
Exit Function
Fehlerbehandlung:
Debug.Print ("[DLOOKUP] Fehler im Ausführen der Prozedur cDLookup()]")
cDLookup = "#Fehler"
Exit Function
End Function
My main issue is with the generated Queries. If a user types garbage that gets inputted into this, there is a runtime error from ADODB when opening the recordset. Can I verify beforehand that a SQL query is not going to do that and catch it to not crash my entire program somehow?
No, not in the way you want. You need to just try to execute it, and handle the error gracefully. You could do something like create a function called "ValidateSQL" with its own error handler, try to execute it, and return false if the query failed. You could even pass the recordset in byref and set it to have the results if it passes.
It's been a long time so forgive syntax mistakes. Something like this:
Function ValidateSQL(ByRef rs as ADODB.Recordset) as Boolean
On Error GoTo Hell
ValidateSQL = True
'open recordset here
Set rs = ....
If False Then
Hell:
ValidateSQL = False
End If
End Function
If you know you're generating trash, why take the trash to the database and wait for the database to blow up to tell you it's trash? Fail Fast is a thing for a reason.
Hitting a database is not free. Even if it's relatively fast, it's orders of magnitude slower than plain code.
Don't get me wrong, I don't mean "validate that the specified field name does indeed exist in the table with the specified name" and "parse that where statement to see if it makes sense".
However, a few sanity-checks will cost much less than a useless trip to the database. You could:
Verify that the table and field names either don't contain any spaces, or are enclosed in square brackets;
Verify that the table and field names aren't empty;
Verify that the WHERE clause doesn't start with "WHERE", and that it's not empty.
If these simple checks pass, then have the database blow up if they're still wrong.

Error 440 in Visual Basic Application

There is an old VB application running at one of my clients.
An exception is throws in this peice of code:
cn=GetIndexDatabaseConnectionString()
sSql="SELECT * FROM Arh_Naroc WHERE StNarocila = '" & isci & "'"
rs=CreateObject("ADODB.Recordset")
Call rs.Open(sSql,cn)
The exception happens in rs.Open() function. "Error number 440 occured."
This are SBL scripts for KOFAX engine and it's many years old.
The whole SW was transferred from old XP computer to Windows 7 and looks like there are problems everywhere.
Can some one help me determine what is the problem here. At least if I could get a proper error message back in msgbox would be most helpful.
EDIT:
This is the connection string function.
Function GetIndexDatabaseConnectionString
Dim objXmlDocument As Object
Dim objXmlGlobalSettingsFileParh As Object
Dim objXmlIndexDatabaseConnectionString As Object
Dim strGlobalSettingsFilePath As String
Dim strTemp As String
Const strSettingsFilePath = "C:\Data\LocalDocsDistibutingSystem\Settings.xml"
Set objXmlDocument = CreateObject("MSXML2.DOMDocument")
objXmlDocument.Load strSettingsFilePath
Set objXmlGlobalSettingsFileParh = objXmlDocument.selectSingleNode("DocsDistributingSystem/GlobalSettingsFilePath")
strGlobalSettingsFilePath = objXmlGlobalSettingsFileParh.childNodes(0).Text
Set objXmlGlobalSettingsFileParh = Nothing
Set objXmlDocument = Nothing
Set objXmlDocument = CreateObject("MSXML2.DOMDocument")
objXmlDocument.Load strGlobalSettingsFilePath
Set objXmlIndexDatabaseConnectionString = objXmlDocument.selectSingleNode("DocsDistibutingSystem/AscentCapture/IndexDatabase/ConnectionString")
strTemp = objXmlIndexDatabaseConnectionString.childNodes(0).Text
Set objXmlIndexDatabaseConnectionString = Nothing
Set objXmlDocument = Nothing
GetIndexDatabaseConnectionString = strTemp
End Function
This is the relevant line from Settings.xml:
<ConnectionString> Provider=OraOLEDB.Oracle;Data Source=LINO2;User Id=****;Password=****;OLEDB.NET=True; </ConnectionString>
The real data is masked with *. The connection to Oracle appears to be ok. I created ODBC and linked server to sql using the provider and connection data. It works. It must be something missing installed on the computer for ADODB to work...
The connection appears to be working OK. There is no error when its initialized.
The error happens in Call rs.Open(sSql, cn). All i want is the detailed error message when the error happens...
Many thanks.
As it states on MS Knowledge Base
An error occurred while executing a method or getting or setting a
property of an object variable. The error was reported by the
application that created the object. Check the properties of the Err
object to determine the source and nature of the error. Also try using
the On Error Resume Next statement immediately before the accessing
statement, and then check for errors immediately following the
accessing statement.
So as they suggest check the Err object, in a similar fashion to:
If Err.Number <> 0 Then
Msg = "Error: " & Str(Err.Number) & ", generated by " _
& Err.Source & ControlChars.CrLf & Err.Description
MsgBox(Msg, MsgBoxStyle.Information, "Error")
End If
So this will bring back the error in a MsgBox, however you can just use Response.Write if you want it easier to copy & paste etc..
to get the error description you can do as follows :
Function GetIndexDatabaseConnectionString()
On Error GoTo Errorfound
'your
'function
'body
Exit Function
Errorfound:
With Err
MsgBox "Source: " & .Source & vbCrLf & "Desc: " & .Description, vbCritical, "Error " & CStr(.Number)
End With 'Err
End Function

Unable to diligently close the excel process running in memory

I have developed a VB.Net code for retrieving data from excel file .I load this data in one form and update it back in excel after making necessary modifications in data. This complete flow works fine but most of the times I have observed that even if I close the form; the already loaded excel process does not get closed properly.
I tried all possible ways to close it but could not be able to resolve the issue. Find below code which I am using for connecting to excel and let me know if any other approach I may need to follow to resolve this issue.
Note: I do not want to kill the excel process as it will kill other instances of the excel
Dim connectionString As String
connectionString = "Provider=Microsoft.Jet.OLEDB.4.0; Data Source=" & ExcelFilePath & ";
Extended Properties=excel 8.0; Persist Security Info=False"
excelSheetConnection = New ADODB.Connection
If excelSheetConnection.State = 1 Then excelSheetConnection.Close()
excelSheetConnection.Open(connectionString)
objRsExcelSheet = New ADODB.Recordset
If objRsExcelSheet.State = 1 Then objRsExcelSheet.Close()
Try
If TestID = "" Then
objRsExcelSheet.Open("Select * from [" & ActiveSheet & "$]", excelSheetConnection, 1, 1)
Else
objRsExcelSheet.Open("Select Test_ID,Test_Description,Expected_Result,Type,UI_Element,Action,Data,Risk from [" & ActiveSheet & "$] WHERE TEST_Id LIKE '" & TestID & ".%'", excelSheetConnection, 1, 1)
End If
getExcelData = objRsExcelSheet
Catch errObj As System.Runtime.InteropServices.COMException
MsgBox(errObj.Message, , errObj.Source)
Return Nothing
End Try
excelSheetConnection = Nothing
objRsExcelSheet = Nothing
You are using the old VB6 COM interfaces. There isn't any evidence in your code snippet of you ever calling Close() on the RecordSet. You can't call Close() on the Connection since you set it to Nothing.
As written, Excel won't exit until the garbage collector runs and the finalizer thread releases the reference counts on the COM interfaces. Which can take a long time if your program doesn't exit or goes dormant after processing the data. You really should consider uplifting this code to use the classes in System.Data.Oledb so you can properly Dispose() everything when you're done with the objects.
The Q&D solution is to force the finalizer thread to run:
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
Run this after you're done using the RecordSet. It isn't otherwise recommended.