So, I am currently working on an app where I need to call a stored procedure that takes a User defined table as a parameter. In .NET I am able to just pass the data in a datatable, but in MS Access VBA I am having difficulty passing an ADO recordset to the stored procedure as the user defined table.
Private Sub DeleteRecords()
On Error GoTo Err_DeleteRecords
Dim cnn As ADODB.Connection, cmd As New ADODB.Command, param As New ADODB.Parameter
Dim i As Integer
Dim rs As ADODB.Recordset
Dim text As String
Dim gs As String
Set cnn = CurrentProject.Connection
gs = "DECLARE #StagingRecsToDelete StagingRecsToDelete_UDT; SELECT * FROM #StagingRecsToDelete"
Set rs = New ADODB.Recordset
rs.Open gs, cnn, adOpenKeyset, adLockOptimistic
If rs.RecordCount = 0 Then
For i = 0 To Me.lstFingerPrintServiceStaging.ListCount - 1
If Me.lstFingerPrintServiceStaging.Selected(i) Then
text = text + Me.lstFingerPrintServiceStaging.Column(0, i)
rs.AddNew
rs!intFingerPrintServiceStagingID = text
End If
Next i
End If
If rs.RecordCount > 0 Then
Set cmd = New ADODB.Command
cmd.ActiveConnection = cnn
cmd.CommandType = adCmdStoredProc
cmd.CommandText = "dbo.SPFingerPrintDeleteErrors"
Set param = cmd.CreateParameter("#ReturnValue", adInteger, adParamReturnValue)
cmd.Parameters.Append param
Set param = cmd.CreateParameter("StagingRecsToDelete_UDT", adVarient, adParamInput, , rs)
cmd.Parameters.Append param
Set param = cmd.CreateParameter("#ErrNbr", adInteger, adParamOutput)
cmd.Parameters.Append param
Set param = cmd.CreateParameter("#ErrMsg", adVarChar, adParamOutput, 8000)
cmd.Parameters.Append param
cmd.Execute
End If
Select Case cmd.Parameters("#ReturnValue").Value
Case 1 'System error
MsgBox cmd.Parameters("#ErrMsg").Value, vbOKOnly + vbInformation, gApplicationTitle
Exit Sub
Case 0 'Succeeds
MsgBox "The Selected records have been successfully deleted.", vbOKOnly + vbInformation, gApplicationTitle
Case -1 'User Error
MsgBox cmd.Parameters("#ErrMsg").Value, vbOKOnly + vbInformation, gApplicationTitle
Exit Sub
End Select
cnn.Close
Set cnn = Nothing
Exit_DeleteRecords:
Exit Sub
Err_DeleteRecords:
MsgBox "Form=" & Me.Name & ", Function=DeleteRecords" & vbCrLf & "Err#=" &
Err & " " & Error$
Resume Exit_DeleteRecords
End Sub
For the most part, all of it works, but I do not know what datatype to set the UDT to when I pass it to the stored procedure.
The MS Access (DAO) does not support it.
You can create an XML with your data and pass it as SP param or insert it into a temporary table one by one fisrt.
Related
I'm making a program in which I have to check some column values in another table before trying to save values in a different table.. both tables are in SQL.
I tried my best to do it myself but I get the error near the highlighted line.
rs.open(insert into testreport_tb1...
Private Sub Command1_Click()
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim BrdSrNo As String
Dim Result As Boolean
Dim machineName As String
machineName = Environ("computername")
' Ready objects for use.
Set cn = New ADODB.Connection
Set rs = New ADODB.Recordset
BrdSrNo = BoardSrNo.Text
Result = False
' Connect.
cn.Open "{Here I give the connection string}"
' Fetch a recordset.
rs.Open "select * from testreport_tb1 where board_SrNo = '" & BrdSrNo & "' order by test_DateTime desc", cn, adOpenStatic, adLockReadOnly
' Display value, and total recordcount.
MsgBox rs.Fields(3)
MsgBox rs.Fields(8)
'MsgBox rs.RecordCount
stage_Status = rs.Fields(3)
stage_Id = rs.Fields(8)
rs.Close
cn.Close
If stage_Status = "C" Then
If stage_Id = "True" Then
rs.Open "insert into testreport_tb1 values('" & BrdSrNo & "',3,GETDATE(),'" & Result & "',NULL,'" & machineName & "',' KO ','A','D')", cn, adOpenDynamic, adLockBatchOptimistic
MsgBox "saved"
End If
End If
' Close and release objects.
rs.Close
cn.Close
Set rs = Nothing
Set cn = Nothing
End Sub
As far as I remember, you can't use rs.Open when executing DML statements, (insert, update or delete), but only when you are executing select statements.
Also, you need to use ADODB.Command and set parameters instead of concatenating strings to create your insert statement, otherwise it's an open door for sql injection attacks.
It's been a very long time since the last time I've worked with ADODB, but your insert code should look something like this:
If stage_Status = "C" And stage_Id = "True" Then
Dim cmd as new ADODB.Command
cmd.CommandText = "insert into testreport_tb1 values(?, 3, GETDATE(), ?, NULL, ?, ' KO ', 'A', 'D')"
cmd.ActiveConnection = cn
Set param = cmd.CreateParameter(, adVarChar, adParamInput)
param.Value = BrdSrNo
cmd.Parameters.Append param
Set param = cmd.CreateParameter(, adVarChar, adParamInput)
param.Value = Result
cmd.Parameters.Append param
Set param = cmd.CreateParameter(, adVarChar, adParamInput)
param.Value = machineName
cmd.Parameters.Append param
cmd.Execute
MsgBox "saved"
End If
Note: Code was written directly here, and as I wrote, it's been a long time since I've used ADODB, so there might be mistakes in the code. However, this is the proper way of executing an insert statement with ADODB.
I am trying to debug Excel VBA code that is calling a stored procedure resident on our SQL Server (SQL Server 2008 R2). With a bit of error trapping I can now see what the error is but I have no idea what is causing it.
Here is my VBA code:
Sub Add_Results_Of_ADO_Recordset()
Dim cnt As ADODB.Connection
Dim rst As ADODB.Recordset
Dim cmd As ADODB.Command
Dim stSQL As String
Const stADO As String = "Provider=SQLOLEDB.1;User ID =xxxxx;Password=xxxxx;" & _
"Persist Security Info=False;" & _
"Initial Catalog=MyDatabase;" & _
"Data Source=xxxxxxxxxxxx"
' initialise ADO
Set cnt = New ADODB.Connection
cnt.Open stADO
On Error GoTo Err_SaveProposal
Set cmd = New ADODB.Command
With cmd
.ActiveConnection = cnt
.CommandText = "PolicyList"
.CommandType = adCmdStoredProc
.NamedParameters = True
.Parameters.Refresh
.Parameters.Append .CreateParameter("#Incept", adVarChar, adParamInput, 10, "2/1/2014")
.Parameters.Append .CreateParameter("#Expire", adVarChar, adParamInput, 10, "2/1/2014")
.Parameters.Append .CreateParameter("#PStatus", adVarChar, adParamInput, 20, "BOOK")
End With
' Debug code to ensure parameters are set correctly
For Each prm In cmd.Parameters
Debug.Print prm.Name & " : " & prm.Value
Next
cmd.Execute
If cnt.State = adStateOpen Then cnt.Close
Exit Sub
Err_SaveProposal:
Debug.Print Err.Number & ": " & Err.Description
If cnt.State = adStateOpen Then cnt.Close
End Sub
Here are the messages written to the immediate window:
#RETURN_VALUE :
#Incept :
#Expire :
#PStatus :
#Incept : 2/1/2014
#Expire : 2/1/2014
#PStatus : BOOK
-2147217900: Procedure or function PolicyList has too many arguments specified.
It looks like the three arguments I am trying to send to the stored procedure are being sent twice. Once as empty strings and one with the values I want to send. Any suggestions?
I've set up a Stored Procedure in SQL Server that works fine. I can now call it from VBA, but want to return a value to know if there were any errors etc. The last parameter in my SP is set up as OUTPUT:
#DataSetID int = 0,
#Destination char(1)='-',
#errStatusOK bit OUTPUT
My VBA to call the SP is below, but it won't work now, after adding the new parameter and I'm not sure where I'm going wrong, I keep getting 3708 - Parameter object is improperly defined. Inconsistent or incomplete information was provided.:
Set cnn = New adodb.Connection
cnn.ConnectionString =
"DRIVER=SQL Server;SERVER=SERVER\SERVER;DATABASE=a_db;Trusted_Connection=Yes"
cnn.Open cnn.ConnectionString
Set cmd = New adodb.Command
cmd.ActiveConnection = cnn
cmd.CommandType = adCmdStoredProc
cmd.CommandText = "stprMoveDataSet"
Set param = cmd.CreateParameter
("#DataSetID", adInteger, adParamInput, , stDataSet)
cmd.Parameters.Append param
Set param = cmd.CreateParameter
("#Destination", adChar, adParamInput, 1, stDestination)
cmd.Parameters.Append param
Set param = cmd.CreateParameter
("#errStatusOK", adBit, adParamReturnValue)
cmd.Parameters.Append param
rs.CursorType = adOpenStatic
rs.CursorLocation = adUseClient
rs.LockType = adLockOptimistic
rs.Open cmd
How can I get the vba to work with the OUTPUT parameter and make the return value 'readable' by the vba.
EDIT - I've changed the question to be more specifically about returning values and not just about using OUTPUT Parameters.
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
Set cnn = New adodb.Connection
cnn.ConnectionString =
"DRIVER=SQL Server;SERVER=SERVER\SERVER;DATABASE=a_db;Trusted_Connection=Yes"
cnn.Open cnn.ConnectionString
Set cmd = New adodb.Command
cmd.ActiveConnection = cnn
cmd.CommandType = adCmdStoredProc
cmd.CommandText = "stprMoveDataSet"
Set param1 = cmd.CreateParameter
("#DataSetID", adInteger, adParamInput, , stDataSet)
cmd.Parameters.Append param
Set param2 = cmd.CreateParameter
("#Destination", adChar, adParamInput, 1, stDestination)
cmd.Parameters.Append param
Set param3 = cmd.CreateParameter
("#errStatusOK", adBit, adParamOutput, , adParamReturnValue)
cmd.Parameters.Append param
rs.CursorType = adOpenStatic
rs.CursorLocation = adUseClient
rs.LockType = adLockOptimistic
rs.Open cmd
I'd initially looked at OUTPUT Parameters, but could not find out how to get them back to Access (in VBA) to then provide feedback to the user. A colleague suggested using a SELECT in the Stored procedure and to use this.
STORED PROCEDURE:
Added the following at the end:
SELECT #errStatusOK as errStatusOK, #countCurrent as countCurrent, #countHistorical as countHistorical
VBA:
Dim cnn As ADODB.Connection
Dim cmd As New ADODB.Command, rs As New ADODB.Recordset, param As New ADODB.Parameter
Dim fld As ADODB.Field
Dim stMessage As String
Set cnn = New ADODB.Connection
cnn.ConnectionString = "DRIVER=SQL Server;SERVER=SERVER\SERVER;DATABASE=a_db;Trusted_Connection=Yes"
cnn.Open cnn.ConnectionString
Set cmd = New ADODB.Command
cmd.ActiveConnection = cnn
cmd.CommandType = adCmdStoredProc
cmd.CommandText = "stprMoveDataSet"
Set param = cmd.CreateParameter("#DataSetID", adInteger, adParamInput, , stDataSet)
cmd.Parameters.Append param
Set param = cmd.CreateParameter("#Destination", adChar, adParamInput, 1, stDestination)
cmd.Parameters.Append param
rs.CursorType = adOpenStatic
rs.CursorLocation = adUseClient
rs.LockType = adLockOptimistic
'rs.Open cmd
Set rs = cmd.Execute
If rs!errstatusok = True Then
stMessage = "Operation appears to have been successful, check the DataSets Listing..." & Chr(13) & "Also, the Server returned the following information: ["
Else
stMessage = "Operation appears to have failed, check the DataSets Listing..." & Chr(13) & "Also, the Server returned the following information: ["
End If
For Each fld In rs.Fields
stMessage = stMessage & "| " & fld.Name & " / " & fld.Value & " |"
Next fld
stMessage = stMessage & "]"
MsgBox stMessage
This returns the folliwing:
Operation appears to have failed, check the DataSets Listing...
Also, the Server returned the following information: [| errStatusOK / False || countCurrent / 0 || countHistorical / 10 |]
Among the other parameter enumerations from which "adParamInput" is taken, another is "adParamOutput", which is to indicate an out parameter from a stored procedure, and "adParamInputOutput" for a parameter which goes "both directions," as it were. In your case, I believe "adParamOutput" would be appropriate. I hope this is what you're looking for.
I am trying to return the results of a stored procedure to a form. I have managed to iterate thru the results using an ADO recordset, but cannot bind the results to the form..
Here is the VBA code:
Private Sub RetrieveSiteInformation()
Dim cmd As New ADODB.Command
Dim cnn As New ADODB.Connection
Dim rs As ADODB.Recordset, f As ADODB.Field
With cnn
.Provider = "SQLOLEDB"
.ConnectionString =
"data source=UKFCSVR;initial catalog=ACACB;Trusted_Connection=Yes"
.Open
End With
Dim param1 As ADODB.Parameter
If Nz(txtSiteID_Search.Value, vbNullString) <> vbNullString Then
Set param1 = cmd.CreateParameter("#SiteID", adBigInt, adParamInput)
param1.Value = txtSiteID_Search.Value
cmd.Parameters.Append param1
End If
With cmd
.ActiveConnection = cnn
.CommandText = "spSiteInformation_Retrieve"
.CommandType = adCmdStoredProc
**' THIS FAILS**
Me.Recordset = .Execute
**' THIS LOOP WORKS FINE**
' Set rs = .Execute
' rs.MoveFirst
' For Each f In rs.Fields
' Debug.Print f.Name
' Next
' With rs
' Do While Not .EOF
' Debug.Print ![CompanyName] & " " & ![Postcode]
' .MoveNext
' Loop
' End With
End With
cnn.Close
End Sub
Okay, I have tested this example. It includes changes to suit my set-up which I have left in, rather than guessing at your set-up. Most of this is taken from http://support.microsoft.com/kb/281998/EN-US/
Dim cn As New ADODB.Connection
Dim cmd As New ADODB.Command
Dim param1 As New ADODB.Parameter
With cn
.Provider = "Microsoft.Access.OLEDB.10.0"
.Properties("Data Provider").Value = "SQLOLEDB"
.Properties("Data Source").Value = "Server"
.Properties("Integrated Security").Value = "SSPI"
.Properties("Initial Catalog").Value = "Test"
.Open
End With
txtSiteID_Search = 1
If Nz(txtSiteID_Search, vbNullString) <> vbNullString Then
Set param1 = cmd.CreateParameter("#SiteID", adBigInt, adParamInput)
param1.Value = txtSiteID_Search
cmd.Parameters.Append param1
End If
With cmd
.ActiveConnection = cn
.CommandText = "spSiteInformation_Retrieve"
.CommandType = adCmdStoredProc
Set Me.Recordset = .Execute
End With
Forget ADO. Create a passthru query in Access, with property ReturnsRecords = True.
Bind your form to that passthru query.
Using VBA, change the .SQL property of that QueryDef object, then open the form. You're done.
Set qry = CurrentDb.QueryDefs("myQryDef")
qry.SQL = "exec spMyStoredProc " & "'argument1'"
You need to use Set whenever you assign an object reference in VBA.
Change Me.Recordset = .Execute to Set Me.Recordset = .Execute.
Also, you probably need to open it with a supported cursor type. I don't think there's a way to change the cursor type if you use the Execute method on the Command object. You'll have to create the Recordset separately.
Set rs = New ADODB.Recordset
rs.Open cmd, , adOpenKeyset
Set Me.Recordset = rs
This works:
Dim rst As New ADODB.Recordset
rst.Open "SELECT * FROM dbo.ftblTest(1,2,3)", CP.Connection, adOpenKeyset, adLockReadOnly
But it would be nicer to do this:
rst.Open "SELECT * FROM dbo.ftblTest(#Param1=1,#Param2=2,#Param3=3)", CP.Connection, adOpenKeyset, adLockReadOnly
If I try the second method I get the error: "parameters were not supplied for the function ftblTest"
Is it possible to use named parameters with multi-statement table-valued functions?
Edit 1: Examples Added Using Command Object
First the SQL
create function ftblTest (#Input int)
RETURNS #Results TABLE (
OutputField int
)
AS
BEGIN
INSERT INTO #Results SELECT #Input
Return
End
Some Code (run from inside an Access 2003 ADP, with a connection to the correct SQL DB)
Public Sub test()
Dim rst As New ADODB.Recordset
Dim cmd As New ADODB.Command
'method 1 works
rst.Open "SELECT * FROM dbo.ftblTest(2)", CurrentProject.Connection, adOpenKeyset, adLockReadOnly
Debug.Print rst.Fields(0)
rst.Close
With cmd
.ActiveConnection = CurrentProject.Connection
.CommandType = adCmdTable
'method 2 works
.CommandText = "dbo.ftblTest(3)"
Set rst = cmd.Execute
Debug.Print rst.Fields(0)
'method 3 fails
.CreateParameter "#Input", adInteger, adParamInput, , 4
.CommandText = "dbo.ftblTest(#Input)"
Set rst = cmd.Execute 'error here:-2147217900 Must declare the scalar variable "#Input".
Debug.Print rst.Fields(0)
End With
End Sub
How can I get the named parameters to work in method 3?
Edit 2: test code modified to use Parameters.Append
Public Sub test()
Dim rst As New ADODB.Recordset
Dim cmd As New ADODB.Command
Dim p As New ADODB.Parameter
With cmd
.ActiveConnection = CurrentProject.Connection
.CommandType = adCmdTable
'Parameter Append method fails
p = .CreateParameter("#Input", adInteger, adParamInput, , 4)
Debug.Print p.Name, p.Type = adInteger, p.Direction = adParamInput, p.SIZE, p.Value 'note that name not set!
With p
.Name = "#Input"
.Type = adInteger
.Direction = adParamInput
.SIZE = 4 'this shouldn't be needed
.Value = 4
End With
Debug.Print p.Name, p.Type = adInteger, p.Direction = adParamInput, p.SIZE, p.Value 'properties now set
.Parameters.Append p
.CommandText = "dbo.ftblTest(#Input)"
Set rst = cmd.Execute 'error here:-2147217900 Must declare the scalar variable "#Input".
Debug.Print rst.Fields(0)
End With
End Sub
this still doesn't work.
Edit 3: I removed the # from create parameter
as suggested and tried the CommandText 3 ways and got 3 different errors:
.CommandText = "dbo.ftblTest"
error: Parameters were not supplied for the function 'dbo.ftblTest'.
.CommandText = "dbo.ftblTest()"
error: An insufficient number of arguments were supplied for the procedure or function dbo.ftblTest.
.CommandText = "dbo.ftblTest(Input)"
error: "Input" is not a recognized table hints option. If it is intended as a parameter to a table-valued function or to the CHANGETABLE function, ensure that your database compatibility mode is set to 90.
This should work:
Dim cmd As New ADODB.Command
With cmd
.ActiveConnection = CurrentProject.Connection
.CommandType = adCmdTable
'you need to add question a mark for each parameter
.CommandText = "dbo.ftblTest(?)"
'you can even add a order by expression like:
.CommandText = "dbo.ftblTest(?) ORDER BY ..."
.Parameters.Append .CreateParameter("#Input", adInteger, adParamInput, , 4)
Set rst = cmd.Execute
Debug.Print rst.Fields(0)
End With
Yes, you can use parameters with a table function.
rst.Open "SELECT * FROM dbo.ftblTest(#Param1,#Param2,#Param3)", CP.Connection, adOpenKeyset, adLockReadOnly
Before you open the database connection add parameters and set their values.
Don't use the # in the name of your parameter and don't list the parameter by name in the command text. I've always done this with a stored procedure, so I'm not sure exactly how the paranethesis are handle for the command text.
try:
.CreateParameter "Input", adInteger, adParamInput, , 4
And:
.CommandText = "dbo.ftblTest()"
Or:
.CommandText = "dbo.ftblTest"