I'm getting an error "Invalid operation on null data" when my C# code is calling Oracle Function. This happens only if no data is found. If data is found and function returns a value, then everything works ok. I'm a little confused, as - to my understanding at least - function should return 100 if no data found (see function exception).
Oracle Function:
create or replace FUNCTION F_SCO_DPD
(
p_tip IN NUMBER,
p_dav IN VARCHAR2
)
RETURN NUMBER
IS
sco NUMBER;
BEGIN
SELECT max(score) keep(dense_rank first order by vrednost)
INTO sco
FROM sco_sif_score
WHERE sif_kat = 11
AND tip_pod = p_tip
AND vrednost >= (SELECT a.dpd
FROM sco_dpd a
WHERE a.par_davcna = p_dav);
RETURN sco;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
RETURN 100;
END F_SCO_DPD;
C# Code:
using (OracleCommand cmd = new OracleCommand())
{
cmd.Connection = conn;
cmd.CommandText = "F_SCO_DPD";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new OracleParameter("p_tip", Podjetje.TipSub));
cmd.Parameters.Add(new OracleParameter("p_dav", Podjetje.Davcna));
cmd.Parameters.Add(new OracleParameter("sco", OracleDbType.Decimal, ParameterDirection.ReturnValue));
cmd.BindByName = true;
cmd.ExecuteScalar();
Score.ScoDpd = (int)(OracleDecimal)cmd.Parameters["sco"].Value;
}
You are running an aggregation function. max(score) keep (dense_rank first order by vrednost) is as much an aggregation function as max(score).
That means that your query is an aggregation query with no GROUP BY. All such queries return exactly 1 row. If no rows match the WHERE clause, then the value is NULL.
So, the exception is never triggered. Instead, check if the returned value is NULL.
The resulting code is:
create or replace FUNCTION F_SCO_DPD
(
p_tip IN NUMBER,
p_dav IN VARCHAR2
)
RETURN NUMBER
IS
v_sco NUMBER;
BEGIN
SELECT max(score) keep (dense_rank first order by vrednost)
INTO v_sco
FROM sco_sif_score
WHERE sif_kat = 11 AND
tip_pod = p_tip AND
vrednost >= (SELECT a.dpd
FROM sco_dpd a
WHERE a.par_davcna = p_dav
);
RETURN COALESCE(v_sco, 100);
END F_SCO_DPD;
Related
I was given this function
CREATE FUNCTION [dbo].[GET_WEBGIS_ISSUE_NUM]
()
RETURNS VARCHAR(50)
AS
BEGIN
DECLARE #v_new_num int, #v_new_issue_num varchar(50);
SET #v_new_num = (SELECT COUNT(*) + 1
FROM [dbo].[WEBGIS_ISSUE]
WHERE [ISSUE_NUM] LIKE CONCAT(FORMAT(GETDATE(), 'yyMM'), '%'));
IF #v_new_num < 10
SET #v_new_issue_num = CONCAT(FORMAT(GETDATE(), 'yyMM'), '00', #v_new_num);
ELSE IF #v_new_num < 100
SET #v_new_issue_num = CONCAT(FORMAT(GETDATE(), 'yyMM'), '00', #v_new_num);
ELSE
SET #v_new_issue_num = CONCAT(FORMAT(GETDATE(), 'yyMM'), #v_new_num);
RETURN #v_new_issue_num
END;
I tried calling it from the following C# code
SqlConnection cnn = new SqlConnection(connectionString);
SqlCommand cmd = new SqlCommand();
cmd.Connection = cnn;
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "[NEPS].[dbo].[GET_WEBGIS_ISSUE_NUM]";
//add any parameters the stored procedure might require
if (cmd.Connection.State == ConnectionState.Closed) //cmd.Connection.Open();
{
cnn.Open();
var o = cmd.ExecuteScalar();
//blabla
cnn.Close();
}
but when I debug the code, I kept on receiving null.
Notes: the connection is ok, it is connected, when I tried changing the function's name it yields an error and when I checked through the SQL Server it also returns an appropriate return value.
You're treating a scalar function as a stored procedure, which is the wrong type for this type of execution. You need to 'CommandType.Text' with scalar functions.
Other notes on the C# part :
use using blocks with SqlConnection and SqlCommand (let the using clause handles the dispose and close connection parts for you).
the query should be declared as const string
always end the query with a semicolon (even if it's running in the SQL Server without it).
avoid using short names, choose a readable naming for your variables.
Here is the C# code :
const string query = "SELECT [NEPS].[dbo].[GET_WEBGIS_ISSUE_NUM]();";
using(SqlConnection connection = new SqlConnection(connectionString))
using(SqlCommand command = new SqlCommand(query, connection))
{
connection.Open();
var result = command.ExecuteScalar();
// do stuff
}
For the function GET_WEBGIS_ISSUE_NUM perhaps you can avoid the extra IFs with this line :
CREATE FUNCTION [dbo].[GET_WEBGIS_ISSUE_NUM]
()
RETURNS VARCHAR(50)
AS
BEGIN
DECLARE #v_new_num int, #v_new_issue_num varchar(50);
SET #v_new_num = (SELECT COUNT(*) + 1
FROM [dbo].[WEBGIS_ISSUE]
WHERE [ISSUE_NUM] LIKE CONCAT(FORMAT(GETDATE(), 'yyMM'), '%'));
SET #v_new_issue_num, FORMAT(GETDATE(), 'yyMM') + RIGHT('000' + CAST(#v_new_num AS VARCHAR), 4);
RETURN #v_new_issue_num
END;
It is more common to use a SELECT statement to return a scalar function result. When you use EXECUTE (due to CommandType.StoredProcedure), you need to also specify a return parameter and retrieve the result from the parameter after execution:
var result = cmd.Parameters.Add("#result", SqlDbType.VarChar, 50);
result.Direction = ParameterDirection.ReturnValue;
cmd.ExecuteNonQuery(); //ExecuteScalar will work too but the result is null and you still need to use the parameter
var o = result.Value;
As noted in the comments to your question, consider the concurrency implications of this approach. Duplicate values will be returned until the row count changes.
What I need:
In PLS/SQL on an Oracle DB, create a stored procedure or function with parameters, which given a declared table of , where is a ROW of a table (with all the fields), returns the resultset following the conditions given in the parameters. After, I need to call them from Microsoft Entity Framework with edmx file.
Basically the need is to being able to provide a quick report of the table contents into a pdf, matching some filters, with an oracle db.
The mantainer must be able, provided a script I give, to create and add new reports, so this needs to be dynamic.
Here's what I've got so far:
CREATE OR REPLACE type THETABLEIWANTTYPE as table of THETABLEIWANT%TYPE
create function
SCHEMA.THETABLEIWANT_FUNCTION(PARAM_GR in number default 1)
return THETABLEIWANTTYPE
PIPELINED
as
result_table THETABLEIWANTTYPE
begin
SELECT S.id, S.idg, S.sta, S.tab
Bulk collect into result_table
from SCHEMA.THETABLEIWANT S
WHERE IDGR = PARAM_GR
IF result_table.count > 0 THEN
for i in result_table.FIRST .. result_table.LAST loop
pipe row (result_table(i))
end loop
end if
return
end;
But it's not working. It gives errors.
Running CREATE TYPE I get:
Compilation errors for TYPE SCHEMA.THETABLEIWANT
Error: PLS-00329: schema-level type has illegal reference to
SCHEMA.THETABLEIWANT
The mantainer will launch the script creating a TYPE of the row of the table I need, then the function should return a table with the records.
Then calling it from Entity Framework I should be able to execute it like I'm calling a normal select from my table, IE:
``_dbContext.THETABLEIWANT.Where(x => x.IDGR = Param_gr).ToList();
The problem is that mantainers should be able to generate new kind of reports with any select inside without the need of my intervention on the software code.
Any hint?
It's ok also to bulk all the select result into a temp table but it has to be dynamic as column will be changing
I ended up to write a PLS/SQL procedure that returns a cursor and managing it from C# code with Oracle.ManagedDataAccess Library.
Here's the procedure, for anyone interested:
CREATE OR REPLACE PROCEDURE SCHEMA.PROC_NAME(
PARAM_1 VARCHAR2,
RESULT OUT SYS_REFCURSOR)
IS
BEGIN
OPEN RESULT FOR
SELECT A, V, C AS MY_ALIAS from SCHEMA.TABLE WHERE FIELD = PARAM_1 AND FIELD_2 = 'X';
END;
And here's the C# code for calling and getting the result:
OracleConnection conn = new OracleConnection("CONNECTIONSTRING");
try
{
if (conn.State != ConnectionState.Open)
conn.Open();
List<OracleParameter> parametri = new List<OracleParameter>()
{
new OracleParameter
{
ParameterName = nameof(filter.PARAM_1),
Direction = ParameterDirection.Input,
OracleDbType = OracleDbType.NVarchar2,
Value = filter.PARAM_1
}
};
OracleCommand cmd = conn.CreateCommand();
cmd.Parameters.AddRange(parametri.ToArray());
OracleParameter cursor = cmd.Parameters.Add(
new OracleParameter
{
ParameterName = "RESULT",
Direction = ParameterDirection.Output,
OracleDbType = OracleDbType.RefCursor
}
);
cmd.CommandText = procedureName;
cmd.CommandType = CommandType.StoredProcedure;
cmd.ExecuteNonQuery();
using (OracleDataReader reader = ((OracleRefCursor)cursor.Value).GetDataReader())
{
if (reader.HasRows)
while (reader.Read())
{
//Iterate the result set
}
}
}
catch(Exception ex)
{
//Manage exception
}
I am learning Npgsql and PostgrSQL. I am unable to get this simple test to work. Here is my function:
CREATE OR REPLACE FUNCTION count_customers(_customerid integer DEFAULT NULL::integer)
RETURNS void AS
$BODY$
BEGIN
SELECT COUNT(*) FROM Customers
WHERE CustomerId = _customerid or _customerid is null;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Here is my C# code:
[Test]
public void ExecuteScalarTest()
{
NpgsqlConnection conn = new NpgsqlConnection("Host=localhost; Database=postgres; User ID=postgres; Password=password");
conn.Open();
IDbCommand command = conn.CreateCommand();
command.CommandText = "count_customers";
command.CommandType = CommandType.StoredProcedure;
object result = command.ExecuteScalar();
conn.Close();
Console.WriteLine(result);
}
I keep getting the error below.
Npgsql.NpgsqlException : ERROR: 42601: query has no destination for result data
This is nothing to do with nPgSQL. Your problem is in your stored function.
You've written a trivial wrapper in PL/PgSQL, but you haven't used RETURN. You can't use SELECT in PL/PgSQL except when its output goes to a variable (via SELECT INTO or as a subquery like x := (SELECT ...) or to the RETURN QUERY statement.
You should write:
BEGIN
RETURN QUERY
SELECT COUNT(*) FROM Customers
WHERE CustomerId = _customerid
OR _customerid is null;
END
and define your procedure as RETURNS bigint, since obviously you cannot get a value from the function if it returns void. Also, this function is STABLE not VOLATILE. If you aren't sure, say nothing. The same is true for COST - unless you have a good reason, leave it out.
This is still overcomplicated though. You can use a simple sql function for calls like this, e.g.
CREATE OR REPLACE FUNCTION count_customers(_customerid integer DEFAULT NULL::integer)
RETURNS bigint LANGUAGE sql STABLE AS
$BODY$
SELECT COUNT(*) FROM Customers
WHERE CustomerId = $1 OR $1 is null;
$BODY$;
I'm having problems executing a function in one of my oracle packages from c#. The following is my code for opening the connection and executing the function:
Decimal firstID = Decimal.Parse("2453699");// This values are just for testing
string secondID = "12345";
Decimal sec = Decimal.Parse("1");
string estatus = "TEXT";
OracleConnection con = new OracleConnection();
con.ConnectionString = "User Id=user;Password=the_pass;Data Source=Data_Source";
con.Open();
string sql = "Package.F_FUNCTION_1";
OracleCommand com = new OracleCommand(sql, con);
com.CommandType = System.Data.CommandType.StoredProcedure;
com.Parameters.Add("returnVal", OracleDbType.Varchar2, 32767);
com.Parameters["returnVal"].Direction = System.Data.ParameterDirection.ReturnValue;
com.Parameters.Add("v_firstID",OracleDbType.Decimal,10);
com.Parameters.Add("v_secondID", OracleDbType.Varchar2,200);
com.Parameters.Add("p_sec", OracleDbType.Decimal, 3);
com.Parameters.Add("p_estatus", OracleDbType.Varchar2,50);
com.Parameters["v_firstID"].Value = firstID;
com.Parameters["v_secondID"].Value = secondID;
com.Parameters["p_sec"].Value = sec;
com.Parameters["p_estatus"].Value = estatus;
com.ExecuteNonQuery();
string val = com.Parameters["returnVal"].Value.ToString();
con.Close();
And the following is the function in my package, which needs to call a second function in the same package:
FUNCTION F_FUNCTION_1(v_firstID IN NUMBER,
v_secondID IN VARCHAR2
,p_sec IN NUMBER DEFAULT NULL
,p_estatus IN VARCHAR2 DEFAULT NULL)
RETURN VARCHAR2 AS
v_Return VARCHAR2(200) := '';
secuencia VARCHAR2(1000) := null;
secc VARCHAR2(1000) := NULL;
BEGIN
-- Some validations
v_Return := Package.F_FUNCTION_2(
v_secondID => v_secondID,
P_SEC => p_sec,
P_ESTATUS => p_estatus
);
return v_Return;
END F_FUNCTION_1;
FUNCTION F_FUNCTION_2(v_secondID IN VARCHAR2
,p_sec IN NUMBER DEFAULT NULL
,p_estatus IN VARCHAR2 DEFAULT NULL)
RETURN VARCHAR2 AS
--some variables
v_URL VARCHAR2(500) := NULL;
BEGIN
--A lot of code here that works
RETURN v_URL;
END F_FUNCTION_2;
The problem is: when i run this code, i get the error:
ORA-01460: unimplemented or unreasonable conversion requested
I suppose it is related to the parameters types. I'v tried changing types, using ExecuteScalar instead of ExecuteNonQuery, changing the syntax of the parameters addition to the command... but they didn't work.
I know it's easy but I have a brain lock now.
Hope someone can help.
For this to work you need to add a parameter to your oracle function as an OUT parameter and then extract the parameter as you are doing after using ExecuteNonQuery. I would add the OUT parameter as the first one in your F_FUNCTION_1 function.
So I have this stored procedure in SQL server that has this bit of SQL in it
... part of store procedure...
IF ##ERROR = 0 --AND ##ROWCOUNT = 1
BEGIN
.. dO STUFF
SELECT * FROM MyTable
RETURN 0
END
ELSE
BEGIN
RAISERROR('Something went wrong :-(', 16, 1)
RETURN -1
END
END
in my C# code where I get the data I do it like this
//Sql param used to get the return value from the store procedure
SqlParameter returnValueParam = command.Parameters.Add("#return_value", SqlDbType.Int);
returnValueParam.Direction = ParameterDirection.ReturnValue;
using (var reader = command.ExecuteReader(CommandBehavior.CloseConnection))
{
while (reader.Read())
{
SpRetrunValue.EmailAddress = DBNulls.DBNullToString(reader["Email"], string.Empty);
... More stuff
}
reader.NextResult();
SpRetrunValue.ExternalData = new List<ExternalData>();
var ExtData = new ExternalData();
while (reader.Read())
{
ExtData.Id = DBNulls.DBNullToInteger(reader["ID"], 0);
SpRetrunValue.ExternalData.Add(intExtData);
}
//get the return code on the SP 0 for success -1 for error
SpRetrunValue.ResultCode = (int)returnValueParam.Value;
}
the problem I am having is that if I use it with command.ExecuteNonQuery(); then I can get the return value. But using as is now I can not get the return value, but I do get the result set. Is it not possible to get the return value this way? I have seen this post here on stackoverflow, but that requires me adding another param to the stored procedure, which I feel defeats the purpose of just returning a value like in my stored procedure above.
You'll have to complete processing for all rowsets before attempting to capture the Return value or OUTPUT parameters. Place SpRetrunValue.ResultCode = (int)returnValueParam.Value; after your using statement.
Understanding SQL Server Return Codes and Output Parameters
Also, this post