I want to create a procedure or function in oracle that returns more than one row of different types to my application. I found some examples on the internet but i can't get it to work...
Example:
I created a custom type in oracle:
create type proc_selectall is object
(Mjera varchar2(50),
Status number(10,0),
Naziv varchar2(50),
Stat number(10,0));
Then:
create type ret_selectall is table of proc_selectall;
Then created function:
create or replace function fsp_SelectAll return ret_selectall
is
l_ret_selectall ret_selectall := ret_selectall();
n integer := 0;
BEGIN
for r in(SELECT JEDINICAMJERE.Mjera,
JEDINICAMJERE.Status,
KATEGORIJE.Naziv,
KATEGORIJE.Status as Stat
FROM JEDINICAMJERE, KATEGORIJE)
loop
l_ret_selectall.extend;
n := n + 1;
l_ret_selectall(n) := proc_selectall(r.Mjera, r.Status, r.Naziv, r.Stat);
end loop;
return l_ret_selectall;
END;
When I type this line:
select * from table (fsp_SelectAll)
I get a nice and expected output:
MJERA | STATUS | NAZIV | STAT
tr 0 name1 1
fd 1 name2 1
ds 1 name3 0
.....
Later i tried to get those values in my app.
I tried something like this:
//cmd.Parameters.Add("proc_selectall ", OracleDbType.Object);
//cmd.Parameters.Add("ret_selectall ", OracleDbType.Array);
//cmd.Parameters["ret_selectall "].Direction = ParameterDirection.ReturnValue;
//if (connection.State == ConnectionState.Closed)
// connection.Open();
//cmd.ExecuteNonQuery();
Later this...
//OracleCommand objCmd = new OracleCommand("select * from table fsp_SelectAll", connection);
//objCmd.CommandType = CommandType.StoredProcedure;
//objCmd.ExecuteNonQuery();
//int size=objCmd.ExecuteReader().FieldCount;
I also tried with
//adapter = new OracleDataAdapter(cmd);...
But there are always some errors about type, parameters...
(Connection to oracle work. I executed Insert, Update, Delete procedures and they work fine)
Also i tried to create this procedure: (this was my first try)
CREATE OR REPLACE PROCEDURE osp_SelectAll (Mjera OUT VARCHAR2,
Status OUT NUMBER, Naziv OUT VARCHAR2,Stat OUT NUMBER) AS
BEGIN
SELECT JEDINICAMJERE.Mjera,
JEDINICAMJERE.Status,
KATEGORIJE.Naziv,
KATEGORIJE.Status as Stat INTO Mjera, Status, Naziv, Stat
FROM JEDINICAMJERE, KATEGORIJE;
END osp_SelectAll;
But it doesn't return any row or rows... and i give up.
Please help me, i need to solve this.
Returning a user defined object is not so easy, it requires a lot of additional effort.
Try this one:
create or replace function fsp_SelectAll return SYS_REFCURSOR is
res SYS_REFCURSOR;
BEGIN
OPEN res FOR
SELECT JEDINICAMJERE.Mjera,
JEDINICAMJERE.Status,
KATEGORIJE.Naziv,
KATEGORIJE.Status as Stat
FROM JEDINICAMJERE, KATEGORIJE;
return res;
END;
and then
OracleCommand objCmd = new OracleCommand("fsp_SelectAll", connection);
objCmd.CommandType = CommandType.StoredProcedure;
objCmd.Parameters.Add("res", OracleDbType.RefCursor, ParameterDirection.ReturnValue);
var da = new OracleDataAdapter(objCmd);
var dt = new DataTable();
da.Fill(dt);
Or you can use ExecuteReader(), if you prefer:
OracleDataReader dr = objCmd.ExecuteReader();
while ( dr.Read() ) {
...
}
dr.Close();
Note, I never worked with .CommandType = CommandType.StoredProcedure;. If it fails use this syntax:
OracleCommand objCmd = new OracleCommand("BEGIN :res := fsp_SelectAll; END;", connection);
objCmd.CommandType = CommandType.Text;
Related
I am trying to insert into a table while returning its identity value. But "Index was outside the bounds of the array" error is thrown. I can execute query in dbForge successfully but not when I try to execute query in C# with oracle managed data access .
Query is very simple . If I disable transaction, the row is inserted in the database but i get the error and cannot get the return value.
var query = #"insert into table1 VALUES (97,'Mondon') RETURNING Id INTO :id";
OracleTransaction transaction = null;
using (var connection = new OracleConnection(_conStr))
{
try
{
connection.Open();
var command = connection.CreateCommand();
transaction = connection.BeginTransaction();
command.Transaction = transaction;
command.CommandText = query;
command.CommandTimeout = 5 * 60;
command.Parameters.Add(new OracleParameter("id", OracleDbType.Int32, ParameterDirection.ReturnValue));
var result = command.ExecuteNonQuery();
transaction.Commit();
var id = Convert.ToInt32(command.Parameters["id"].Value);
}
catch (Exception ex)
{
transaction.Rollback();
Logger.LogError(ex);
}
}
you have too much code
you have misconception(s)
Let me know if you have questions, see comments inline
int newId = 0;
// NOTE, if you don't insert into field 'ID' you need to list fields
var sql = "insert into table1 (fld1, fd2) VALUES (97,'Mondon') RETURNING Id INTO :id";
try
{
using (var conn = new OracleConnection(_conStr))
{
using (var cmd = new OracleCommand(sql, conn))
{
cmd.CommandType = CommandType.Text;
cmd.Parameters.Add(new OracleParameter("id", OracleDbType.Int32, ParameterDirection.Output)); // this is output, not return
conn.Open();
var count = cmd.ExecuteNonQuery();
if (count > 0) // table can have a trigger so number of rows changed can be more than 1
{
// YOUR BIG MISCONCEPTION HERE (FIXED)
OracleDecimal val = (OracleDecimal)cmd.Parameters["id"].Value; // this returns special oracle struct
int newId = val.ToInt32(); // you can use val.IsNull but here it is not possible
}
else
throw new Exception("Value not inserted");
}
}
}
catch (Exception ex)
{
Logger.LogError(ex);
}
Note that for insert of a single record explicit transaction is not needed
Old post, but I had this same problem today, and found a solution here: https://stackoverflow.com/a/29660204/210916
"You need to declare the variable as shown below. As a rule of thumb, always test your query on the Oracle server before you embed it into your code. Most, importantly use parametrized Store Procedures to avoid sql injection attacks. So Do not embed queries into your code." #Dan Hunex
In your query, you need to declare id before INSERT command
I think you have to encapsulate the insert into a function:
create function InsertTable1(n in integer, val in varchar2) return integer as
res integer;
begin
insert into table1 VALUES (n, val) RETURNING Id INTO res;
RETURN res;
end;
And then in your application:
var query = #"BEGIN :0 := InsertTable1(97,'Mondon'); END;";
It would be a good idea to define also the input values as bind-parameters, rather than static strings.
A full dynamic solution could be similar to this:
create function InsertTable(cmd in varchar2) return integer as
res integer;
begin
EXECUTE IMMEDIATE cmd USING OUT res;
RETURN res;
end;
var query = #"BEGIN :0 := InsertTable('insert into table1 VALUES (97,''Mondon'') RETURNING Id INTO :res'); END;";
I have used following stored procedure in sql:
alter procedure [dbo].[usp_Member_Org_OnGoingJobs]
(
#idUser varchar(50)
)
as
begin
declare #qry as varchar(max)
set #qry='select J.idJob,j.DateAdded,j.OpenedByWho,j.JobAddress ,j.final,j.idOrg,j.note
from Job J
inner join Users U on
U.idOrg=J.idOrg
where U.IdUser='+ #idUser+ '
and ISNULL(j.Final,'')=''
order by idJob'
execute(#qry)
end
GO
This stored procedure is formed sucessfully in sql.
But, When i tried to use them through asp.net c#, It gives me error:
Incorrect syntax near the keyword 'order'.
Everything seems correct.
Please tell me where i am making mistake??
Edit:
private void BindOnGoingJobs()
{
string sqlOnGoingJobs = "usp_Member_Org_OnGoingJobs";
DataTable dtJobList = new DataTable();
ArrayList paramList = new ArrayList();
paramList.Add(new ParamData("#idUser", Convert.ToString(Session["idUser"])));
dtJobList = obj.ExecuteProcedureAndGetDataTable(sqlOnGoingJobs, paramList);
grdOnGoingJobs.DataSource = dtJobList;
grdOnGoingJobs.DataBind();
paramList.Clear();
}
public DataTable ExecuteProcedureAndGetDataTable(string procedureName, ArrayList Parameters)
{
DataTable dt = new DataTable();
try
{
if (con.State != ConnectionState.Open)
con.Open();
cmd.Connection = con;
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = procedureName;
cmd.Parameters.Clear();
foreach (ParamData p in Parameters)
{
cmd.Parameters.AddWithValue(p.pName, p.pValue);
}
da.SelectCommand = cmd;
da.Fill(dt);
con.Close();
return dt;
}
catch (Exception ex)
{
con.Close();
return dt;
}
}
You need to double your single quotes around the ISNULL check
set #qry='select J.idJob,j.DateAdded,j.OpenedByWho,j.JobAddress ,j.final,j.idOrg,j.note
from Job J
inner join Users U on
U.idOrg=J.idOrg
where U.IdUser='+ #idUser+ '
and ISNULL(j.Final,'''')=''''
order by idJob'
You need to escape the quotation mark by placing quotes 2 times like this.
and ISNULL(j.Final,'''')=''''
Check this blog post http://blog.sqlauthority.com/2008/02/17/sql-server-how-to-escape-single-quotes-fix-error-105-unclosed-quotation-mark-after-the-character-string/
Just add the table alias in order by, I guess should solve the issue
order by J.idJob
I'm working on converting an existing application from SQL Server over to Oracle, and I've hit a roadblock. I'm trying to execute an anonymous block as dynamic SQL and return a result set. However nothing I've tried seems to be able to return any values. Stored procedures are out due to design constraints.
My query is defined as:
DECLARE type id_array IS TABLE OF number;
t_Ids id_array;
BEGIN
UPDATE CSM_RECORDS SET MIGRATION_STATE = 1, LAST_UPDATE = CURRENT_DATE
WHERE OBJECT_UID IN
(SELECT OBJECT_UID
FROM CSM_RECORDS obj
WHERE MIGRATION_STATE = 0
AND ROWNUM <= :BatchSize)
AND (:BatchName IS NULL OR obj.BATCH_NAME = :BatchName)
RETURNING OBJECT_UID BULK COLLECT INTO t_Ids;
OPEN rcursor FOR SELECT * FROM CSM_RECORDS;-- WHERE OBJECT_UID IN (t_Ids);
END;
You can see I've commented out the WHERE clause on the cursor in an attempt just to get anything to return at all.
Over on the C# side, I've got:
OracleCommand getNextNodesC = new OracleCommand(SQL_AS_SHOWN_ABOVE, conn);
getNextNodesC.BindByName = true;
OracleParameter batchSizeP = new OracleParameter("BatchSize", OracleDbType.Int32);
batchSizeP.Value = batchSize;
getNextNodesC.Parameters.Add(batchSizeP);
OracleParameter batchNameP = new OracleParameter("BatchName", OracleDbType.Varchar2);
batchNameP.Value = batchName;
getNextNodesC.Parameters.Add(batchNameP);
OracleParameter returnCursor = new OracleParameter("rcursor", OracleDbType.RefCursor);
returnCursor.Direction = ParameterDirection.Output;
getNextNodesC.Parameters.Add(returnCursor);
getNextNodesC.ExecuteNonQuery();
return ((Oracle.ManagedDataAccess.Types.OracleRefCursor)returnCursor.Value).GetDataReader();
The end goal is a DbDataReader that I can use, but in the above code, the returnCursor.Value seems to remain null. I've tried various combinations of Output vs. ReturnValue parameters and ExecuteNonQuery() and ExecuteReader() to no avail.
Any pointers would be appreciated, but an example of code that would actually accomplish what I'm looking for would be spectacular.
TLDR: You're missing the colon on your cursor bind variable:
OPEN :rcursor FOR SELECT * FROM CSM_RECORDS;-- WHERE OBJECT_UID IN (t_Ids);
Full answer:
As you probably know, in oracle there is the pl/sql context (your anonymous block) and the SQLplus context. Here is a block with SQLplus vars, the block with the related bind variables, and a SQLplus print at the end:
var rcursor refcursor
var fromDate varchar2(50)
var toDate varchar2(50)
exec :fromDate := '1-mar-2014';
exec :toDate := '1-apr-2014';
begin
open :rcursor for
SELECT
trunc(to_date(:fromDate,'dd-mon-yyyy')) + NUMTODSINTERVAL(n,'day') AS Full_Date
FROM (
select (level-1) n
from dual
connect by level-1 <= trunc(to_date(:toDate,'dd-mon-yyyy')) - trunc(to_date(:fromDate,'dd-mon-yyyy'))
)
;
end;
/
print rcursor
When executing a block in .net, ODP.net is taking care of the preparation done at the SQLplus level. Here is the same block executed from .net (as an nunit test):
[Test]
public void RefCursorFromBatch()
{
OracleCommand cmd = new OracleCommand();
cmd.CommandText = #"
begin
open :rcursor for
SELECT
trunc(to_date(:fromDate,'dd-mon-yyyy')) + NUMTODSINTERVAL(n,'day') AS Full_Date
FROM (
select (level-1) n
from dual
connect by level-1 <= trunc(to_date(:toDate,'dd-mon-yyyy')) - trunc(to_date(:fromDate,'dd-mon-yyyy'))
)
;
end;";
cmd.BindByName = true;
cmd.Parameters.Add("fromDate", OracleDbType.Date).Value = DateTime.Today.AddDays(-30);
cmd.Parameters.Add("toDate", OracleDbType.Date).Value = DateTime.Today;
cmd.Parameters.Add("rcursor", OracleDbType.RefCursor).Direction = ParameterDirection.Output;
using (cmd.Connection = new OracleConnection("..."))
{
cmd.Connection.Open();
var reader = cmd.ExecuteReader();
OracleDataAdapter da = new OracleDataAdapter(cmd);
DataTable dt = new DataTable();
da.Fill(dt);
Assert.Greater(dt.Rows.Count, 0);
}
}
You can read more here: http://www.brothersincode.com/post/executing-SQL-Plus-Batches-from-Net.aspx
In case you do not have an answer yet, here is some example code that I have confirmed is working, that you can use as a starting point.
using System;
using System.Data;
using Oracle.ManagedDataAccess.Client;
using Oracle.ManagedDataAccess.Types;
namespace ConsoleApplication1
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
try
{
string conString = "User Id=scott;Password=tiger;Data Source=orcl;Pooling=false;";
OracleConnection con = new OracleConnection();
con.ConnectionString = conString;
con.Open();
string cmdtxt = "BEGIN " +
"OPEN :1 for select ename, deptno from emp where deptno = 10; " +
"OPEN :2 for select ename, deptno from emp where deptno = 20; " +
"OPEN :3 for select ename, deptno from emp where deptno = 30; " +
"END;";
OracleCommand cmd = con.CreateCommand();
cmd.CommandText = cmdtxt;
OracleParameter p1 = cmd.Parameters.Add("refcursor1",
OracleDbType.RefCursor);
p1.Direction = ParameterDirection.Output;
OracleParameter p2 = cmd.Parameters.Add("refcursor2",
OracleDbType.RefCursor);
p2.Direction = ParameterDirection.Output;
OracleParameter p3 = cmd.Parameters.Add("refcursor3",
OracleDbType.RefCursor);
p3.Direction = ParameterDirection.Output;
cmd.ExecuteNonQuery();
OracleDataReader dr1 =
((OracleRefCursor)cmd.Parameters[2].Value).GetDataReader();
OracleDataReader dr2 =
((OracleRefCursor)cmd.Parameters[1].Value).GetDataReader();
while (dr1.Read() && dr2.Read())
{
Console.WriteLine("Employee Name: " + dr1.GetString(0) + ", " +
"Employee Dept:" + dr1.GetDecimal(1));
Console.WriteLine("Employee Name: " + dr2.GetString(0) + ", " +
"Employee Dept:" + dr2.GetDecimal(1));
Console.WriteLine();
}
Console.WriteLine("Press 'Enter' to continue");
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.InnerException);
Console.WriteLine(ex.Data);
}
}
}
}
Try this as your dynamic sql...
DECLARE type id_array IS TABLE OF number;
t_Ids id_array;
BEGIN
UPDATE CSM_RECORDS SET MIGRATION_STATE = 1, LAST_UPDATE = CURRENT_DATE
WHERE OBJECT_UID IN
(SELECT OBJECT_UID
FROM CSM_RECORDS obj
WHERE MIGRATION_STATE = 0
AND ROWNUM <= :BatchSize)
RETURNING OBJECT_UID BULK COLLECT INTO t_Ids;
SELECT * FROM CSM_RECORDS;-- WHERE OBJECT_UID IN (t_Ids);
END;
Thanks for the feedback everyone. It turns out that while debugging this issue, at some point I switched out the variable that defined my query, so that changes I was making to the query weren't actually being applied when running the application. As a result, I'm not sure exactly which fix ended up being the answer. But just for completeness, here's what I ended up using that worked.
First, run:
create type id_array as table of number;
(From answer on this question)
Query:
DECLARE t_Ids id_array;
BEGIN
UPDATE CSM_RECORDS SET MIGRATION_STATE = 1, LAST_UPDATE = CURRENT_DATE
WHERE OBJECT_UID IN
(SELECT OBJECT_UID
FROM CSM_RECORDS obj
WHERE MIGRATION_STATE = 0
AND ROWNUM <= :BatchSize
AND (:BatchName IS NULL OR obj.BATCH_NAME = :BatchName)
RETURNING OBJECT_UID BULK COLLECT INTO t_Ids;
OPEN :rcursor FOR SELECT * FROM CSM_RECORDS WHERE OBJECT_UID IN (SELECT * FROM TABLE(cast(t_Ids as id_array)));
END;
And the C# looks like:
OracleCommand getNextNodesC = new OracleCommand(QUERY_DEFINED_ABOVE.Replace("\r\n", "\n"), conn);
getNextNodesC.BindByName = true;
OracleParameter batchSizeP = new OracleParameter("BatchSize", OracleDbType.Int32);
batchSizeP.Value = batchSize;
getNextNodesC.Parameters.Add(batchSizeP);
OracleParameter batchNameP = new OracleParameter("BatchName", OracleDbType.Varchar2);
batchNameP.Value = batchName;
getNextNodesC.Parameters.Add(batchNameP);
OracleParameter returnCursor = new OracleParameter("rcursor", OracleDbType.RefCursor);
returnCursor.Direction = ParameterDirection.ReturnValue;
getNextNodesC.Parameters.Add(returnCursor);
return getNextNodesC.ExecuteReader();
b_levitt's suggestion about the missing colon on the cursor bind variable was spot-on though. And I also needed to replace all the "\r\n"s in my query with "\n".
I'm still not clear on why I need to run CREATE TYPE... rather than just using DECLARE TYPE... since the latter seems to work fine for the BULK COLLECT part of the block, but it does seem to work fine now.
Thanks again for the help.
I have a stored procedure that selects certain columns into a #table. I then do a select * from that #table in order to retrieve my results.
My problem is that when I use SELECT * INTO #tableName it returns the #table name and the results, but when I SELECT DoodleName INTO #tableName it returns a blank table name.
I want to use the table name in my code.
This is my C# code:
DataTable dataTable = new DataTable();
using (SqlCommand command = new SqlCommand("doodle", connection))
{
command.CommandType = CommandType.StoredProcedure;
try
{
if (connection.State == ConnectionState.Closed)
openConnectionString(connection);
SqlDataReader reader = command.ExecuteReader();
dataTable.Load(reader);
reader.Close();
}
catch (Exception ex)
{
userConnectionErrorMessage = "Error Retrieving results. " + ex.Message;
}
finally
{
if (connection.State == ConnectionState.Open)
connection.Close();
}
Here is the stored procedure that returns the #table name:
SELECT *
INTO [#table]
FROM tblDoodle d
RIGHT OUTER JOIN tblRandom r
ON d.DoodleID = d.DoodleID
WHERE r.RandomID LIKE 1
AND d.Active LIKE 0;
SELECT *
FROM #table;
and here is the stored procedure that does not return the #table name:
SELECT d.DoodleName
INTO [#table]
FROM tblDoodle d
RIGHT OUTER JOIN tblRandom r
ON d.DoodleID = d.DoodleID
WHERE r.RandomID LIKE 1
AND d.Active LIKE 0;
SELECT *
FROM #table;
I have no idea how to get this working. I also think it is an interesting enough question. I can probably use a workaround to get this done, but this is bothering me way more than it should.
How can I use a stored procedure (with parameters - has a return value of type int) from code behind?
My stored procedure looks like this :
ALTER Procedure [dbo].[sp_Noskheh_SumOfTotalPay]
#Co_ID int
AS
-----------------
Declare #Sum bigint
-----------------
BEGIN
SELECT
#Sum = SUM(TotalPay)
FROM Noskheh
WHERE
(Co_ID = #Co_ID)
RETURN #Sum
END
I want to use #Sum in code behind ...
Would you please show me a way for doing that ?
Thanks in advance
best regards
You need to set up a SqlConnection and a SqlCommand. If you have your code with the RETURN #Sum statement in the end, you need to do this (define a parameter of type RETURN_VALUE):
using(SqlConnection _conn = new SqlConnection(-your-connection-string-here))
using(SqlCommand _cmd = new SqlCommand("dbo.sp_Noskheh_SumOfTotalPay", _conn))
{
_cmd.CommandType = CommandType.StoredProcedure;
_cmd.Parameters.Add(new SqlParameter("#CO_ID", SqlDbType.Int));
_cmd.Parameters["#CO_ID"].Value = 5; // whatever value you want
_cmd.Parameters.Add(new SqlParameter("#RETURN_VALUE", SqlDbType.BigInt));
_cmd.Parameters["#RETURN_VALUE"].Direction = ParameterDirection.ReturnValue;
_conn.Open();
_cmd.ExecuteNonQuery();
Int64 result = Int64.Parse(_cmd.Parameters["#RETURN_VALUE"].Value);
_conn.Close();
}
It would be a lot easier if you would replace that RETURN statement with a simple SELECT:
SELECT #Sum
In that case, you can use the simplified version I had before - using .ExecuteScalar() to retrieve the single value of the single row being returned from the stored proc:
using(SqlConnection _conn = new SqlConnection(-your-connection-string-here))
using(SqlCommand _cmd = new SqlCommand("dbo.sp_Noskheh_SumOfTotalPay", _conn))
{
_cmd.CommandType = CommandType.StoredProcedure;
_cmd.Parameters.Add(new SqlParameter("#CO_ID", SqlDbType.Int));
_cmd.Parameters["#CO_ID"].Value = 5; // whatever value you want
_conn.Open();
object result = _cmd.ExecuteScalar();
_conn.Close();
Int64 sum = Int64.Parse(result);
}
That should call your stored proc, read the single value you're returning, and converting it into an int variable called sum.
There is no shortage of tutorials on this subject.
You can add an SqlParameter #Sum with Direction set to ReturnValue
Example:
SqlCommand cmd = new SqlCommand():
cmd.Connection = // place your SqlConnection object;
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "StoreProcedureName";
cmd.Parameters.Add("#Sum", SqlDbType.BigInt).Direction = ParameterDirection.ReturnValue