I have a stored procedure as follows
create procedure [dbo].[PriceConfirm]
#quote float,
#membershipType int,
#promocode nvarchar(20),
#giftCertificateCode nvarchar(20)
as
if(LEN(#giftCertificateCode)>1)
begin
declare #giftType int
select #giftType = MembershipType from GiftCertificate where GiftCertificateCode=#giftCertificateCode
if #giftType = #membershipType
begin
select 1 as result
end
else
begin
select 0 as result
end
end
else
begin
declare #total float
select #total = Price from MembershipType where TypeID=#membershipType
declare #discount float
select #discount = 0
if(LEN(#promocode)>1)
begin
select #discount = DiscountAmount from Membership_Promo where Promocode=#promocode and MembershipType = #membershipType
end
else
begin
select #discount=0
end
if ABS(#total-#discount-#quote) <.01
begin
select 1 as result
end
else
begin
select 0 as result
end
end
And if I just execute the stored procedure in SQL Server Management Studio, it works.
exec PriceConfirm #quote=69.99, #membershipType=6, #promocode='1', #giftCertificateCode='1'
That returns 1, as it should.
But in C#, when I try to pass in the parameters #quote, #membershipType, #promocode, #giftCertificateCode with the exact same values, I get an exception in the code. It reads '69.99' is out of range.
In my table and in the stored procedures, I have the columns as floats. I just don't understand why passing in a C# double is giving me a precision error. Can anybody advise?
Edit:
Here's the C# code:
IDataAccess dataAccess = _dataAccessService.GetDataAccess();
IDataConnection connection = _dataAccessService.GetConnection(Connectionstring);
var operation = new DataOperation("PriceConfirm");
operation.Parameters.Add(new DataParameter("#quote", DbType.Double, quote));
operation.Parameters.Add(new DataParameter("#membershipType", DbType.Int32, membership));
operation.Parameters.Add(new DataParameter("#promocode", DbType.String, promocode));
operation.Parameters.Add(new DataParameter("#giftCertificateCode", DbType.String, giftcode));
IResultSet reader = dataAccess.ExecuteResultSet(connection, operation, ResultSetType.Reader);
Reader is null after it tries to execute the operation. It throws an exception saying that "Data Parameter '69.99' is out of range."
OK you should NOT ever have numerics stored as floats if you do math calculations on them. You should fix your tables before doing anything else. Floats are exdtremely poor to use as they are inexact, always.
In your sp (which should not begin with sp_ BTW - that is for system procs and SQL Server will check the master datbase first every time it is run which is a waste of processing time)
Your sp does not accept float, it is defined as a decimal(4,2). Run Profiler and see what the C# code is trying to send to the database. I'll bet it is sending more than 2 decimal places.
It turns out changing the type to Decimal(18,4) does the trick. Apparently float types in SQL Server 2008 are kind of buggy. It would have worked sooner for me if I had remembered to change the DbType to Decimal in the C# code.
Related
I'm running a stored procedure inside a foreach loop in c#. After completion, all the rows of a column are getting updated with the top most value. Below is the stored procedure:
ALTER PROCEDURE [dbo].[getservername8]
#number varchar(255)
AS
DECLARE #server_name varchar(500)
SELECT #server_name = short_description
FROM [Event_alerts].[dbo].[event_alerts]
DECLARE #s AS varchar(50)
SELECT #s = SUBSTRING(#server_name, CHARINDEX('-', #server_name) + 15, 50)
UPDATE event_alerts
SET server_name = #s
WHERE number = #number
This is the C# code:
using (SqlCommand command2 = new SqlCommand("getservername8", conn))
{
command2.CommandType = CommandType.StoredProcedure;
command2.Parameters.AddWithValue("#number",number);
command2.Parameters["#number"].Direction = ParameterDirection.Input;
command2.ExecuteNonQuery();
}
Any help would be much appreciated
The culprit is because you are getting the same value of #server_name for each call. So #s si also same for all. that is why you are inserting the same value in the column
select #server_name = short_description from [Event_alerts].[dbo].[event_alerts]
declare #s as varchar(50)
select #s= SUBSTRING(#server_name, CHARINDEX('-', #server_name) + 15, 50)
Your code has a valid where clause, so it should only be updating the matching rows. I can only speculate that all rows have the same value for number.
However, you seem to have an error in the definition of #server_name -- and that might be the problem you are referring to. There is no where clause so it is set to an arbitrary value -- possibly from what you would call "the last row". Although that nomenclature is a misinterpretation of what happens.
Your SP is too complex anyway. I suspect that you intend:
alter procedure [dbo].[getservername8] (
#number varchar(255)
) as
begin
update ea
set server_name = substring(short_description, charindex('-', short_description) + 15, 50)
from event_alerts ea
where number = #number;
end; -- getservername8
Also note the good programming practices:
The statements end in a semicolon.
The body of the SP uses begin/end.
The end is tagged with the name of the SP.
I am trying to call a stored procedure using C# EF6 to bring back data. I have tried to run the stored procedure in SQL management studio and it seems to work fine, however when I try to run it in my application I get an error saying "Must declare the scalar variable "#devID"
Here is part of my method in my application calling the stored procedure
public IHttpActionResult GetMetrics(int deviceID, string attribute, string startDate)
{
if (deviceID == 0)
{
return NotFound();
}
var metrics = db.Database.SqlQuery<Metrics>("GetMetrics #devID, #MetricType, #startTime", deviceID, attribute, startDate).ToList();
and here is my stored procedure:
ALTER PROCEDURE [dbo].[GetMetrics]
-- Add the parameters for the stored procedure here
#devID int,
#MetricType nvarchar(20),
#startTime nvarchar(50)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
SELECT *
FROM dbMetrics
WHERE deviceID = #devID and MetricType = #MetricType and timeStamp >= #startTime
ORDER BY timeStamp
END
As per the documentation, if you want to use named parameters, you need to pass SqlParameter objects like this:
var metrics = db.Database.SqlQuery<Metrics>("GetMetrics #devID, #MetricType, #startTime",
new SqlParameter("devID", deviceID),
new SqlParameter("MetricType", attribute),
new SqlParameter("startTime", startDate)
).ToList();
alter procedure [dbo].[XXX]
(
#vendorworksationID uniqueidentifier ,
#sdate date,
#edate date,
#total int out
)
begin
select #total = COUNT(*)
from AdvertisedCampaignHistory a
where
CAST(a.CreationDate AS DATE) BETWEEN CAST(#sdate as DATE) AND CAST(#edate as DATE)
and a.CampaignID in (select cc.BCampaignID
from BeaconCampaign cc, VendorWorkStation vw
where cc.VendorWorkStationID = vw.VendorWorkStationID
and VendorID = #vendorworksationID)
return #total
end
The above code shows the stored procedure that return an integer value from SQL Server
ObjectParameter Output = new ObjectParameter("total", typeof(Int32));
var resBC = this.Context.getTotalSentBeaconCampaign(VendorWorkstationID, sdate,edate,Output).FirstOrDefault();
The above code shows how I am passing parameters and retrieving the value on the C# side
While running the code I am getting following error
The data reader returned by the store data provider does not have
enough columns for the query requested.
What could be the possible cause for this error?
Entity Framework cannot support Stored Procedure Return scalar values out of the box.To get this to work with Entity Framework, you need to use "Select" instead of "Return" to return back the value.
More Ref : http://www.devtoolshed.com/using-stored-procedures-entity-framework-scalar-return-values
I have a oracle stored procedure which updates a table with the following statement.
update boxes
set location = 'some value'
where boxid = passed value
I have a page where the user selects 100+ boxes and updates them with a new location value. Currently, I have to call the stored procedure 100+ times to update each box(by passing a boxid each time).
I want to know how I can pass a list of boxids from C# into the stored procedure so that I have to call the stored procedure just one time.
I am hoping to use a where in(boxids) kind of where clause in the update statement.
Please let know how can I achieve this. Thanks in advance!
Oracle allows you to pass arrays of values as parameters. Borrowing from this SO question and this one you can define an INT_ARRAY type like this:
create or replace type CHAR_ARRAY as table of INTEGER;
Then define your stored procedure as:
CREATE OR REPLACE PROCEDURE product_search(
...
myIds IN CHAR_ARRAY,
...)
AS
SELECT ...
...
WHERE SomeIdField IN (Select column_value FROM TABLE(myIds))
...
You can then pass the list of values by setting the OracleParameter.CollectionType property like this:
OracleParameter param = new OracleParameter();
param.OracleDbType = OracleDbType.Int32;
param.CollectionType = OracleCollectionType.PLSQLAssociativeArray;
I'd create a new procedure, designed to handle a list of values. An efficient approach would be to load the multiple values into a global temp table, using a bulk insert, and then have the procedure update using a join to the GTT.
A notional example would look like this:
OracleTransaction trans = conn.BeginTransaction(IsolationLevel.RepeatableRead);
OracleCommand cmd = new OracleCommand(insertSql, conn, trans);
cmd.Parameters.Add(new OracleParameter("BOX_ID", OracleDbType.Number));
cmd.Parameters[0].Value = listOfBoxIds; // int[] listOfBoxIds;
cmd.ExecuteArray();
OracleCommand cmd2 = new OracleCommand(storedProc, conn, trans);
cmd2.CommandType = CommandType.StoredProcedure;
cmd2.ExecuteNonQuery();
trans.Commit();
Your PL/SQL block may look like this one:
CREATE OR REPLACE PACKAGE YOUR_PACKAGE AS
TYPE TArrayOfNumber IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
PROCEDURE Update_Boxes(boxes IN TArrayOfNumber );
END YOUR_PACKAGE;
/
CREATE OR REPLACE PACKAGE BODY YOUR_PACKAGE AS
PROCEDURE Update_Boxes(boxes IN TArrayOfNumber) is
BEGIN
FORALL i IN INDICES OF boxes
update boxes
set location = boxes(i)
where boxid = ...;
END Update_Boxes;
END YOUR_PACKAGE;
The C# code you get already in answer from Panagiotis Kanavos
I understand your concern - the round trips will be taxing.
Unfortunately I don't have anything to test, but you can try
Oracle bulk updates using ODP.NET
or
-- edit1: go with Panagiotis Kanavos's answer if your provider supports it, else check below --
-- edit12 as highlighted by Wernfried, long is deprecated. Another thing consider is max length varchar2: it doesn't scale on a very big set. Use the one below as the last resort. --
changing your stored procedure to accept string
implement string_2_list in asktom.oracle.com.
create or replace type myTableType as table of varchar2 (255);
create or replace function in_list( p_string in varchar2 ) return myTableType
as
l_string long default p_string || ',';
l_data myTableType := myTableType();
n number;
begin
loop
exit when l_string is null;
n := instr( l_string, ',' );
l_data.extend;
l_data(l_data.count) :=
ltrim( rtrim( substr( l_string, 1, n-1 ) ) );
l_string := substr( l_string, n+1 );
end loop;
return l_data;
end;
Above is early variant and splice to varchar2, but if you read more (including other threads) at that site
you'll find more advanced variants (optimized, better exception handling)
ALTER PROCEDURE [dbo].[SelectCompletionNonCompletionCourseReport]
#LearnerName NVARCHAR(510) = NULL,
#ManagerId INT = NULL,
#CourseId INT = NULL,
#StartDateFrom SMALLDATETIME = NULL,
#StartDateTo SMALLDATETIME = NULL,
#TeamList XML = NULL,
#JobID NVARCHAR(max)=NULL,
#CourseStatus NVARCHAR(20)=NULL,
#ReportAdminID INT=0,
#ReportTeamList NVARCHAR(max)=NULL,
#RowsTotal int = 0,
#PageIndex int = 1,
#RowsPerPage int = 10
AS
BEGIN
DECLARE #TblCrieiria TABLE
(
id INT IDENTITY(1, 1),
areacode NVARCHAR(11),
regioncode NVARCHAR(11),
teamcode NVARCHAR(11)
)
IF #TeamList IS NULL
BEGIN
INSERT INTO #TblCrieiria VALUES(NULL,NULL,NULL)
END
BEGIN
This is the beginning of the procedure...
using (Database db = new Database(DScape.DAL.Config.ConfignPropertyName.DSCAPELMS_CONNECTION_STRING_NAME))
{
var cmd = new SqlCommand
{
CommandText = "SelectCompletionNonCompletionCourseReport",
CommandType = CommandType.StoredProcedure
};
cmd.Parameters.AddWithValue("#LearnerName", LearnerName);
cmd.Parameters.AddWithValue("#ManagerId", ManagerId);
cmd.Parameters.AddWithValue("#CourseId", CourseId);
cmd.Parameters.AddWithValue("#StartDateFrom", StartDateFrom);
cmd.Parameters.AddWithValue("#StartDateTo", StartDateTo);
cmd.Parameters.AddWithValue("#TeamList", TeamList);
cmd.Parameters.AddWithValue("#JobID", JobID);
cmd.Parameters.AddWithValue("#CourseStatus", CourseStatus);
cmd.Parameters.AddWithValue("#ReportAdminID", ReportAdminID);
cmd.Parameters.AddWithValue("#ReportTeamList", ReportTeamList);
cmd.Parameters.AddWithValue("#PageIndex", 1);
DataSet dsClient = db.GetDataSet(cmd);
if (dsClient.Tables.Count > 0)
return dsClient.Tables[0];
else
return null;
}
This is the method which communicates with the procedure, and it gaves me an error
Parameter does not exist as a stored procedure parameter/ function/procedure take too many arguments...
It's about #PageIndex parameter. Doesn't matter what is the value, we don't talk for values here but for parameter which is defined in the stored procedure but doesn't work?
And for the record, this problem did pop-up today w/o any code writing/modifying just appeared as I tried to do that report, when yesterday it was all good...I have a teammate which is next to me with absolute the same code both in sql and c# and it works just fine on his pc, but mine throws this errors, I'm trying to resolve this from 3 hours and I am completely out of answers , so please give me direction in which should I continue to resolve this .....................
and I say again, the problem is not from the connection to DB or type of the parameter or the value, the error is committed with the parameter itself - does not exist in the procedure, which is insane in my opinion.
Given that all parameters are optional, you are not required to explicitly provide any of them from your client code. Default values will be provided for you by SQL Server. The contract explictly states it in the stored procedure's signature.
An optional parameter is exactly that: optional. If you had provided the incorrect number of parameters, SQL Server would have returned a different error, indicating that the number of parameters was incorrect. This is not the case. Instead, you are seeing that you are asking for a parameter that is undefined, which indicates that the stored procedure signature you think you are calling does not match the stored procedure signature you are actually calling.
Verify that you are both connecting to the same database instance. If you are not, verify that the stored procedure is identical on both database instances.
parameter count doesnt match. check the params again.
You have to send parameters for rowstotal and rowsperpage as well because you have declared them at the top before "begin" clause.
If you do not want to send that params and they will be just constant, please declare them below as variable or constant, not a parameter.
i.e.
CREATE PROCEDURE DeleteById
#TableName sysname,
#Id int
AS
BEGIN
DECLARE #StrId AS VARCHAR(50)
SET #StrId = CONVERT(VARCHAR(50), #Id)
--any sp code here
END
Hope this helps.