I try to get return value of this Oracle stored procedure with VBScript, but everytime returned NULL (nothing), not populate cmd("retVal"). Where is my mistake?
If this stored procedure works fine it must return a integer value like 1 or -2 parameters are passed and change db data, but not return any value in OUT param in stored procedure.
Set connORA = Server.CreateObject("ADODB.Connection")
connORA.ConnectionString = "conninfo"
connORA.Open
Set cmd = Server.CreateObject("ADODB.Command")
Set cmd.ActiveConnection = connORA
cmd.CommandText = "{call procedureName(486954,100002335,'0','0',0,'12-03-2015','X','IPTAL_EDILDI',443,'SAT',?)}"
cmd.Prepared = True
cmd.Parameters.Append cmd.CreateParameter("retVal", 131, 2)
cmd.Execute
Response.Write cmd("retVal")
Set oCmd = Nothing
connORA.Close
Set connORA=nothing
Related
I am doing some clean up to protect from SQL injection attacks happening in a older internal website that uses ASP. Here's the gist of it all in code...
Database connection is setup in a separate asp file named connect.asp
<%
on error resume next
Set DB = Server.CreateObject("ADODB.Connection")
DB.CommandTimeout = 180
DB.ConnectionTimeout = 180
connStr = "Provider=SQLOLEDB;Data Source=xxx-xxxx-xxxxxx;Initial Catalog=xxxx;Persist Security Info=True;User ID=xxxxx;Password=xxxxxxxxxxxx;"
DB.Open connStr
' Check DB connection and go to Error Handler is error exists
if DB.state=0 then
Response.Write "<p>Cannot connect to database.</p>"
TrapError Err.description
Response.end
end if
%>
This works and the db connections is opened.
I have a file named DBFunctions.asp that I use to sort of map functions to stored procedures and their parameters. I am trying to use the function below to return a ADO recordset to another asp front end page.
Function GetFacilityByFID(fid)
set rs = server.CreateObject("ADODB.Recordset")
Set cmd = Server.CreateObject("ADODB.Command")
Set cmd.ActiveConnection = DB
cmd.CommandText = "GetFacilityByFID"
cmd.CommandType = adCmdStoredProc
cmd.Parameters.Append cmd.CreateParameter("#FID", adVarChar, adParamInput, 20)
cmd("#FID") = fid
Set rs = cmd.Execute
Set GetFacilityByFID = rs
End Function
Here is the code from the calling front end asp page, facDetail.asp
<%
Dim FID, FCBI, Error
FID = Request("FID")
FCBI = Request("FCBI")
' Check DB connection and go to Error Handler is error exists
if DB.state=0 then
Response.Write "<p>Cannot connect to database.</p>"
TrapError Err.description
Response.end
else
if FID then
Set RS = GetFacilityByFID(FID)
elseif FCBI then
Set RS = GetFacilityByFCBI(FCBI)
end if
if RS.EOF then
Response.Write "<BR><p class=alert>No record found</p>"
response.End
end if
end if
%>
The calling page is displaying that there are no records returned
but the stored procedure works when executed in SSMS.
Updated code
Here's the SQL Code for the GetFacilityByFID stored procedure.
CREATE PROCEDURE [dbo].[GetFacilityByFID]
#FID varchar(20)
AS
BEGIN
SET NOCOUNT ON;
SELECT [FAC_CBI_NBR]
,[FAC_ID]
,[FAC_TYPE]
,[FAC_SUBTYPE]
,[FAC_REGION]
,[FAC_COST_CENTER]
,[FAC_SUPPLY_CODE]
,[FAC_UPLINE]
,[FAC_SERVICE]
,[FAC_LOCATION_NAME]
,[FAC_LOCAL_ADDR1]
,[FAC_LOCAL_ADDR2]
,[FAC_LOCAL_CITY]
,[FAC_LOCAL_STATE]
,[FAC_LOCAL_ZIP]
,[FAC_MAIL_ADDR1]
,[FAC_MAIL_ADDR2]
,[FAC_MAIL_CITY]
,[FAC_MAIL_STATE]
,[FAC_MAIL_ZIP]
,[FAC_COUNTRY]
,[FAC_PHONE]
,[FAC_FAX]
,[FAC_MANAGER]
,[FAC_CONTACT]
,[FAC_CONTACT_PHONE]
,[FAC_CONTACT_EXT]
,[FAC_CONTACT_EMAIL]
,[FAC_COMMENTS]
,[FAC_CHANGED_BY]
,[FAC_LAST_UPDATE]
,[FAC_MAILOUT]
,[FAC_CONTRACTION]
,[FAC_PROPERTY_CODE]
,[FAC_ATTN_TO]
FROM [cbid].[dbo].[FACILITY]
WHERE [FAC_ID]=#FID
END
GO
Can anyone tell me what is going wrong? I have been looking at this too long and have grown frustrated with it.
Any help will be greatly appreciated!
Edit:
Current Status of issue: getting the following from the ADO provider
Error Number: -2147217904 Error Desc: Procedure or function 'GetFacilityByFID' expects parameter '#FID', which was not supplied. Error Source: Microsoft OLE DB Provider for SQL Server
You need to specify the size.
From CreateParameter Method (ADO)
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.
cmd.Parameters.Append cmd.CreateParameter("#FID", adVarChar, adParamInput, Len(fid))
Before going any further, the first thing is to confirm if your stored procedure GetFacilityByFID actually returns a Recordset? Most likely it does not. If it only return a single string value, you should modify Function GetFacilityByFID(fid) to something like below:
Function GetFacilityByFID(fid)
Set cmd = Server.CreateObject("ADODB.Command")
Set cmd.ActiveConnection = DB
cmd.CommandText = "GetFacilityByFID"
cmd.CommandType = adCmdStoredProc
cmd.Parameters.Append cmd.CreateParameter("#returnVal", adVarChar, adParamOutput, 255, "")
cmd.Parameters.Append cmd.CreateParameter("#FID", adVarChar, adParamInput)
cmd("#FID") = fid
cmd.Execute
GetFacilityByFID = cmd("#returnVal")
End Function
I rewrote the function using some of the examples I found and some of LankyMart's suggestions. Thanks everyone for your help.
Here's the working code...
Function GetFacilityByFID(fid)
Set rs = server.CreateObject("ADODB.Recordset")
Set cmd = Server.CreateObject("ADODB.Command")
Set cmd.ActiveConnection = DB
cmd.CommandText = "GetFacilityByFID"
cmd.CommandType = adCmdStoredProc
cmd.CommandTimeout = 900
set prm = cmd.CreateParameter("#FID",adVarChar, adParamInput, 20, fid)
cmd.Parameters.Append prm
rs.CursorLocation = adUseClient
rs.Open cmd, , adOpenForwardOnly, adLockReadOnly
Set GetFacilityByFID = rs
On Error GoTo 0
End Function
The way you're setting the parameters looks no right, try this instead:
Dim param
Set param = cmd.CreateParameter("#FID", adVarChar, adParamInput)
cmd.Parameters.Append param
param.Value = fid
You can read more here: https://msdn.microsoft.com/pt-br/library/ms675860(v=vs.85).aspx
Hope it helps
I have an Excel 2013 VBA macro which needs to call an SQL procedure on an Oracle 12c database. The Oracle procedure is executed (it writes the result into a table) but in Excel I receive the error at Set rs = cmd.Execute:
Operation is not allowed when the object is closed
Below the code:
Dim v_userpw As String
Dim cnn As ADODB.Connection
Dim rs As New ADODB.Recordset
Dim cmd As New ADODB.Command
Dim l_userpw, l_reqid, l_pwhash, l_sighash As New ADODB.Parameter
Dim objErr As ADODB.Error
v_userpw = Cells(7, 1).Value
On Error GoTo err_test
'Set cnn = CreateObject("ADODB.Connection")
Set cnn = New ADODB.Connection
cnn.ConnectionString = "Provider=OraOLEDB.Oracle;Data Source=devdb;User ID=db1;Password=db1;"
cnn.Open
Set cmd = New ADODB.Command
Set cmd.ActiveConnection = cnn
Set l_userpw = cmd.CreateParameter("l_userpw", adVarChar, adParamInput, 1024, v_userpw)
cmd.Parameters.Append l_userpw
Set l_reqid = cmd.CreateParameter("l_reqid", adVarChar, adParamOutput, 1024)
cmd.Parameters.Append l_reqid
Set l_pwhash = cmd.CreateParameter("l_pwhash", adVarChar, adParamOutput, 1024)
cmd.Parameters.Append l_pwhash
Set l_sighash = cmd.CreateParameter("l_sighash", adVarChar, adParamOutput, 1024)
cmd.Parameters.Append l_sighash
'cmd.Properties("PLSQLRSet") = True
cmd.CommandText = "{CALL db1.genheader(?, ?, ?, ?)}"
Set rs = cmd.Execute
'cmd.Properties("PLSQLRSet") = False
Cells(8, 1) = rs.Fields("reqid").Value
Cells(9, 1) = rs.Fields("pwhash").Value
Cells(10, 1) = rs.Fields("sighash").Value
cnn.Close
err_test:
MsgBox Error$
For Each objErr In cnn.Errors
MsgBox objErr.Description
Next
cnn.Errors.Clear
Resume Next
The Oracle procedure looks like this:
create or replace procedure genheader (
l_userpw in varchar2,
l_reqid out varchar2,
--l_pwhash out raw,
--l_sighash out raw
l_vpwhash out varchar2,
l_vsighash out varchar2
)
I need to return the values in the predefined cells.
Does the procedure actually return a resultset? It looks like it just returns data using output parameters, so you wouldn't get the results from a recordset, you'd get them from the command parameters after the command executes.
cmd.Execute
Cells(8, 1) = cmd("l_reqid")
Cells(9, 1) = cmd("l_pwhash")
Cells(10, 1) = cmd("l_sighash")
Try testing the connection state to ensure it is open prior to assigning the connection to the ActiveConnection property of the command object. This can cause unstable behavior. If you don't want to do this in code, you can assign a breakpoint prior to the set line of code and check your locals window for the connection state. Also you need to specify the name of the Oracle Stored Procedure
cmd.Name = "genheader"
Cheers,
Boris
To preface this post, I want to say that I am fairly new to Excel 2007 vba macros. I am trying to call an Oracle PL/SQL stored procedure that has a cursor as an output parameter. The procedure spec looks like this:
PROCEDURE get_product
(
out_cur_data OUT SYS_REFCURSOR,
rptid IN NUMBER,
scenario IN VARCHAR2
);
And I have written my macro as:
Sub GetProduct()
Const StartRow As Integer = 4
Dim conn As ADODB.Connection
Set conn = New ADODB.Connection
With conn
.ConnectionString = "<my connection string>"
.Open
End With
Dim cmd As ADODB.Command
Set cmd = New ADODB.Command
With cmd
.ActiveConnection = conn
.CommandType = adCmdText
.CommandText = "{call their_package.get_product({out_cur_data 100},?,?)}"
.NamedParameters = True
.Parameters.Append cmd.CreateParameter("rptid", adNumeric, adParamInput, 0, 98)
.Parameters.Append cmd.CreateParameter("scenario", adVarChar, adParamInput, 4, "decline001")
End With
Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
With rs
.CursorType = adOpenStatic
.CursorLocation = adUseClient
.LockType = adLockOptimistic
End With
Set rs = cmd.Execute
Cells(StartRow + 1, 1).CopyFromRecordset rs
rs.Close
conn.Close
End Sub
This does not work obviously, I get a run-time error '-2147217900 (80040e14): One or more errors occurred during processing of command.' So, OK.
I am looking for some guidance/advice on how to bring back that cursor into an ADODB.RecordSet. I don't think I have set up the output cursor correctly for "out_cur_data", but my searches online for any help have come up dry so far. Can any give me a basic working example to help me understand what I am doing wrong?
BTW... I do not have control of the stored procedure at all, it is from an external package.
Any help is really appreciated.
Thanks,
Doran
I think it should be this one:
With cmd
.Properties("PLSQLRSet") = TRUE
.ActiveConnection = conn
.CommandType = adCmdText
.CommandText = "{call their_package.get_product(?,?)}"
.NamedParameters = True
.Parameters.Append cmd.CreateParameter("rptid", adNumeric, adParamInput, 0, 98)
.Parameters.Append cmd.CreateParameter("scenario", adVarChar, adParamInput, 4, "decline001")
End With
...
Set rs = cmd.Execute
cmd.Properties("PLSQLRSet") = FALSE
Note:
Although their_package.get_product() takes three parameters, only two need to be bound because Ref cursor parameters are automatically bound by the provider.
For more information check Oracle documentation: Oracle Provider for OLE DB Developer's Guide - "Using OraOLEDB with Visual Basic"
I have a function that returns an ADO recordset, this in turn is used to feed subsequent functions. The first time it's passed to one of these functions, it runs fine; but in the next function, the recordset is at EOF. I assumed that my recordset was being passed By Ref, so tried using MoveFirst: this gives me an error because the cursor type is defaulted as forward only.
I tried the following:
Set it the parameter as By Val; this had no effect.
Created a copy of the recordset and used the original in the first function and the copy in the second; still the same problem (second is saying EOF, which implies that it's empty rather than at EOF; I can't imagine that the two recordsets are connected in some way, but perhaps).
Changed my DB code to use a different cursor type (adOpenStatic): This would actually be more correct for my application, but it gave very weird results. Namely, in a query that worked fine before, after the change, the first field was not being returned! This is kind of important, so I couldn't continue down this route.
Any ideas?
Main DB query function: GetData. Takes SQL and list of varchar parameters (if any) and returns recordset. It references dbOpen, which simply opens the connection with the supplied credentials.
Private Function GetData(ODBC As DBConnection, ByVal QuerySQL As String, Optional Parameters As Collection) As ADODB.Recordset
Dim DB As ADODB.Connection
Dim Query As ADODB.Command
Dim Parameter As ADODB.Parameter
Set DB = New ADODB.Connection
If dbOpen(DB, ODBC) Then
Set Query = New ADODB.Command
Query.ActiveConnection = DB
Query.CommandText = QuerySQL
If Not Parameters Is Nothing Then
For Each param In Parameters
Set Parameter = Query.CreateParameter(, adVarChar, adParamInput, Len(param), param)
Query.Parameters.Append Parameter
Next
Set Parameter = Nothing
End If
Set GetData = Query.Execute
' Uncommenting this seems to reset the data in GetData...
' A bit of a memory leak, but we assume VBA will take care of it :P
'dbClose DB
Else
MsgBox "Cannot connect to the database.", vbExclamation
Set GetData = Nothing
End If
End Function
I then do something like this:
Dim myRecords as ADODB.Recordset
' For the sake of argument, we assume this returns a nontrivial recordset
Set myRecords = GetData(someDSN, someSQL, someParameters)
Debug.Print myRecords.EOF ' Returns False
ID = writeHeader(myRecords)
Debug.Print myRecords.EOF ' Returns True
writeData ID, myRecords ' Breaks because at EOF
The writeHeader function will go through each record in the recordset, using MoveNext. However, if I pass myRecords by value or as a copy, then we still get the EOF problem. If I add MoveFirst at the end of the record iteration code in writeHeader, it will complain that I can't move to the beginning of such a recordset. If I change my GetData function so that Set GetData = Query.Execute is changed to:
Set GetData = New ADODB.Recordset
GetData.CursorType = adOpenStatic
GetData.Open Query
Then, when a query in my code is run -- which hasn't changed -- the first field is not a part of the recordset (!?) Say, for example, myRecords!ID is the first field: if this is referenced, I'll get a warning that said item isn't a member of the records (and it's not an enumerated member, either; so myRecords.Fields(1) just returns the next field in the query).
With a useful tip from #TimWilliams, I solved this by changing my GetData function to the following:
Private Function GetData(ODBC As DBConnection, ByVal QuerySQL As String, Optional Parameters As Collection) As ADODB.Recordset
Dim DB As ADODB.Connection
Dim Query As ADODB.Command
Dim Parameter As ADODB.Parameter
Dim Output As ADODB.Recordset
Set DB = New ADODB.Connection
If dbOpen(DB, ODBC) Then
Set Query = New ADODB.Command
Query.ActiveConnection = DB
Query.CommandText = QuerySQL
If Not Parameters Is Nothing Then
For Each param In Parameters
Set Parameter = Query.CreateParameter(, adVarChar, adParamInput, Len(param), param)
Query.Parameters.Append Parameter
Next
Set Parameter = Nothing
End If
Set Output = New ADODB.Recordset
Output.CursorType = adOpenStatic
Output.CursorLocation = adUseClient
Output.Open Query
Else
MsgBox "Cannot connect to the database.", vbExclamation
Set Output = Nothing
End If
Set GetData = Output
End Function
Then, before cursoring through the recordset, I use MoveFirst.
I have been working an ADO VBScript that needs to accept parameters and incorporate those parameters in the Query string that gets passed the the database. I keep getting errors when the Record Set Object attempts to open. If I pass a query without parameters, the recordset opens and I can work with the data. When I run the script through a debugger, the command object does not show a value for the parameter object. It seems to me that I am missing something that associates the Command object and Parameter object, but I do not know what. Here is a bit of the VBScript Code:
...
'Open Text file to collect SQL query string'
Set fso = CreateObject("Scripting.FileSystemObject")
fileName = "C:\SQLFUN\Limits_ADO.sql"
Set tso = fso.OpenTextFile(fileName, FORREADING)
SQL = tso.ReadAll
'Create ADO instance'
connString = "DRIVER={SQL Server};SERVER=myserver;UID=MyName;PWD=notapassword; Database=favoriteDB"
Set connection = CreateObject("ADODB.Connection")
Set cmd = CreateObject("ADODB.Command")
connection.Open connString
cmd.ActiveConnection = connection
cmd.CommandText = SQL
cmd.CommandType = adCmdText
Set paramTotals = cmd.CreateParameter
With paramTotals
.value = "tot%"
.Name = "Param1"
End With
'The error occurs on the next line'
Set recordset = cmd.Execute
If recordset.EOF then
WScript.Echo "No Data Returned"
Else
Do Until recordset.EOF
WScript.Echo recordset.Fields.Item(0) ' & vbTab & recordset.Fields.Item(1)
recordset.MoveNext
Loop
End If
The SQL string that I use is fairly standard except I want to pass a parameter to it. It is something like this:
SELECT column1
FROM table1
WHERE column1 IS LIKE ?
I understand that ADO should replace the "?" with the parameter value I assign in the script. The problem I am seeing is that the Parameter object shows the correct value, but the command object's parameter field is null according to my debugger.
I know this is old, but for anyone still fiding this (like I did via google):
If you're using Stored Procedures:
set cmd = Server.CreateObject("ADODB.Command")
with cmd
.ActiveConnection = db_connection
.CommandText = "stored_procedure_name"
.CommandType = adCmdStoredProc
.Parameters.Append .CreateParameter("#Parameter1",adInteger,adParamInput,,1)
.Parameters.Append .CreateParameter("#Parameter2",adVarChar,adParamInput,100,"Up to 100 chars")
.Parameters.Append .CreateParameter("#Parameter3",adBoolean,adParamInput,,true)
.Parameters.Append .CreateParameter("#Parameter4",adDBTimeStamp,adParamInput,,now())
end with
set rs = cmd.execute
'do stuff with returned results from select or leave blank if insert/delete/etc stored procedure
set rs = nothing
set cmd = nothing
If not, I beleive you change the .CommandText to your SQL statement with questions marks in place and your Parameters must follow the same order.
See http://www.devguru.com/technologies/ado/quickref/command_createparameter.html
For a breakdown of what values you're passing with CreateParameter, as well as a list of types and their descriptions.
After you create the parameter, you have to append it to the Command object's Parameters collection before you execute the Command:
Set paramTotals = cmd.CreateParameter
With paramTotals
.Value = "tot%"
.Name = "Param1"
End With
cmd.Parameters.Append paramTotals
You may also need to specify the Type and Size properties for the Parameter. Generally, I use the arguments of the CreateParameter function to set all the necessary properties in one line:
Set paramTotals = cmd.CreateParameter("Param1", adVarChar, adParamInput, 30, "tot%")
cmd.Parameters.Append paramTotals
I never got CreateParameter doing what I wanted. Proper parameterization is a necessity to avoid SQL injection, but CreateParameter is a complete PITA. Thankfully, there's an alternative: Command.Execute takes in parameters directly.
dim cmd, rs, rows_affected
set cmd = Server.createObject("adodb.command")
cmd.commandText = "select from Foo where id=?"
set cmd.activeConnection = someConnection
set rs = cmd.execute(rows_affected, Array(42))
It's much nicer when wrapped up in a proper abstraction. I wrote my own database class wrapping ADODB.Connection so I wouldn't have to do all of this manually. It relies a bit on other custom classes, but the gist of it should be apparent:
class DatabaseClass
' A database abstraction class with a more convenient interface than
' ADODB.Connection. Provides several simple methods to safely query a
' database without the risk of SQL injection or the half-dozen lines of
' boilerplate otherwise necessary to avoid it.
'
' Example:
'
' dim db, record, record_set, rows_affected
' set db = Database("/path/to/db")
' set record = db.get_record("select * from T where id=?;", Array(42))
' set record_set = db.get_records("select * from T;", empty)
' rows_affected = db.execute("delete from T where foo=? and bar=?",
' Array("foo; select from T where bar=", true))
private connection_
' An ADODB connection object. Should never be null.
private sub CLASS_TERMINATE
connection_.close
end sub
public function init (path)
' Initializes a new database with an ADODB connection to the database at
' the specified path. Path must be a relative server path to an Access
' database. Returns me.
set connection_ = Server.createObject ("adodb.connection")
connection_.provider = "Microsoft.Jet.OLEDB.4.0"
connection_.open Server.mapPath(path)
set init = me
end function
public function get_record (query, args)
' Fetches the first record returned from the supplied query wrapped in a
' HeavyRecord, or nothing if there are no results.
dim data: set data = native_recordset(query, args)
if data.eof then
set get_record = nothing
else
set get_record = (new HeavyRecordClass).init(data)
end if
end function
public function get_records (query, args)
' Fetches all records returned from the supplied query wrapped in a
' RecordSet (different from the ADODB recordset; implemented below).
set get_records = (new RecordSetClass).init(native_recordset(query, args))
end function
public function execute (query, args)
' Executes the supplied query and returns the number of rows affected.
dim rows_affected
build_command(query).execute rows_affected, args
execute = rows_affected
end function
private function build_command (query)
' Helper method to build an ADODB command from the supplied query.
set build_command = Server.createObject("adodb.command")
build_command.commandText = query
set build_command.activeConnection = connection_
end function
private function native_recordset (query, args)
' Helper method that takes a query string and array of arguments, queries
' the ADODB connection, and returns an ADODB recordset containing the
' result.
set native_recordset = build_command(query).execute( , args) ' Omits out-parameter for number of rows
end function
end class