I'm trying to pass a list of parameters to a SQL query in VBA, I have an excel table with a list of codes in a range that is dynamic, could 2, could be 2000. I want to then return all data from SQL where the codes match. Below I'm trying to define an ADODB parameter as an array, then I want to pass that to SQL.
However defining the parameter variable as a range gives a type mismatch. Or Object required if I don't define as an array;
Dim conn As New ADODB.Connection
conn.Open "Provider=SQLOLEDB; Data Source=XXXXXXXXX; Initial Catalog=CDB; Integrated Security=SSPI;"
Dim code(0) As ADODB.Parameter
Set code(0) = wb.ActiveSheet.Range(Cells(2, colCode), Cells(rowCount, colCode)).Value
'Dim code As ADODB.Parameter
'Set code = wb.ActiveSheet.Range(Cells(2, colCode), Cells(rowCount, colCode)).Value
'Dim code(rowCount - 1) As ADODB.Parameter
'Set code(rowCount - 1) = wb.ActiveSheet.Range(Cells(2, colCode), Cells(rowCount, colCode)).Value
Set cmd = New ADODB.Command
Set rs = New ADODB.Recordset
With cmd
.ActiveConnection = conn
.CommandType = adCmdText
.Parameters.Append .CreateParameter("#code", adVarChar, adParamInput, 10000, code)
.CommandTimeout = 0
End With
sql = "SELECT * FROM table WHERE code in (?);"
cmd.CommandText = sql
Set rs = cmd.Execute
Related
I've tried searching a lot of places and can't quite find what I'm looking for.
I want to write a sub routine in vba that will tell me the parameters of a stored procedure stored on SQL Server.
I know how to execute a stored proc with parameters from excel vba. And I have written a stored proc that takes a stored proc name and returns the parameters. So I could use this. But I thought maybe there is a better way that I don't know about. I found a SQLCommandBuilder Class for VB that would be perfect but I need it in VBA. Is this available in VBA and I just don't know where to activate it?
Thanks
**Additional information: After the helpful comments below I am getting closer to what I am aiming to achieve.
I want to be able to pass any stored procedure into my subroutine and it will be able to figure out how many parameters it needs and what they will be
Here is my code so far
Private Sub execStoredProcedureWithParameters(strServer As String,
strDatabase As String, strSchema As String, strUSPName As String)
'Declare variables
Dim cmd As ADODB.Command
Dim conn As ADODB.Connection
Dim prm As ADODB.Parameter
Dim rs As ADODB.Recordset
Dim intParamCount As Integer
'Open database connection
Set conn = New ADODB.Connection
conn.ConnectionString = "Provider=sqloledb;Data Source=" + strServer + ";Initial Catalog=" + strDatabase + ";Integrated Security=SSPI;"
conn.CommandTimeout = 0
'Here's where the connection is opened.
conn.Open
'This can be very handy to help debug!
'Debug.Print conn.ConnectionString
Set cmd = New ADODB.Command
With cmd
.CommandText = strSchema + "." + strUSPName
.CommandType = adCmdStoredProc
.ActiveConnection = conn
.Parameters.Refresh
For intParamCount = 0 To .Parameters.Count - 1
Debug.Print .Parameters(intParamCount).Name, .Parameters(intParamCount).Type, .Parameters(intParamCounti).Size, .Parameters(intParamCount).Attributes, .Parameters(intParamCount).NumericScale
' Set prm = cmd.CreateParameter(.Parameters(i).Name, adVarChar, adParamInput, 255)
' cmd.Parameters.Append prm
' cmd.Parameters(.Parameters(i).Name).Value = "DBName"
Next
End With
Set rs = New ADODB.Recordset
'Execute the Stored Procedure
Set rs = cmd.Execute
'Populate the sheet with the data from the recordset
Sheet1.Range("RecordSet").CopyFromRecordset rs
'Cleanup
rs.Close
Set rs = Nothing
conn.Close
Set conn = Nothing
End Sub
Concerning the parameters. Is there a way to convert the DataTypeEnum from the value to the constant. So the type is currently coming through as 202 for the first parameter which I would set to adVarWChar according to this table
https://learn.microsoft.com/en-us/sql/ado/reference/ado-api/datatypeenum
You can do this with ADODB, add a reference to Microsoft ActiveX Data Objects then you can:
With New ADODB.Command
Set .ActiveConnection = myAdoDbConnection
.CommandText = "[dbo].[usp_XXX]"
.CommandType = adCmdStoredProc
.Parameters.Refresh
For i = 0 To .Parameters.Count - 1
Debug.Print .Parameters(i).Name, .Parameters(i).Type, .Parameters(i).Direction
Next
End With
There should be a necessity requiring this as it requires a round trip to the server.
I have a Access front end that has an ODBC connection titled fna to the Sybase back end on a server. I can easily reference the tables in the Sybase database but in the vba I need to be able to call stored procedures and functions that are in Sybase.
Function name is CalcStats.
Edit:
After reading through some of the links in the comment, I have gotten to the following code which seems to run but I can't figure out how to find the return value the function is providing.
Dim trash As String
Dim conn As ADODB.Connection
Dim cmd1 As ADODB.Command
Dim rs1 As Recordset
Set conn = New ADODB.Connection
conn.ConnectionString = "datasource=ODBC;DSN=fna"
conn.Open
Set cmd1 = New ADODB.Command
cmd1.ActiveConnection = conn
cmd1.CommandType = adCmdStoredProc
cmd1.CommandText = "CalcStats"
cmd1.Execute
Edit 2:
Finally figured out the output part. Thank you A.S.H for the links and Gord Thompson for the method for getting the connection string that for the ADODB that is needed.
So I can now create a connection, trigger the function and get the return value. However, now I'm working on the last part which is adding input parameters. I've added an input parameter to the function in sybase and saved it but I can't seem to pass an input value into the function on the Access side.
Dim trash As String
Dim conn As ADODB.Connection
Dim cmd1 As ADODB.Command
Set conn = New ADODB.Connection
conn.ConnectionString = "datasource=ODBC;DSN=fna"
conn.Open
Set cmd1 = New ADODB.Command
cmd1.ActiveConnection = conn
cmd1.CommandType = adCmdStoredProc
cmd1.CommandText = "CalcStats"
cmd1.Parameters.Append cmd1.CreateParameter("MeasurementClm", adInteger, adParamInput, , 3)
cmd1.Parameters.Append cmd1.CreateParameter("OutputValue1", adInteger, adParamReturnValue)
cmd1.Execute
trash = cmd1.Parameters.Item(0).Value
conn.Close
So the last problem I was having is that for whatever reason, I could declare an output parameter if there were no inputs but if there are inputs I had to remove the output parameter declaration. The Function output is in the parameters.item(0) position. Thanks to A.S.H and Gord Thompson for the links and suggestions. Here is the code I ended up with that is working now:
Dim trash As String
Dim conn As ADODB.Connection
Dim cmd1 As ADODB.Command
Dim rs1 As Recordset
Set conn = New ADODB.Connection
conn.ConnectionString = "datasource=ODBC;DSN=fna"
conn.Open
Set cmd1 = New ADODB.Command
cmd1.ActiveConnection = conn
cmd1.CommandType = adCmdStoredProc
cmd1.CommandText = "CalcStats"
cmd1.Parameters.Refresh
cmd1.Parameters.Append cmd1.CreateParameter("InputValue1", adInteger, adParamInput, , 3)
'cmd1.Parameters.Append cmd1.CreateParameter("OutputValue3", adInteger, adParamReturnValue)
cmd1.Execute
trash = cmd1.Parameters.Item(0).Value
conn.Close
I am trying to execute a SQL Server stored procedure from Excel VBA. The procedure returns rows into a result set object. However, while running the code, it throws an error:
3704 Operation is not allowed when the object is closed
Note:
There is no problem with the database connection because Select query running on the same connection object are working fine.
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim cmd As ADODB.Command
Dim prm As ADODB.Parameter
Dim rst As New ADODB.Recordset
Set cn = New ADODB.Connection
Set cmd = New ADODB.Command
ThisWorkbook.initialize
cn.Provider = "sqloledb"
cn.Properties("Data Source").Value = ThisWorkbook.server
cn.Properties("Initial Catalog").Value = ThisWorkbook.db
cn.Properties("User ID").Value = "xxxxx"
cn.Properties("Password").Value = "xxxxx"
cn.Open
Set cmd = New ADODB.Command
cmd.CommandText = "Generate_KPI_Process_Quality_Check_RunTime"
cmd.CommandType = adCmdStoredProc
cmd.ActiveConnection = cn
Set prm = cmd.CreateParameter("#currentMonth", adChar, adParamInput, 255, cmb_month.Value)
cmd.Parameters.Append prm
Set prm = cmd.CreateParameter("#center", adChar, adParamInput, 255, cmb_center.Value)
cmd.Parameters.Append prm
rst.CursorType = adOpenStatic
rst.CursorLocation = adUseClient
rst.CursorLocation = adUseServer
rst.LockType = adLockOptimistic
rst.Open cmd
If (rst.BOF And rst.EOF) Then
'Some Code
End If
Put
SET NOCOUNT ON
in the stored procedure -- this will prevent output text generation like "1 record(s) updated".
You have to provide more parameters for the Open method of Recordset Object
try rst.Open cmd, cn
Use the Set keyword to assign the object:
Set cmd.ActiveConnection = cn
otherwise, the default property of the Connection object (which happen to be the connection string) will be assigned in lieu of the Connection object itself.
Just put another recordset that will contain resultsets
Dim rst1 As New ADODB.Recordset
SET rst1=rst.NextRecordset 'this will return the first resultset
If rst1.BOF or rst1.EOF Then...
'some code
End If
I have excel VBA script:
Set cоnn = CreateObject("ADODB.Connection")
conn.Open "report"
Set rs = conn.Execute("select * from table" )
Script work fine, but i want to add parameter to it. For example " where (parentid = myparam)", where myparam setted outside query string. How can i do it?
Of course i can modify query string, but i think it not very wise.
You need to use an ADODB.Command object that you can add parameters to. Here's basically what that looks like
Sub adotest()
Dim Cn As ADODB.Connection
Dim Cm As ADODB.Command
Dim Pm As ADODB.Parameter
Dim Rs as ADODB.Recordset
Set Cn = New ADODB.Connection
Cn.Open "mystring"
Set Cm = New ADODB.Command
With Cm
.ActiveConnection = Cn
.CommandText = "SELECT * FROM table WHERE parentid=?;"
.CommandType = adCmdText
Set Pm = .CreateParameter("parentid", adNumeric, adParamInput)
Pm.Value = 1
.Parameters.Append Pm
Set Rs = .Execute
End With
End Sub
The question mark in the CommandText is the placeholder for the parameter. I believe, but I'm not positive, that the order you Append parameters must match the order of the questions marks (when you have more than one). Don't be fooled that the parameter is named "parentid" because I don't think ADO cares about the name other than for identification.
Alternative example returning a command from a function:
Function BuildCommand(conn As ADODB.Connection) As ADODB.Command
Dim cmd As ADODB.Command
Set cmd = New ADODB.Command
cmd.ActiveConnection = conn
cmd.CommandType = adCmdText
cmd.Parameters.Append cmd.CreateParameter("#name", adVarChar, adParamInput, 255, "Dave")
cmd.CommandText = "SELECT * FROM users WHERE name = #name;"
Set BuildCommand = cmd
End Function
A couple things to note:
When using adVarChar data type, the size argument to cmd.CreateParameter (e.g. 255) is required. Not supplying it results a run-time error 3708: Application-defined or object-defined error, as indicated in the documentation:
If you specify a variable-length data type in the Type argument, you must either pass a Size argument or set the Size property of the Parameter object before appending it to the Parameters collection; otherwise, an error occurs.
If the cmd.ActiveConnection property is set when cmd.CommandText is set, and cmd.CommandText contains named parameters, cmd.Parameters will be populated accordingly. Calling cmd.Parameters.Append afterwards could result in duplicates. For example:
cmd.ActiveConnection = conn
cmd.CommandType = adCmdText
Debug.Print cmd.Parameters.Count ' 0
cmd.CommandText = "SELECT * FROM users WHERE name = #name;"
Debug.Print cmd.Parameters.Count ' 1
cmd.Parameters.Append cmd.CreateParameter("#name", adVarChar, adParamInput, 255, "Dave")
Debug.Print cmd.Parameters.Count ' 2
I believe this is what is meant in the documentation, which is slightly inaccurate:
If the Prepared property of the Command object is set to True and the Command object is bound to an open connection when you set the CommandText property, ADO prepares the query (that is, a compiled form of the query that is stored by the provider) when you call the Execute or Open methods.
As a workaround, either set cmd.CommandText or cmd.ActiveConnection after adding parameters.
I am trying to run a stored procedure from Excel. I know how to do it without using dynamic dates but I need the date range to be dynamic.
Sub TestStoredProcedure()
Dim CServer As String
Dim CDatabase As String
Dim CLogon As String
Dim CPass As String
Dim StartDate As Date
Dim EndDate As Date
Dim TStartDate As String
Dim TEndDate As String
CServer = "111111" ' Your server name here
CDatabase = "111111" ' Your database name here
CLogon = "11111111" ' your logon here
CPass = "111111" ' your password here
Dim Cmd1 As New ADODB.Command
Dim rs As New ADODB.Recordset
Dim intTemp As Integer
Set Cmd1 = New ADODB.Command
Cmd1.ActiveConnection = cn
Cmd1.CommandText = "callstatisticsbyQ"
Cmd1.CommandType = adCmdStoredProc
Cmd1.Parameters.Refresh
Cmd1.Parameters(0).Value = Worksheets("Sheet2").Range("A1")
Cmd1.Parameters(1).Value = Worksheets("Sheet2").Range("A2")
Cmd1.Parameters(2).Value = Worksheets("Sheet2").Range("A3")
Set rs = Cmd1.Execute()
rs.Open Cmd1
Worksheets("Procedure Export").Range("A1").CopyFromRecordset rs
Call DumpSP("prcGetData", "", "", Worksheets("Procedure Export").Range("A1"))
End Sub
I get an error saying something about user defined type not defined.
To use ADO you click Tools->references in the VBA IDE & tick "Microsoft ActiveX Data Objects" - preferably the highest version thereof.
Additionally you use cn as the connection but its not defined in that sub (assuming its not global) & you will may need to Set Cmd1.ActiveConnection = cn.
Also take a look at this, it defines the input (adParaminput) paramaters in advance rather than using .Refresh which is pretty inefficient (takes a trip to the server)
Update for example:
rem for create procedure callstatisticsbyQ (#i int, #c varchar(10)) as select 1234;
Dim cn As ADODB.Connection
Dim Cmd1 As ADODB.Command
Dim rs As ADODB.Recordset
Set cn = New ADODB.Connection
Set Cmd1 = New ADODB.Command
Set Cmd1 = New ADODB.Command
cn.Open "Provider=SQLNCLI10;Server=1.2.3.4;Database=x;Uid=x; Pwd=x;"
Set Cmd1.ActiveConnection = cn
Cmd1.CommandText = "callstatisticsbyQ"
Cmd1.CommandType = adCmdStoredProc
Cmd1.Parameters.Append Cmd1.CreateParameter("p1", adInteger, adParamInput, , Worksheets("Sheet2").Range("A1"))
Cmd1.Parameters.Append Cmd1.CreateParameter("p2", adVarChar, adParamInput, 20, Worksheets("Sheet2").Range("A2"))
Set rs = Cmd1.Execute()
MsgBox rs(0)
rs.Close
cn.Close