Multi-value date parameter in stored procedure? - c#

I'm trying to get a stored procedure to work that accepts a multi-value parameter for dates. This isn't in SSRS but I'm trying to use the same approach as I do with it:
ALTER PROCEDURE spSelectPlacementData
(
#ClientID SMALLINT,
#SourceFileDates VARCHAR(MAX)
)
AS
BEGIN
SELECT (snip)
FROM [APS].[dbo].[Account] A
WHERE ClientID = #ClientID
AND A.[SourceFileDate] IN (SELECT * FROM dbo.Split(#SourceFileDates))
END
I use this approach with INT and VARCHAR fields on SSRS report multi-value parameters.
Here is the code I'm using to concatenate the SourceFileDates:
string sourceFileDates = "";
foreach (DateTime file in job.sourceFiles)
{
if (file == job.sourceFiles.Last())
{
sourceFileDates += "'" + file.ToString("d") + "'";
}
else
{
sourceFileDates += "'" + file.ToString("d") + "', ";
}
}
selectRunCommand = new SqlCommand("spSelectPlacementData", sqlConnection);
selectRunCommand.CommandType = CommandType.StoredProcedure;
selectRunCommand.Parameters.Add("#ClientID", SqlDbType.SmallInt);
selectRunCommand.Parameters["#ClientID"].Value = job.clientID;
selectRunCommand.Parameters.Add("#SourceFileDates", SqlDbType.VarChar);
selectRunCommand.Parameters["#SourceFileDates"].Value = sourceFileDates;
Using this dbo.Split function I grabbed online:
/****** Object: UserDefinedFunction [dbo].[Split] Script Date: 09/20/2011 11:16:13 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[Split]
/* This function is used to split up multi-value parameters */
(
#ItemList VARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #IDTable TABLE (Item VARCHAR(MAX) collate database_default )
AS
BEGIN
DECLARE #tempItemList VARCHAR(MAX)
SET #tempItemList = #ItemList
DECLARE #i INT
DECLARE #Item VARCHAR(MAX)
SET #tempItemList = REPLACE (#tempItemList, #delimiter + ' ', #delimiter)
SET #i = CHARINDEX(#delimiter, #tempItemList)
WHILE (LEN(#tempItemList) > 0)
BEGIN
IF #i = 0
SET #Item = #tempItemList
ELSE
SET #Item = LEFT(#tempItemList, #i - 1)
INSERT INTO #IDTable(Item) VALUES(#Item)
IF #i = 0
SET #tempItemList = ''
ELSE
SET #tempItemList = RIGHT(#tempItemList, LEN(#tempItemList) - #i)
SET #i = CHARINDEX(#delimiter, #tempItemList)
END
RETURN
END
I guess I'm not entirely clear on what differs between how I'm formatting the parameter, how SSRS does so for similar parameters (this is the only one I've tried doing from code), and how the Date data type affects required formatting. I'm getting a "Conversion failed when converting date and/or time from character string." error when selecting more than one value.
Edit: As requested, example of foreach loop output:
'9/9/2011', '8/19/2011', '8/12/2011'

Why not use a Table-Valued parameter?
Create a user-defined table-type DateTimes on SQL
create type DateTimes as table
(
[Value] datetime
)
Then amend your stored procedure:
ALTER PROCEDURE spSelectPlacementData
(
#ClientID SMALLINT,
#SourceFileDates DateTimes readonly -- must be readonly
)
Now you can treat #SourceFileDates as a readonly table-variable.
When specifying your SqlCommand parameters, a Table-Valued parameter is specified as SqlDbType.Structured and passed as a DataTable or DataRowcollection. So, you can populate it like so:
var sourceFileDates = new DataTable();
sourceFileDates.Columns.Add("Value", typeof(DateTime));
foreach (DateTime file in job.sourceFiles)
{
sourceFileDates.Rows.Add(file);
}
selectRunCommand.Parameters.Add(new SqlParameter {
ParameterName = "#SourceFileDates",
Value = sourceFileDates,
SqlDbType = SqlDbType.Structured // make sure you specify structured
});
Now everything is nice and properly typed... and you don't have to do any string parsing or casting.
As a side-note, you might as well go ahead and create Strings and Integers types as well; You'll get hooked on TVPs and use them all over the place.

SSRS cheats a bit because it's controlling the inputs... it's not so concerned about SQL Injection attacks. Through a stored procedure, this would be a bit more difficult to do.
What has worked well for me when I needed to send multiple values in a single argument in 2005, I would send them as an XML string like so:
<dates>
<date>2011-01-23</date>
<date>2011-02-24</date>
</dates>
and then treat this as a table in the function:
select
x.a.value('.', 'datetime') as myDate
from
#XMLArg.nodes('/dates/date') x(a);
now you should have your data as table-valued. (syntax may be a little off, this is off the top of my head)

Related

Use only one petition to server instead multiple

I have a list of CheckBoxes:
List<CheckBox> checkBoxes = new List<CheckBox>();
I want to update it via a stored procedure, so I have:
private void btnSave_Click(object sender, EventArgs e)
{
SQLConnMgr db = new SQLConnMgr();
foreach (var c in checkBoxes)
{
db.ExeSQL($"exec test #CheckBoxName = {c.Name}, #CheckBoxValue = {c.Checked} ");
}
}
Stored procedure:
CREATE OR ALTER PROCEDURE test
-- Add the parameters for the stored procedure here
#CheckBoxName VARCHAR(255),
#CheckBoxValue BIT
AS
BEGIN
SET NOCOUNT ON;
-- Insert statements for procedure here
UPDATE MyTable SET #CheckBoxName = #CheckBoxValue
END
My question is: is there another way to do this? Like sending multiple petitions in the foreach statement instead of only one at a time?
UPDATE
So to be more clear every bool is a column so I need something like:
DECLARE #CurrentCheckboxName VARCHAR(255) = (SELECT
[CheckBoxName]
FROM #CheckBoxList)
UPDATE [m]
SET
#CurrentCheckboxName = [c].[CheckBoxValue]
FROM [RedMarkItems] [m]
JOIN #CheckBoxList [c] ON [c].[CheckBoxName] = #CurrentCheckboxName
but how can iterate on each checkboxName in my DECLARE?
Depending on the version of SQL Server you are using, you could use a TABLE parameter type for your stored proc and call it only once.
CREATE TYPE dbo.MyCheckBoxValues AS TABLE(
CheckBoxName VARCHAR(255) NOT NULL,
CheckBoxValue BIT NOT NULL )
Then you modify your stored proc to use the type.
CREATE OR ALTER PROCEDURE test
-- Add the parameters for the stored procedure here
#CheckBoxList MyCheckBoxValues READONLY
AS
BEGIN
SET NOCOUNT ON;
-- Insert statements for procedure here
UPDATE m SET CheckBoxValue=c.CheckBoxValue
FROM MyTable m
JOIN #CheckBoxList c ON c.CheckBoxName=m.CheckBoxName
END
You can also use Dynamic SQL in your stored proc. For Each checkboxValues :
DECLARE #Query nvarchar(max);
SET #Query = 'UPDATE Table SET ' + #CheckboxName + '='+ #CheckBoxValue;
exec sp_executeSql #Query
Then you only have to get the values in your code.
Something like this should do it.
StringBuilder builder = new StringBuilder();
builder.Append("DECLARE #MyCheckboxes MyCheckBoxValues; ");
foreach (Guid id in _equipmentToMerge)
{
builder.Append(String.Format("INSERT INTO #MyCheckboxes (CheckBoxName, CheckBoxValue) VALUES ('{0}',{1}); ", name, ischecked));
}
builder.Append("exec dbo.test #MyCheckboxes ");
I think there is a problem with your Update statement in your stored procedure. Could you change it thusly?
Update MyCheckBoxValues SET CheckBoxValue = #CheckBoxValue Where CheckBoxName = #CheckBoxName
I don't know why you want to complicate things. Use a single connection for the loop and I can't imagine that you could have a prohibitive number of check boxes on your form. If it is still too slow, get rid of entity framework or whatever orm you are using and try dealing with the server directly.

Unable to create dynamic sql query in asp.net C#

I am keep getting
Conversion failed when converting the varchar value '46434,15864' to data type int.
I have this texbox which accepts numeric and commas. I need to create query with emp_num in (46434,15864) like syntax.
The query generated from codebehind is this, which runs fine manually in sql server:
SELECT * -- column names
FROM [DBO].[tablename] LPR
WHERE LPR.[EMPLOYEE_NUMBER] in (46434,15864)
code:
if (txtEmpNum.Text.Trim() != "")
{
////sb.Append(" and LPR.[EMPLOYEE_NUMBER] like '%'+ #empnumber + '%' ");
sb.Append(" and LPR.[EMPLOYEE_NUMBER] in (#empnumber) ");
cmd.Parameters.Add("#empnumber", SqlDbType.VarChar).Value = txtEmpNum.Text.Trim(); //.Replace("," , "','");
}
cmd.CommandText = sb.ToString();
DataTable dt = GetData(cmd);
gvdetails.DataSource = dt;
gvdetails.DataBind();
Table:
You will have to parametrize every value. This way you'll have dynamically created query, but NOT prone to SQL Inject
Here's code:
//where IN part of your query
string inClause = "and LPR.[EMPLOYEE_NUMBER] in ({0})";
// string arrays for values and variables of your query
string[] paramValues = txtEmpNum.Text.Trim().Split(',');
string[] paramVars = paramValues.Select((s, i) => "#empNo" + i.ToString()).ToArray();
//create query, ie. and LPR.[EMPLOYEE_NUMBER] in (#empNo0, #empNo1...)
inClause = string.Format(inClause, string.Join(", ", paramVars));
//add vars and values to command
for (int i = 0; i < paramVars.Length; i++)
{
cmd.Parameters.Add(paramVars[i], SqlDbType.Int).Value = paramValues[i];
}
You need a split function to create a list from an string. You can create that function running this script once :
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[fnSplitString]
(
#string NVARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #output TABLE(splitdata NVARCHAR(MAX)
)
BEGIN
set #delimiter = coalesce(#delimiter, dbo.cSeparador());
DECLARE #start INT, #end INT
SELECT #start = 1, #end = CHARINDEX(#delimiter, #string)
WHILE #start < LEN(#string) + 1 BEGIN
IF #end = 0
SET #end = LEN(#string) + 1
INSERT INTO #output (splitdata)
VALUES(SUBSTRING(#string, #start, #end - #start))
SET #start = #end + 1
SET #end = CHARINDEX(#delimiter, #string, #start)
END
RETURN
END
Now your query will be :
SELECT *
FROM [DBO].[tablename] LPR
WHERE LPR.[EMPLOYEE_NUMBER] in (select * from fnSplitString(#empnumber, ','))
You can call it from C# exactly the same way you called your original code.
Basically the error is saying that your variable #empnumber is varchar and LPR.[EMPLOYEE_NUMBER] is integer
You can just append the value of your textbox to your query.
Edit: As others have suggested, this would be prone to SQL Injection. Marc Guillot and Nino solutions are better.

Bulk Update stored procedure with user input (WITHOUT A DATATABLE)

Can I perform a bulk update using a stored procedure that sends data to a temp table from user input which is not in a datatable.
If I have a foreach loop that takes user input such as values from a checkboxlist and text boxes what I want to know is how to parameterize those separate values in my stored procedure or if I can do it in my code. I cannot use table-valued parameters since I'm using a version of SQL that does not support it.
conn.Open();
foreach(ListItem item in CheckBoxList1.Items)
{
if(item.Selected)
{
//handling parameters in loop.
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "Update_Account_Table";
cmd.Parameters["#SeqNum"].Value = amount.Text;
cmd.Parameters["#SeqDate"].Value = DateTime.ParseExact(datepicker.Text, "mmddyyyy", CultureInfo.InvariantCulture);
cmd.Parameters["#Account_ID"].CheckBoxList1.SelectedValue;
cmd.ExecuteNonQuery();
}
conn.Close();
}
Stored procedure
CREATE TABLE TempTable
(
SeqNum int,
SeqDate datetime,
Account_ID varchar(2)
);
CREATE PROCEDURE [ACCOUNTTABLE_UPDATE]
AS
SET NOCOUNT ON
BEGIN
UPDATE AccountTable
SET SeqNum = t.SeqNum, SeqDate = t.SeqDate
FROM AccountTable AT
INNER JOIN TempTable t ON AT.AccountID = t.AccountID
END
This uses a dynamic table name in stored procedure, and sets value based on incoming parameter. You could use code below instead of temp table, or use parameters #seqnum, #SeqDate, #AcctID in place of #Stats_Value below:
USE [EODData]
GO
/****** Object: StoredProcedure [dbo].[erase_Stats] Script Date: 8/23/2016 4:32:55 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[erase_Stats]
#table varchar(25), #stats_value int
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
Declare #ProductsSQL nvarchar(max);
SET #ProductsSQL = 'update ' + #table + ' set stats_completed = #Stats_Value'
exec sp_executesql #ProductsSQL
END
GO
you can add # before the table to make it temporary table
your should be like
CREATE TABLE #TempTable
will create temporary table and destroyed after process completes

How to process an integer list row by row sent to a procedure in SQL Server 2008 R2?

I have an ArrayList (in C#) that contains some int numbers (those are IDs in a table), I want to select some data for each number(s) in this ArrayList and return a table variable or a #temporary table :)
I found a solution for passing this ArrayList as an user-defined table type to my stored procedure:
CREATE TYPE [dbo].[integer_list_tbltype] AS TABLE(
[n] [int] NOT NULL,
PRIMARY KEY CLUSTERED ([n] ASC)
WITH (IGNORE_DUP_KEY = OFF)
)
GO
CREATE PROCEDURE [dbo].[Sp_apr_get_apraisors]
(#listNumbers INTEGER_LIST_TBLTYPE readonly)
AS
....
but I didn't find an efficient way to read this array as easily as in C# :(
Is there any way to write a loop for each of these numbers and save data in a temp table and finally return it to C#??
SQL is set based, so your best option is to write a single select statement that would join your input table to the tables containing the data you would like to look up. The select statement would be the result set to be sent back to your application. Then if you want to use straight ADO.Net, you can use the SqlDataReader class to read back into C#, or you could use an ORM like Linq2Sql, Entity Framework, or NHibernate. By the way, if you must do a loop in Sql, please avoid cursors. They are slow and unnecessarily complicated both to manage and to develop. Use a while loop instead.
I would suggest you change the procedure parameter to varchar(n) and then send in those values as comma-delimited string.
DECLARE #IDs VARCHAR(MAX)
SELECT #IDs = '1,2,3'
DECLARE #ID INT
WHILE LEN(#IDs) > 0
BEGIN
SELECT #ID = CONVERT(INT, LEFT(#IDs, CHARINDEX(',', #IDs + ',') -1)
-- Do something with the ID here...
SELECT #IDs = STUFF(#IDs, 1, CHARINDEX(',', #IDs + ','), '')
END
mmmmm :), after 24h (!) search aorund the www , i found my problem Answer, #Toni's answer helped me on this :) Tanx #Toni :*
1) first define stored procedure
CREATE PROCEDURE [spName]( #list_entry VARCHAR(max)=NULL)
AS
BEGIN
SELECT [Column1,column2,...]
FROM [TABLE(s)]
WHERE ( #list_entry IS NULL
OR Column1 IN (SELECT value FROM Fn_split(#list_person, ',')) )
END
2) write a function to split items (comma delimited)
CREATE FUNCTION [dbo].[fn_Split](#text varchar(8000), #delimiter varchar(20) = ' ')
RETURNS #Strings TABLE
(
position int IDENTITY PRIMARY KEY,
value varchar(8000)
)
AS
BEGIN
DECLARE #index int
SET #index = -1
WHILE (LEN(#text) > 0)
BEGIN
SET #index = CHARINDEX(#delimiter , #text)
IF (#index = 0) AND (LEN(#text) > 0)
BEGIN
INSERT INTO #Strings VALUES (#text)
BREAK
END
IF (#index > 1)
BEGIN
INSERT INTO #Strings VALUES (LEFT(#text, #index - 1))
SET #text = RIGHT(#text, (LEN(#text) - #index))
END
ELSE
SET #text = RIGHT(#text, (LEN(#text) - #index))
END
RETURN
END
GO
3) pass my array as a comma-delimited string from .NET
//defin sample array list or your data
ArrayList array = new ArrayList();
//fill array with some data
for (int i = 1000; i<1010;i++)
array.Add(i);
//define connection and command
using(SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["connString"].ConnectionString))
{
connection.Open();
SqlCommand cmd = new SqlCommand("",connection);
cmd.Parameters.AddWithValue("#list_entry", SqlDbType.varchar,8000,Get_comma_delimited_string(array));
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "yourSpName";
cmd.ExecuteNonQuery();
}
/// <summary>
/// Resturns a comma delimited string (sepearte each item in list with ',' )
/// </summary>
public string Get_comma_delimited_string(ArrayList arrayList)
{
string result = string.Empty;
foreach (object item in arrayList)
result += item.ToString() + ",";
return result.Remove(result.Length - 1);
}

how to set return type of a stored procedure according to a specific table

I had made a dynamic stored procedure like this
CREATE PROCEDURE [dbo].[MyProcedure]
#pSelect nvarchar(max)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL nvarchar(max)
SET #SQL = 'select ' + #pSelect + ' from tabel1';
EXEC (#SQL)
END
And on updating my entitydatamodel the in context.cs the above stored procedure is in the form of
virtual int MyProcedure(string pSelect)
{
var pSelectParameter = pSelect != null ?
new ObjectParameter("pSelect", pSelect) :
new ObjectParameter("pSelect", typeof(string));
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction("MyProcedure", pSelectParameter);
}
on calling the stored procedure from c# code
var result = myDataModel.MyProcedure("Select * From table1").tolist();
the above code is showing error because MyProcedure is returning a integer return type
so how could i set the return type of the stored procedure according to tje select query I am passing to it
HOW DO I MODIFY MY STORED PROCEDURE SO THAT ITS RETURN TYPE IS OF ANY SPECIFIC TABLE TYPE
In this case you have to trick the code.
CREATE PROCEDURE [dbo].[MyProcedure]
#pSelect nvarchar(max)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SQL nvarchar(max)
SET #SQL = 'select ' + #pSelect + ' from tabel1';
EXEC (#SQL)
--Remove the below line once you have added the stored procedure to the dbml file.
select * from table1
END
After creating the sp, drag and drop to the c# dbml file. then you can alter the sp by removing the line " select * from table1".
NOTE : if you dont have those columns in the table1, the direct values(any datatype) in the select statement like "select 1 as colmumn1, 'string' as colmumn2, cast('10/01/1900' as datetime) as colmumn3 from table1"
just add # sign in your parameter.
var pSelectParameter = pSelect != null ?
new ObjectParameter("#pSelect", pSelect) :
new ObjectParameter("#pSelect", typeof(string));
may be this should work and i believe your are passing only column name in this parameter.

Categories

Resources