I have a stored procedure call which is duplicating rows through Excel, I think it has to do with the method I am using for the copy from record set. The data when the query is run manually outputs the correct data.
Private Sub Refresh_Click()
Dim Conn As ADODB.Connection, RecordSet As ADODB.RecordSet
Dim Command As ADODB.Command
Dim ConnectionString As String, StoredProcName As String
Dim StartDate As ADODB.Parameter, EndDate As ADODB.Parameter
Application.ScreenUpdating = False
Set Conn = New ADODB.Connection
Set RecordSet = New ADODB.RecordSet
Set Command = New ADODB.Command
' I blanked out the details here as they are not required as this is working
ConnectionString = "PROVIDER=SQLOLEDB;DATA SOURCE=xxxx;INITIAL CATALOG=xxxx; User Id=xxxx;Password=xxxx;"
On Error GoTo CloseConnection
Conn.Open ConnectionString
SellStartDate = Format(Sheets("Sheet1").Range("B2").Value2, "yyyy-mm-dd")
SellEndDate = Format(Sheets("Sheet1").Range("B3").Value2, "yyyy-mm-dd")
StoredProcName = "fsp_PLReportByDates"
With Command
.ActiveConnection = Conn
.CommandType = adCmdStoredProc
.CommandText = StoredProcName
End With
Set StartDate = Command.CreateParameter("#DateFrom", adDBDate, adParamInput, , SellStartDate)
Set EndDate = Command.CreateParameter("#DateTo", adDBDate, adParamInput, , SellEndDate)
Command.Parameters.Append StartDate
Command.Parameters.Append EndDate
Set RecordSet = Command.Execute
Sheets("Sheet1").Range("A7").CopyFromRecordset RecordSet
For intColIndex = 0 To RecordSet.Fields.Count - 1
Range("A6").Offset(0, intColIndex).Value = RecordSet.Fields(intColIndex).Name
Next
RecordSet.Close
Conn.Close
On Error GoTo 0
Application.ScreenUpdating = True
Exit Sub
CloseConnection:
Application.ScreenUpdating = True
MsgBox "SQL Stored Procedure Did Not Execute Sucessfully!", vbCritical, "SQL Error"
Conn.Close
End Sub
Assuming that your old recordset/previous pull is bigger than your new one and when you drop the new one over the old one, some of the old records are still present in your sheet...
Make a named range that grows dynamically with your data. Assuming you have 10 columns from your Proc coming back and any number of rows, make a named range called rng_PLReportByDates and set it to:
=OFFSET(Sheet1!$A$7, 0, 0, COUNTA(Sheet1!$A$7:$A$5000)+1, 10)
This will create a named range that has 10 columns and up to 4993 rows. I assume that's plenty for your recordset, otherwise bump that 5000 to whatever makes sense. The +1 is there just to insure that if the range is completely empty (no values) that this formula will return at least 1 row, otherwise you will error out.
Then... Just before you run:
Sheets("Sheet1").Range("A7").CopyFromRecordset RecordSet
Add this:
Range("rng_PLReportByDates").ClearContents
You can also change that CopyFromRecordset to use your new dynamically sized named range:
Range("rng_PLReportByDates").CopyFromRecordset Recordset
I use this method every time I dump a recordset to a worksheet. I create a dynamically sized named range using that same formula, and then I .ClearContents and .CopyFromRecordset to it.
If your number of columns changes, then you can just add a Counta() formula to that last parameter in the named range formula:
=OFFSET(Sheet1!$A$7, 0, 0, COUNTA(Sheet1!$A$7:$A$5000)+1, COUNTA(Sheet1!$A$7:$IV$7)+1)
As far as the header is concerned, you may want to adjust the named range to go after row 6. Then you can do:
Range("rng_PLReportByDates").ClearContents
Range("rng_PLReportByDates").Offset(1).CopyFromRecordset Recordset
And then do your range work just the same.
Related
we are trying to use ADO to read data from a closed workbook, remove any whitespace and convert any incorrectly keyed dates into a valid format. Once the data has been cleansed, it's uploaded into a custom app.
We are using ADO for speed purposes as we have found using VBA to open/manipulate/close takes too long, meaning we miss our upload target time (we have multiple workbooks we need to apply this to).
The problem we have is converting the dates to a valid format. Dates are entered into the workbook either as dd/mm/yy or dd.mm.yy - we have no control over this, the template was created years ago and we are unable to update it and apply data validation.
Ideas We Have Tried: We have a few ideas, but have not been successful, does anyone know if any of these suggestions could work / suggest alternate ideas?
Check for a "." and apply a Replace(): If InStr(rs.Fields("Date").Value, ".") > 0 Then rs.Fields("Date").Value = Replace(rs.Fields("Date").Value, ".", "/")
This works when the column is read into the record set as type 202: adVarWChar, unfortunatly as the majority of the dates are valid, the data in the record set is set as type 7: adDate, when looping, once we get to an invalid date format (with the dots), we get a debug error:
"you cannot record changes because a value you entered violates the settings defined for this table or list (for example, a value is less than the minimum or greater than the maximum). correct the error and try again"
Convert the whole column data type to 202 adVarWChar:
As the above code works for entries when they are formatted as text, we had an idea to see if we could pull the whole column of data in directly as text, we have experimented with Casting and Convert but cannot get it to work - I no longer have the sample code we were trying for that. I recall experimenting adding IMEX=1 to the connection string, but this didn't seem to make any difference.
Apply a Find/Replace query on a whole column:
Instead of retrieving the data and looping through it, we had an idea to apply a find and replace query directly on the column, similar to how we are able to trim a whole column. Again, we were unable to find any code/queries which worked.
Create an empty record set and set the column type to String:
We had an idea to create a blank/empty record set and manually set the date column to a string type, and then loop through the retrieved data and move them into the new record set. We didn't get very far with this as we weren't too sure how to create a blank RS, then we also thought, how would we write this data back to the worksheet - as I don't think you can write back to a closed workbook.
Here is the code I have at the moment:
Sub DataTesting()
On Error GoTo ErrorHandler
'set the workbook path of the file we want to read from
Dim workbookFileName As String
workbookFileName = "C:\Users\xxx\xxx\myWorkbook.xls"
'create a connection string
Dim connectionString As String
connectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" _
& workbookFileName _
& ";Extended Properties=""Excel 12.0 Xml;HDR=YES;"";" 'IMEX=1"";"
'open the connection
Dim conn As ADODB.connection
Set conn = New ADODB.connection
conn.connectionString = connectionString
conn.Open
Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
'Convert all data in the date column to a valid date (e.g. replace dates with decimals 1.1.21 to 01/01/2021)
'set query to select all data from the date column
Dim query As String
query = "SELECT * FROM [DATA SHEET$B2:B100]" 'col B is the Date column
With rs
.ActiveConnection = conn
'.Fields.Append "Date", adVarChar, 20, adFldMayBeNull 'NOT WORKING
.CursorType = adOpenDynamic
.LockType = adLockOptimistic
.Source = query
.Open
If Not .BOF And Not .EOF Then
While (Not .EOF)
If InStr(rs.Fields("Date").Value, ".") > 0 Then rs.Fields("Date").Value = Replace(rs.Fields("Date").Value, ".", "/")
.MoveNext
Wend
End If
.Close
End With
conn.Close
GoTo CleanUp
ErrorHandler:
MsgBox Err.Description 'THIS WILL BE WRITTEN TO TXT FILE
CleanUp:
'ensure the record set is equal to nothing and closed
If Not (rs Is Nothing) Then
If (rs.State And adStateOpen) = adStateOpen Then rs.Close
Set rs = Nothing
End If
'ensure the connection is equal to nothing and closed
If Not (conn Is Nothing) Then
If (conn.State And adStateOpen) = adStateOpen Then conn.Close
Set conn = Nothing
End If
End Sub
UPDATE:
I am now able to read the data using the following query:
"SELECT IIF([Date] IS NULL, NULL, CSTR([Date])) AS [Date] FROM [DATA SHEET$B2:B10]"
This will only work if I set IMEX=1, which is only read-only. I am able to loop through each item and print out the value / detect where the dots are, but I cannot then amend them!
As mentioned by #Doug Coats I can move the data into an array, perform the manipulation on the array. But how exactly do I then put that array back into the recordset?
I guess I would need to close the first 'read only' connection, and re-open it as a 'write' connection. Then somehow run an update query - but how do I replace the existing record set values with the values from the array?
Thanks
You could try an update query
Const SQL = " UPDATE [DATA SHEET$] " & _
" SET [Date] = REPLACE([Date],""."",""/"")" & _
" WHERE INSTR([Date],""."") > 0 "
Dim n
conn.Execute SQL, n
MsgBox n & " records updated"
Sub testdata()
Dim wb, ws, i
Set wb = Workbooks.Add
Set ws = wb.Sheets(1)
ws.Name = "DATA SHEET"
ws.Cells(1, 2) = "Date"
For i = 2 To 10
If Rnd() > 0.5 Then
ws.Cells(i, 2) = "27.07.21"
Else
ws.Cells(i, 2) = "27/07/21"
End If
Next
wb.SaveAs "c:\temp\so\dates.xls"
wb.Close
End Sub
Using MS Access to complete this project.
I am attempting to simplify my ADODB code by removing the need for the ADODB.Command object. There are two requirements, the need to use parameters and the need to retrieve records affected (for verification that the SQL executed properly).
The syntax I am attempting to use was mentioned in an article documented in the code block.
{connection object}.[{name of query}] {parameter 1, ..., parameter n [, record set object]}
cn.[TEST_ADODB_Connection] 204, Date & " " & Time(), rs
Sub TEST_ADODB_Connection()
'https://technet.microsoft.com/en-us/library/aa496035(v=sql.80).aspx
'Using ADODB without the use of .Command
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim lngRecordsAffected As Long
Set cn = CurrentProject.Connection
'TEST_ADODB_Connection Query
'INSERT INTO tbl_Log ( LogID_Orig, LogMessage )
'SELECT [NewLogID] AS _LogID, [NewLogMessage] AS _LogMessage;
Set rs = New ADODB.Recordset
cn.[TEST_ADODB_Connection] 204, Date & " " & Time(), rs
lngRecordsAffected = rs.RecordCount 'Error 3704 - no records returned
'so this is expected, but how do we
'get records affected by the update query?
Debug.Print lngRecordsAffected
End Sub
UPDATE
Including the original code attempting to be simplified.
The .Command object does provide the functionality I desire, but I am looking for an alternative method if it is feasible.
The article (https://technet.microsoft.com/en-us/library/aa496035(v=sql.80).aspx) provides an example where the .Connection object could be executed using parameters. I am trying to extend that example and obtain records affected.
Sub TEST_ADODB_Command()
Dim cm As ADODB.Command
Dim rs As ADODB.Recordset
Dim iLogID_Auto As Integer
Dim strLogMessage As String
Dim lngRecordsAffected As Long
Set cm = New ADODB.Command
iLogID_Auto = 204
strLogMessage = Date & " " & Time
With cm
Set .ActiveConnection = CurrentProject.Connection
.CommandText = "TEST_ADODB_Connection"
.CommandType = adCmdStoredProc
.NamedParameters = True ' does not work in access
.Parameters.Append .CreateParameter("[NewLogID]", adInteger, adParamInput, , iLogID_Auto)
.Parameters.Append .CreateParameter("[NewLogMessage]", adVarChar, adParamInput, 2147483647, strLogMessage)
Set rs = .Execute(lngRecordsAffected)
Debug.Print lngRecordsAffected
End With
Set rs = Nothing
Set cm = Nothing
End Sub
Thank you for the comments. I believe I have devised what I was searching for.
Two points
ADODB.Command is needed if you want to insert/update and retrieve a record count using parameters using a single .Execute. Examples of this can be found all over the internet including my original post under the update section.
ADODB.Command is NOT needed if you have an insert/update query and a select query. I could not find examples of this method. Below is an example I have come up with.
High level overview of what is going on
Execute the insert/update query. Inserts/Updates will not return a recordSet using the one line method.
Execute a select query. This will return a recordSet, however, I couldn't get the .Count method to work as I would think it should.
tlemaster's suggested link provided a work around in the answer section. The work around is to revise the select query to group the results and use the COUNT(*) to return the count. The returning value is then utilized instead of the .Count method.
Sub TEST_ADODB_Connection()
'https://technet.microsoft.com/en-us/library/aa496035(v=sql.80).aspx
'Using ADODB without the use of .Command and .Parameters
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim lngRecordsAffected As Long
Dim strDateTime As String
Dim lngID As Long
Set cn = CurrentProject.Connection
strDateTime = Date & " " & Time()
lngID = 204 'random number for example purpose
'TEST_ADODB_Connection INSERT Query
'INSERT INTO tbl_Log ( LogID_Orig, LogMessage )
'SELECT [NewLogID] AS _NewLogID, [NewLogMessage] AS _LogMessage;
'This line will execute the query with the given parameters
'NOTE: Be sure to have the parameters in the correct order
cn.[TEST_ADODB_Connection] lngID, strDateTime
'TEST_ADODB_Select
'SELECT Count(tbl_Log.LogID_Orig) AS recordCount
'FROM tbl_Log
'WHERE tbl_Log.LogID_Orig=[_LogID] AND tbl_Log.LogMessage=[_LogMessage];
'Must initilize recordset object
Set rs = New ADODB.Recordset
'This line will execute the query with given parameters and store
'the returning records into the recordset object (rs)
'NOTE: Again, be sure the parameters are in the correct order
'NOTE: the recordset object is always the last argument
cn.[TEST_ADODB_Select] lngID, strDateTime, rs
'unable to directly utilize the .Count method of recordset
'workaround and more optimal solution is to write the SQL
'to return a count using grouping and Count(*) - see SQL above
lngRecordsAffected = rs("recordCount").Value
'Close recordset object
rs.Close
Debug.Print lngRecordsAffected
End Sub
I am trying to run a SP inside a function in an excel macro (button):
Sub Button13_Click()
Call exec_sp_GetLoan_CFM_Info_by_customerID("B3", 2, 1)
Call exec_sp_GetLoan_CFM_Info_by_customerID("B24", 3, 1)
Call exec_sp_GetLoan_CFM_Info_by_customerID("B45", 4, 1)
Call exec_sp_GetLoan_CFM_Info_by_customerID("B66", 5, 1)
Call exec_sp_GetLoan_CFM_Info_by_customerID("B86", 6, 1)
End Sub
The SP returns a single row every time, and I'm trying to write a row with every result set. What happens is that I'm getting a single row in the spreadsheet, for example, as it is shown above, I would only get row 2 with result set for custID from B3. If I comment out the first line, I would only get row 3 with results for custID from B24, and so on. I can never get more than 1 row, and I don't understand why (I'm completely new to vb and excel macros). Can anybody explain what's happening? This is happening even if I don't clear any rows... thank you!
Update: If I set up separate buttons for each 'Call' of the function, it runs fine...
Function exec_sp_GetLoan_CFM_Info_by_customerID(custIDcell As String, stRow As Integer, stCol As Integer)
Dim con As ADODB.Connection
Dim cmd As ADODB.Command
Dim rs As ADODB.Recordset
Dim WSP1 As Worksheet
Set con = New ADODB.Connection
Set cmd = New ADODB.Command
Set rs = New ADODB.Recordset
Application.DisplayStatusBar = True
Application.StatusBar = "Contacting SQL Server..."
' Log in
con.Open "Provider=SQLOLEDB;Data Source=xxxx;Initial Catalog=xxxx ID=xxxx;Password=xxxx;"
cmd.ActiveConnection = con
' Set up the parameter for the Stored Procedure
cmd.Parameters.Append cmd.CreateParameter("custID", adVarChar, adParamInput, 8, Range(custIDcell).Text)
Application.StatusBar = "Running stored procedure..."
cmd.CommandText = "sp_GetLoan_CFM_Info_by_customerID"
Set rs = cmd.Execute(, , adCmdStoredProc)
Set WSP1 = ActiveWorkbook.Worksheets("CIFInfo")
WSP1.Activate
'clear row
WSP1.Rows(stRow).Clear
If rs.EOF = False Then WSP1.Cells(stRow, stCol).CopyFromRecordset rs
'cmd.Parameters("custID").Value = Range("B24").Text
'cleanup
rs.Close
Set rs = Nothing
Set cmd = Nothing
con.Close
Set con = Nothing
Application.StatusBar = "Data successfully updated."
End Function
Beginner's (Excel) mistake... after calling the function with the SP for the first time, the active worksheet gets changed to a different one from the input, so subsequent calls are done with an empty input parameter!!
That wasn't obvious from the post.. sorry!!
I just startied working with this database and I have a small problem.
So the main idea behind this is to use VBA to get needed information from database that I can use later on.
I am using ADO recordset and connect sting to connect to server. All is fine apart from one problem: when I am creating RecordSet by using SQL request it only returns one field when i know there should me more. At the moment I think that RecordSet is just grabbing first result and storing it in but looses anything else that should be there. Can you please help me.
Here is my code:
'Declare variables'
Dim objMyConn As ADODB.Connection
Dim objMyCmd As ADODB.Command
Dim objMyRecordset As ADODB.Recordset
Dim fldEach As ADODB.Field
Dim OrderNumber As Long
OrderNumber = 172783
Set objMyConn = New ADODB.Connection
Set objMyCmd = New ADODB.Command
Set objMyRecordset = New ADODB.Recordset
'Open Connection'
objMyConn.ConnectionString = "Provider=SQLOLEDB;Data Source=Local;" & _
"Initial Catalog=SQL_LIVE;"
objMyConn.Open
'Set and Excecute SQL Command'
Set objMyCmd.ActiveConnection = objMyConn
objMyCmd.CommandText = "SELECT fldImage FROM tblCustomisations WHERE fldOrderID=" & OrderNumber
objMyCmd.CommandType = adCmdText
'Open Recordset'
Set objMyRecordset.Source = objMyCmd
objMyRecordset.Open
objMyRecordset.MoveFirst
For Each fldEach In objMyRecordset.Fields
Debug.Print fldEach.Value
Next
At the moment Debug returns only one result when it should return two because there are two rows with the same OrderID.
The recordset only opens a single record at a time. You are iterating through all the fields in a single record. Not each record in the recordset.
If your query returns two records, you need to tell the Recordset to advance to the next one.
A query returns one recordset which has some number of records which have some number of fields.
You are iterating through the fields only for one record in the returned recordset.
You can do this with a few ways, but I generally do something like:
objMyRecordset.MoveFirst
Do
If Not objMyRecordset.EOF Then
debug.print "Record Opened - only returning 1 field due to SQL query"
For Each fldEach In objMyRecordset.Fields
Debug.Print fldEach.Value
Next
'this moves to the NEXT record in the recordset
objMyRecordset.MoveNext
Else
Exit Do
End If
Loop
Note that if you want to include more fields you will need to modify this line:
objMyCmd.CommandText = "SELECT fldImage FROM tblCustomisations WHERE fldOrderID=" & OrderNumber
To include whatever additional fields you want returned.
In addition to the #enderland's answer, you can also have a disconnected RecordSet, that have all the values and fields ready for consumption. It's handy when you need to pass the data around or need to close the connection fast.
Here's a function that returns a disconnected RecordSet:
Function RunSQLReturnRS(sqlstmt, params())
On Error Resume next
' Create the ADO objects
Dim rs , cmd
Set rs = server.createobject("ADODB.Recordset")
Set cmd = server.createobject("ADODB.Command")
' Init the ADO objects & the stored proc parameters
cmd.ActiveConnection = GetConnectionString()
cmd.CommandText = sqlstmt
cmd.CommandType = adCmdText
cmd.CommandTimeout = 900 ' 15 minutos
collectParams cmd, params
' Execute the query for readonly
rs.CursorLocation = adUseClient
rs.Open cmd, , adOpenForwardOnly, adLockReadOnly
If err.number > 0 then
BuildErrorMessage()
exit function
end if
' Disconnect the recordset
Set cmd.ActiveConnection = Nothing
Set cmd = Nothing
Set rs.ActiveConnection = Nothing
' Return the resultant recordset
Set RunSQLReturnRS = rs
End Function
You are mixing up terms in your question which makes it unclear
In your first paragraph you describe a problem with "Fields", in the last paragraph you turn it into "Rows". Not exactly the same.
But whatever you are trying to achieve, the code you wrote will only return one field and one row.
If you want all FIELDS, your query should be:
objMyCmd.CommandText = "SELECT * FROM tblCustomisations WHERE fldOrderID=" & OrderNumber
If you want all ROWS, your loop should be:
objMyRecordset.MoveFirst
If Not objMyRecordset.BOF Then
While Not objMyRecordset.EOF
debug.print objMyRecordset!fldImage
RS.MoveNext
Wend
End If
I have a SQL Server 2008 stored procedure that updates values in a table. I would like to have the stored procedure return an integer value indicating that the update was successful (return 0) or not (returns error number). What would be the best way to accomplish this via ADO and VBA? Here some of my code in simplified form that performs the update ... I am just not sure how to get back the return value of the stored procedure
Public Function DoUpdate(employeeID as integer, firstName as string, lastname as string) as integer
Dim cnn As ADODB.Connection
Dim cmd As ADODB.Command
Dim activeConnectionString As String
activeConnectionString = GetActiveConnectionString()
Set cnn = New ADODB.Connection
cnn.ConnectionString = activeConnectionString
cnn.CursorLocation = adUseClient
cnn.Open
Set cmd = New ADODB.Command
cmd.ActiveConnection = cnn
cmd.CommandType = adCmdStoredProc
cmd.CommandText = "uspUpdateEmployeeName"
cmd.NamedParameters = True
cmd.Parameters("#EmployeeID").Value = employeeID
cmd.Parameters("#FirstName").Value = firstName
cmd.Parameters("#LastName").Value = lastName
cmd.Execute
'Unsure of how to get back return value here
'DoUpdate = returnValue
Set cnn = Nothing
End Function
Note: The return_value must be the first parameter!
Order matters.
I was getting errors stating that my query "had too many arguments" when I had specified my return_value parameter last, instead of first.
The parameters' ordering was the cause of my error.
If you use
Dim lngRecs As Long
cmd.Execute lngRecs
lngRecs should contain records affected.
I seem to remember that you need to supply an extra parameter with the type 'adParamReturnValue' like this:
Dim lRetVal as Long
Set cmd = New ADODB.Command
cmd.Parameters.Append .CreateParameter("returnvalue", adInteger, adParamReturnValue)
cmd.Execute
'Now check the return value of the procedure
lRetVal = cmd.Parameters("returnvalue")
If lRetVal > 0 then
Several ways are possible to get values back using VBA:
Recordset
Count of records affected (only for Insert/Update/Delete otherwise -1)
Output parameter
Return value
My code demonstrates all four. Here is a stored procedure that returns a value:
Create PROCEDURE CheckExpedite
#InputX varchar(10),
#InputY int,
#HasExpedite int out
AS
BEGIN
Select #HasExpedite = 9 from <Table>
where Column2 = #InputX and Column3 = #InputY
If #HasExpedite = 9
Return 2
Else
Return 3
End
Here is the sub I use in Excel VBA. You'll need reference to Microsoft ActiveX Data Objects 2.8 Library.
Sub CheckValue()
Dim InputX As String: InputX = "6000"
Dim InputY As Integer: InputY = 2014
'open connnection
Dim ACon As New Connection
'ACon.Open ("Provider=SQLOLEDB;Data Source=<SqlServer>;" & _
' "Initial Catalog=<Table>;Integrated Security=SSPI")
'set command
Dim ACmd As New Command
Set ACmd.ActiveConnection = ACon
ACmd.CommandText = "CheckExpedite"
ACmd.CommandType = adCmdStoredProc
'Return value must be first parameter else you'll get error from too many parameters
'Procedure or function "Name" has too many arguments specified.
ACmd.Parameters.Append ACmd.CreateParameter("ReturnValue", adInteger, adParamReturnValue)
ACmd.Parameters.Append ACmd.CreateParameter("InputX", adVarChar, adParamInput, 10, InputX)
ACmd.Parameters.Append ACmd.CreateParameter("InputY", adInteger, adParamInput, 6, InputY)
ACmd.Parameters.Append ACmd.CreateParameter("HasExpedite", adInteger, adParamOutput)
Dim RS As Recordset
Dim RecordsAffected As Long
'execute query that returns value
Call ACmd.Execute(RecordsAffected:=RecordsAffected, Options:=adExecuteNoRecords)
'execute query that returns recordset
'Set RS = ACmd.Execute(RecordsAffected:=RecordsAffected)
'get records affected, return value and output parameter
Debug.Print "Records affected: " & RecordsAffected
Debug.Print "Return value: " & ACmd.Parameters("ReturnValue")
Debug.Print "Output param: " & ACmd.Parameters("HasExpedite")
'use record set here
'...
'close
If Not RS Is Nothing Then RS.Close
ACon.Close
End Sub