Bringing data from sql server - existing data not brought - excel

I have a function that brings data from sql server. The function is tested and is used in many macros. Now I am trying to use it and for some reason it doesn't work, although I am testing the query and it does have data
I opened a macro where the function works and try to test it from there, but still doesn't work.
I am calling to function GetDataFromDatabase (see below) from the following code:
Sub testing()
Dim query As String
Dim ImportedData As Range
query = GetQuery
Debug.Print query
Call GetDataFromDatabase(query, Range("AB1"), False)
End Sub
Note, that when debug.pring prints the query, I take it, run in in the database and I get the data, so the GetQuery function works.
The function includes the following line:
On Error GoTo CloseConnection
And indeed, at some point it goes to closeConnection (line marked below in the function). How do I know what is the error?
Sub GetDataFromDatabase(query As String, cellToCpyData As Range, WithHeaders As Boolean)
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Dim LocalDBCon As ADODB.Connection
Dim SqlTableDatasSet As ADODB.Recordset
Dim SqlDataSetFields As ADODB.Field
Dim Ctr As Long
Dim RDBConString As String
RDBConString = "connection string (the right one)"' This here is ok, I deleted the actual sting
Set LocalDBCon = New ADODB.Connection
Set SqlTableDatasSet = New ADODB.Recordset
LocalDBCon.ConnectionString = RDBConString
On Error GoTo CloseConnection
LocalDBCon.Open
With SqlTableDatasSet
.ActiveConnection = LocalDBCon
.ActiveConnection.CommandTimeout = 0
.Source = query
.LockType = adLockReadOnly
.CursorType = adOpenForwardOnly
.Open
End With
'Adding the sql table headers
If WithHeaders Then
Ctr = 0
For Each SqlDataSetFields In SqlTableDatasSet.Fields
cellToCpyData.Offset(0, Ctr) = SqlDataSetFields.Name
Ctr = Ctr + 1
Next SqlDataSetFields
Set cellToCpyData = cellToCpyData.Offset(1, 0)
End If
---->>cellToCpyData.CopyFromRecordset SqlTableDatasSet 'When not working, jumps from here to CloseConnection<<------------------------------------
SqlTableDatasSet.Close
Wrapup:
On Error Resume Next
LocalDBCon.Close
Exit Sub
CloseConnection:
On Error Resume Next
LocalDBCon.Close
End Sub

You need to add this line of code between "CloseConnection:" and "On Error Resume Next"
Debug.Print Err.Number & " - " & Err.Description
This will print to the immediate window what error is causing the problem. That will give a starting point to go from.

Related

Code runs fine when stepped through but bombs when simply run

I have an Excel VBA program that I'm working on which seems to work just when I use F8 to step through the program. I can do this and go from start to finish on the program and it has no problems. However, If i press F5 and run the program, it bombs causing excel to close and restart (no error message is given)
:(
I've narrowed it down to something happening in one particular module but lacking any indication as to what it is, I can't seem to find what is wrong.
Any help is very very greatly appreciated
The module that seems to be giving me the issue is:
Sub gwLoadWheels(ByRef db As DAO.Database, ByRef pColl As clsParts, ByRef pCrib As clsCrib, ByRef pExcept As clsExceptions)
Dim i As Long
Dim rs As DAO.Recordset
Dim sSQL As String
Dim iSH As Worksheet
Dim cWheel As clsCribItem
Dim rPart As clsPart
Dim rExcept As clsException
On Error GoTo ErrHandler
For Each rPart In pColl
sSQL = "SQL String Removed, Verified using SQL Query in MS Access"
Set rs = db.OpenRecordset(sSQL, dbOpenSnapshot, dbReadOnly)
If Not rs.EOF Then
rs.MoveLast
rs.MoveFirst
For i = 1 To rs.RecordCount
If pCrib.ItemExists(rs!wheel_ID) = False Then
With pCrib
.AddItem (rs!wheel_ID)
With .Item(rs!wheel_ID)
.AveragePieces = rs!Avg_Pieces
.Burn = rs!BurnQty
.Cost = rs!AvgUnitCost
.HoleDiameter = rs!Hole
.OnHand = rs!OnHand
.OnOrder = rs!OnOrder
.OrderQty = rs!OrderQty
.OutsideDiameter = rs!OD
.Radius = rs!Radius
.Spec = rs!Specification
.Treatment = rs!Treat
.Width = rs!Width
.TotalPieces = rs!Number_Per * rPart.Qty
End With
End With
Else
pCrib.Item(rs!wheel_ID).TotalPieces = pCrib.Item(rs!wheel_ID).TotalPieces + (rs!Number_Per * rPart.Qty)
End If
Next i
Else
With pExcept
.AddException (rPart.PartID)
.Exception(rPart.PartID).Qty = .Exception(rPart.PartID).Qty + rPart.Qty
End With
End If
rs.Close
Set rs = Nothing
Next rPart
GoTo SubExit
ErrHandler:
MsgBox "Error: " & Err.Number & vbCrLf & "Description:" & vbCrLf & Err.Description, vbCritical, "Error Info"
i = i
SubExit:
End Sub

control what spreadsheet a query is exported to in msaccess

I have a query which changes based on the criteria I set. The query's result will be changed after exporting the query to a excel spreadsheet.
This is an example of how the query works
CATEGORY = A,B,C
FILTER = D,E,F
The outcome would be something like the following:
AxF
After exporting it to a spreadsheet, I will run the same query again, but with a different set of parameters, such as:
CxD
(Instead of sending the results to different queries, I rewrite the output query's sql in vba, and there is a reason for doing that), but when it comes to exporting the query that is returned, for some reason, it will do one of the following:
override the data that is already in excel
combine with the data that is already in excel
erase all data in spreadsheet
Is there any way to control what spreadsheet a query is out put to?
i would like it to work like the following:
sheet# = outquery(CATEGORYxFILTER)
sheet1 = outquery(AxF)
sheet2 = outquery(CxD)
sheet3 = outquery(BxE)
Because the conditions for the query change every time it is called, posting my query code would be pointless.
but I shall give a better example:
let x represent first parameter
let y represent second parameter
let A represent first category
Let B represent second category
select * from * Where A=x and B=y
say i have a table of the following (this is just an example)
let the table be called EXTABLE
NAME-------BDAY-------AGE-------GENDER-------COUNTRY
AMANDA-----07/04------21--------FEMALE-------USA
MAX--------09/17------30--------MALE---------USA
SARA-------05/03------18--------FEMALE-------ENGLAND
MAX--------09/17------21--------MALE---------ENGLAND
ALEXIS-----10/25------37--------FEMALE-------FRANCE
PIERRE-----07/04------30--------MALE---------FRANCE
MY QUERY MAY SOMETIMES BE SOMETHING LIKE:
SELECT * FROM EXTABLE WHERE AGE = 21 AND GENDER = "MALE"
BUT THE NEXT TIME I CALL THE QUERY IT COULD BE:
SELECT * FROM EXTABLE WHERE COUNTRY = "ENGLAND" AND BDAY = "05/17"
the reason for changing the sql through vba is because not only are the parameters of the conditions changing, but so are the categories to which the parameters are applied
I'm still sure you can do it with parameter queries, although I'll have to have a think about it.
To get something you can work with I've come up with this (sorry, there's multiple queries at the moment):
First I created the two queries as stored queries:
AgeGender
PARAMETERS Person_Age Long, Person_Gender Text ( 255 );
SELECT *
FROM EXTABLE
WHERE Age = Person_Age AND Gender = Person_Gender;
CountryBday
PARAMETERS Person_Country Text(255), Person_Bday DateTime;
SELECT *
FROM EXTABLE
WHERE Country = Person_Country AND Bday = Person_Bday
This is the main procedure to export the two queries:
Public Sub ProcessQuery()
Dim oXL As Object
Dim oWrkBk As Object
Dim oWrkSht As Object
Set oXL = CreateXL
Set oWrkBk = oXL.Workbooks.Add(-4167) 'xlWBATWorksheet - creates a workbook with 1 sheet.
Set oWrkSht = oWrkBk.Worksheets(1)
Export_To_XL "AgeGender", oWrkSht.Range("A1"), "Person_Age", 47, "Person_Gender", "Female"
Set oWrkSht = oWrkBk.Worksheets.Add 'This will now have a reference of your new sheet.
Export_To_XL "CountryBday", oWrkSht.Range("A1"), "Person_Country", "England", "Person_Bday", #5/3/1971#
End Sub
Code to process your queries and export them to Excel. The ParamArray must takes sets of two parameters - the parameter name and its value.
Public Sub Export_To_XL(sQueryName As String, PasteRange As Object, ParamArray Params())
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim rst As DAO.Recordset
Dim fld As DAO.Field
Dim pElement As Long
On Error GoTo ERR_HANDLE
Set db = CurrentDb
Set qdf = db.QueryDefs(sQueryName)
With qdf
For pElement = LBound(Params) To UBound(Params) Step 2
.Parameters(Params(pElement)) = Params(pElement + 1)
Next
End With
Set rst = qdf.OpenRecordset
With rst
If Not (.BOF And .EOF) Then
For Each fld In rst.Fields
PasteRange.offset(, fld.OrdinalPosition) = fld.Name
Next fld
PasteRange.Resize(, rst.Fields.Count).Font.Bold = True
PasteRange.offset(1).CopyFromRecordset rst
PasteRange.Parent.Columns.Autofit
End If
End With
EXIT_PROC:
Set rst = Nothing
On Error GoTo 0
Exit Sub
ERR_HANDLE:
Select Case Err.Number
Case Else
MsgBox "Error " & Err.Number & vbCr & _
" (" & Err.Description & ") in procedure Export_To_XL."
Resume EXIT_PROC
End Select
End Sub
You'll need code to create an instance of Excel:
Public Function CreateXL(Optional bVisible As Boolean = True) As Object
Dim oTmpXL As Object
'''''''''''''''''''''''''''''''''''''''''''''''''''''
'Defer error trapping in case Excel is not running. '
'''''''''''''''''''''''''''''''''''''''''''''''''''''
On Error Resume Next
Set oTmpXL = GetObject(, "Excel.Application")
'''''''''''''''''''''''''''''''''''''''''''''''''''''''
'If an error occurs then create an instance of Excel. '
'Reinstate error handling. '
'''''''''''''''''''''''''''''''''''''''''''''''''''''''
If Err.Number <> 0 Then
Err.Clear
On Error GoTo ERROR_HANDLER
Set oTmpXL = CreateObject("Excel.Application")
End If
oTmpXL.Visible = bVisible
Set CreateXL = oTmpXL
On Error GoTo 0
Exit Function
ERROR_HANDLER:
Select Case Err.Number
Case Else
MsgBox "Error " & Err.Number & vbCr & _
" (" & Err.Description & ") in procedure CreateXL."
Err.Clear
End Select
End Function
Right, just need to figure out how to combine 100 queries into 1 now....
Edit:
This SQL isn't tested, but I'm thinking something along these lines -
PARAMETERS Person_Name Text(255), Person_Bday DateTime, Person_Age Long,
Person_Gender Text(255), Person_Country Text(255);
SELECT *
FROM EXTABLE
WHERE IIF(ISNULL(Person_Name),TRUE,sName = Person_Name) AND
IIF(ISNULL(Person_Bday),TRUE,Bday = Person_Bday) AND
IIF(ISNULL(Person_Age),TRUE,Age = Person_Age) AND
IIF(ISNULL(Person_Gender),TRUE,Gender = Person_Gender) AND
IIF(ISNULL(Person_Country),TRUE,Country = Person_Country)

Excel VBA Object required error when populating ComboBox

I am getting the following error --
Run-time error '424' : Object required
Here is the code where I am getting the error message. The line where the error appears has been highlighted with ****
Sub LoadDropdown_Click()
Dim cnt As ADODB.Connection
Dim rst As ADODB.Recordset
Dim stDB As String, stSQL As String
Dim xlCalc As XlCalculation
Dim vaData As Variant
Dim k As Long
Set cnt = New ADODB.Connection
cnt.connectionString = Module1.GetConnectionString
stSQL = "EXEC dbo.GetData"
With cnt
.CursorLocation = adUseClient 'Necesary for creating disconnected recordset.
.Open stConn 'Open connection.
'Instantiate the Recordsetobject and execute the SQL-state.
Set rst = .Execute(stSQL)
End With
With rst
Set .ActiveConnection = Nothing 'Disconnect the recordset.
k = .Fields.Count
'Populate the array with the whole recordset.
vaData = .GetRows
End With
'Close the connection.
cnt.Close
'Manipulate the Listbox's properties and show the form.
With ComboBox21
.Clear ' **** the error comes at this line since ComboBox21 is empty ******
.BoundColumn = k
.List = Application.Transpose(vaData)
.ListIndex = -1
End With
Dim i As Integer
'Release objects from memory.
Set rst = Nothing
Set cnt = Nothing
End Sub
These are the things I have veified --
The ComboBox actually exists in Sheet1, called Priorities. See this screenshot showing that Sheet1 contains a Combobox called ComboBox21
The below function, LoadDropdown_Click is present in Sheet1. See this screenshot for details
This code works when running from certain machines. It used to work on my machine earlier, but now I am getting this error suddenly without having made any changes to the code or the environment.
I tried changing ComboBox21 to Sheet1.ComboBox21, but I got a compile error - Method or Data member not found.
It will be great if someone can help!
Please change your code:
With ComboBox21
.Clear ' **** the error comes at this line since ComboBox21 is empty ******
.BoundColumn = k
.List = Application.Transpose(vaData)
.ListIndex = -1
End With
'With the below:
Sheet1.ComboBox21.Clear
With Sheet1.ComboBox21
.BoundColumn = k
.List = Application.Transpose(vaData)
.ListIndex = -1
End With
Basically, you just add "On Error Resume Next" to avoid those annoying error messages popping up and then add "Err.Clear" to clear the error flag just in case.
Private Sub ComboBox1_Change()
On Error Resume Next
ComboBox5.List = Sheets("Data").Range("B1:B6").Value
Err.Clear
End Sub
NOTE: This is probably not the right way of doing things but at least it works on my end

Excel VBA: Late binding reference

I'm trying to write some code for an add-in in excel, which grabs some data from an SQL Server. The code itself is working flawlessly, but somehow something got corrupted.
It seems that the code will work fine a few times and then all of a sudden trigger an excel-crash. After a long time I've determined that it has something to do with the references, seeing as if upon crash I change the reference 'Microsoft ActiveX Data Objects 2.8 Library' to something else, and then back again, the add-in will work again.
Seeing as rebuilding the add-in doesn't work, I'm beginning to explore the option of late-binding. I just can't seem to understand how to do it.
Private Sub RetrieveToWorksheet(SQL As String, WriteTo As Range, Optional WriteColumnNames As Boolean = True)
If GetStatus = "True" Then
MsgBox ("Database is currently being updated. Please try again later.")
Exit Sub
End If
Application.ScreenUpdating = False
Dim Connection As ADODB.Connection
Dim RecordSet As ADODB.RecordSet
Dim Field As ADODB.Field
Dim RowOffset As Long
Dim ColumnOffset As Long
On Error GoTo Finalize
Err.Clear
Set Connection = New ADODB.Connection
Connection.ConnectionTimeout = 300
Connection.CommandTimeout = 300
Connection.ConnectionString = "Provider=sqloledb;Data Source=vdd1xl0001;Initial Catalog=SRDK;User Id=SRDK_user;Password=password;Connect Timeout=300"
Connection.Mode = adModeShareDenyNone
Connection.Open
Set RecordSet = New ADODB.RecordSet
RecordSet.CursorLocation = adUseServer
RecordSet.Open SQL, Connection, ADODB.CursorTypeEnum.adOpenForwardOnly
RowOffset = 0
ColumnOffset = 0
If WriteColumnNames = True Then
For Each Field In RecordSet.Fields
WriteTo.Cells(1, 1).Offset(RowOffset, ColumnOffset).Value = Field.Name
ColumnOffset = ColumnOffset + 1
Next
ColumnOffset = 0
RowOffset = 1
End If
WriteTo.Cells(1, 1).Offset(RowOffset, ColumnOffset).CopyFromRecordset RecordSet
Finalize:
If Not RecordSet Is Nothing Then
If Not RecordSet.State = ADODB.ObjectStateEnum.adStateClosed Then RecordSet.Close
Set RecordSet = Nothing
End If
If Not Connection Is Nothing Then
If Not Connection.State = ADODB.ObjectStateEnum.adStateClosed Then Connection.Close
Set Connection = Nothing
End If
If Err.Number <> 0 Then Err.Raise Err.Number, Err.Source, Err.Description
End Sub
Long story short: I just want the add-in to automatically add the reference 'Microsoft ActiveX Data Objects 2.8 Library'.
All help is greatly appreciated!
In answer to your question about late binding, this involves replacing the line of code
Dim Connection As ADODB.Connection
with
Dim Connection As object
and replacing
Set Connection = New ADODB.Connection
with
Set Connection = GetObject(, "ADODB.Connection")
And similarly for the other objects from that library.
Now, I am not sure if this will fix the actual issue that you are having. It sounds like there is a bug in the ActiveX library and you are hitting it, although nothing you are doing seems particularly esoteric.

Force vba to wait for sql query to execute before continuing

This seems to be a fairly common problem but none of the the solutions I've found seem to work.
I'm grabbing some data from SQL Server and copying it into a worksheet. Then I want to copy a range from the new data and do other stuff with it. All of this happens in a single vba function.
My problem is when the function is run from Excel it moves onto the second part of the function without waiting for the query to return the required data.
Of course the function works fine when I run it from the vba IDE.
Dim a As New ADODB.Connection
Dim r As New ADODB.Recordset
a.Open (connStr)
Set r = a.Execute(sqlstr)
sht.Range("A2").CopyFromRecordset r
'please wait here until the proc has executed?
checkData = sht.Range("A2").Value
When I run the function from Excel checkData is always empty, when I run it with F5 it always has the required data.
Try using:
Application.CalculateUntilAsyncQueriesDone
after you execute the SQL, but before you copy the RecordSet
Set r = a.Execute(sqlstr)
Application.CalculateUntilAsyncQueriesDone
sht.Range("A2").CopyFromRecordset r
Does this work?
Dim a As New ADODB.Connection
Dim r As New ADODB.Recordset
a.Open (connStr)
Set r = a.Execute(sqlstr)
Do
'Wait
Loop Until Not r Is Nothing
sht.Range("A2").CopyFromRecordset r
checkData = sht.Range("A2").Value
Alternatively, if this fails, you could try testing some property of r, like EOF or BOF, and if an error occurs, or you get an unexpected value you know the data hasn't yet loaded. For example:
Dim a As New ADODB.Connection
Dim r As New ADODB.Recordset
a.Open (connStr)
Set r = a.Execute(sqlstr)
On Error Resume Next
Do
Err.Clear
r.EOF 'Put your test here, you might test rowcount or similar.
'I've simply asked for the EOF property and ignored the result, I'm
'not sure if this will work in your case. Some testing may be required.
While Err.Num <> 0
On Error GoTo 0 'Or whatever you previously had this set to
sht.Range("A2").CopyFromRecordset r
checkData = sht.Range("A2").Value
This might help. Instead of setting up the data source in code, set it up on the target worksheet as a data connection (Excel menu Data | From Other Sources | etc.). Once a connection object named "(Default)" is created you tap it in code along these lines:
With ActiveWorkbook
.Connections("(Default)").OLEDBConnection.BackgroundQuery = False
.Connections("(Default)").OLEDBConnection.CommandText = sqlstr
.RefreshAll
' do more stuff
' will wait for .RefreshAll to complete because .BackgroundQuery = false
End With
I think you need a r.movelast after the execute to make sure all the rows are returned.
Something like
Set r = a.Execute(sqlstr)
If Not r.EOF Then
r.MoveLast
End If
sht.Range("A2").CopyFromRecordset r
Along the lines of andy holaday, I got this to work by unchecking "Enable background refresh" in the external data range properties. Disabling this feature forces excel to wait while the query is run.
But if there is no recordset being returned I found this code will wait until the SQL code returns before going to the next VBA statement. Handy when there is dependency by one command on another or if you need an entire data set created before moving on.
Dim Con As ADODB.Connection
Dim CmdTxt As String
Set Con = New Connection
Con.ConnectionString = ThisWorkbook.GetYourConnectString()
Con.Open
CmdTxt = "EXEC db.schema.StoredProc1 #Param1 = 'Yes'"
ExecuteSql Con, CmdTxt, True, True
CmdTxt = "EXEC db.schema.StoredProc2 #Param1 = 'No'"
ExecuteSql Con, CmdTxt, True, True
MsgBox "Both commands completed sequentially"
The code for ExecuteSql is:
Public Function ExecuteSql(Con As ADODB.Connection, sql As String, _
Optional StopOnError As Boolean = True, _
Optional WaitUntilDone As Boolean = False) As String
Dim cmd As ADODB.Command
Set cmd = New ADODB.Command
With cmd
.CommandType = 1
.CommandText = sql
.ActiveConnection = Con
If WaitUntilDone = True Then
.CommandTimeout = 0 'set timeout to unlimited
.Execute , , adExecuteNoRecords 'no records value speeds up internal code
Else
.Execute
End If
End With
ExecuteSql = ""
Exit Function

Resources