Build SQL query string using user input - c#

I have to make a string by using the values which the user selects on the webpage,
Suppose I need to display files for multiple machines with different search criteria...
I currently use this code:
DataTable dt = new DataTable();
SqlConnection connection = new SqlConnection();
connection.ConnectionString = ConfigurationManager
.ConnectionStrings["DBConnectionString"].ConnectionString;
connection.Open();
SqlCommand sqlCmd = new SqlCommand
("SELECT FileID FROM Files
WHERE MachineID=#machineID and date= #date", connection);
SqlDataAdapter sqlDa = new SqlDataAdapter(sqlCmd);
sqlCmd.Parameters.AddWithValue("#machineID", machineID);
sqlCmd.Parameters.AddWithValue("#date", date);
sqlDa.Fill(dt);
Now this is a fixed query where the user just has one machine and just selects one date...
I want to make a query in which the user has multiple search options like type or size if he/she wants depending on what he/she selects.
Also if he/she can select multiple machines...
SELECT FileID FROM Files
WHERE (MachineID=#machineID1 or MachineID = #machineID2...)
AND (date= #date and size=#size and type=#type... )
All of this happens at runtime... otherwise I have to create a for loop to put multiple machines one by one... and have multiple queries depending on the case the user selected...
This is quite interesting and I could use some help...

If you are going to do this via dynamic SQL, you need to build a call to the IN function. (e.g. In(id1, id2, id3...)
private string GetSql( IList<int> machineIds )
{
var sql = new StringBuilder( "SELECT FileID FROM Files Where MachineID In(" );
for( var i = 0; i < machineIds.Count; i++ )
{
if ( i > 0 )
sql.Append(", ")
sql.Append("#MachineId{0}", i);
}
sql.Append(" ) ");
//additional parameters to query
sql.AppendLine(" And Col1 = #Col1" );
sql.AppendLine(" And Col2 = #Col2 ");
...
return sql.ToString();
}
private DataTable GetData( IList<int> machineIds, string col1, int col2... )
{
var dt = new DataTable();
var sql = GetSql( machineIds );
using ( var conn = new SqlConnection() )
{
conn.ConnectionString = ConfigurationManager.ConnectionStrings["DBConnectionString"].ConnectionString;
using ( var cmd = new SqlCommand( sql, conn ) )
{
conn.Open();
for( var i = 0; i < machineIds.Count; i++ )
{
var parameterName = string.Format("#MachineId{0}", i );
cmd.Parameters.AddWithValue( parameterName, machineIds[i] );
}
cmd.Parameters.AddWithValue( "#Col1", col1 );
cmd.Parameters.AddWithValue( "#Col2", col2 );
...
using ( var da = new SqlDataAdapter( cmd ) )
{
da.Fill( dt );
}
}
}
return dt;
}

You can use WHERE MachineID IN ('Machine1', 'Machine2', 'Machine3', ... 'MachineN')
Then in your loop you would just add the 1..n machines. The IN clause works with 1 element or n elements, so it should be fine.
However, I'd look at using a stored procedure to do it rather than hardcoding the SQL into your application.

Build a real table and load the machine ids into it.
Then your SQL would be:
where MachineID in ( select MachineID from userMachine where userID = x)
When you are done, remove all rows for the userID:
delete from userMachine where userID = x.

Normally when I want to create a "search" type query, I use optional parameters. This allows me to send something or nothing to the parameter, making the query go from vague to very specific.
Example:
SELECT
COL1,
COL2,
COL3
FROM TABLE
WHERE (#COL1 IS NULL OR #COL1 = '' OR #COL1 = COL1)
As you'll notice above, if you pass in NULL or BLANK it won't add the parameter to the query. If you do enter a value, then it'll be used in the comparison.

Ideally you are trying to arrive at a solution similar to creating "MachineID in (1, 2, 3, 4)" dynamically.
Option 1
There are many ways to complete this task from passing in a comma separated string into the stored proc and dynamically build the sql string and then calling "EXEC sp_executesql #sql"
WHERE IN (array of IDs)
Option 2
You can pass in a string of comma separated values and then parse out the values into their own temp-table and then join on to it
http://vyaskn.tripod.com/passing_arrays_to_stored_procedures.htm
Option 3 - my choice
You can now pass in the array of values using XML and then select the array items easily.
http://support.microsoft.com/kb/555266
.

I would also recommend using a stored procedure because otherwise you will leave yourself open to an SQL injection attack - especially where you are building up a string based on user input.
Something like:
a' or 1=1; -- Do bad things
You can use sp_executesql in SQL to run an SQL statement that is built up with a where clause like #dcp suggests and although it wouldn't optimize well it is probably a quick command to run anyway.
SQL Injection attacks by example
One way to achieve this would be using charindex. This example demonstrates how a stored procedure could be run when passed a space separated list of ids:
declare #machine table (machineId int, machineName varchar(20))
declare #files table (fileId int, machineId int)
insert into #machine (machineId, machineName) values (1, 'machine')
insert into #machine (machineId, machineName) values (2, 'machine 2.0')
insert into #machine (machineId, machineName) values (3, 'third machine')
insert into #machine (machineId, machineName) values (4, 'machine goes forth')
insert into #machine (machineId, machineName) values (5, 'machine V')
insert into #files (fileId, machineId) values (1, 3)
insert into #files (fileId, machineId) values (2, 3)
insert into #files (fileId, machineId) values (3, 2)
insert into #files (fileId, machineId) values (4, 1)
insert into #files (fileId, machineId) values (5, 3)
insert into #files (fileId, machineId) values (6, 5)
declare #machineText1 varchar(100)
declare #machineText2 varchar(100)
declare #machineText3 varchar(100)
set #machineText1 = '1 3 4'
set #machineText2 = '1'
set #machineText3 = '5 6'
select * from #files where charindex(rtrim(machineId), #machineText1, 1) > 0
-- returns files 1, 2, 4 and 5
select * from #files where charindex(rtrim(machineId), #machineText2, 1) > 0
-- returns file 4
select * from #files where charindex(rtrim(machineId), #machineText3, 1) > 0
--returns file 6
So you can create this stored procedure to achieve your aim:
create procedure FilesForMachines (#machineIds varchar(1000))
as
select * from [Files] where charindex(rtrim(machineId), #machineIds, 1) > 0
The charindex tip is from BugSplat.

Related

Must declare table variable error with c# paramaterized query

I am getting an error with an SQL statement:
must declare the table variable #table
I don't know how I should fix it. Here is the code for the part that makes the query (for example 3 is Communications, 4 is Radio:basic, 5 is 45, 6 is 5, and 7 is 0).
string table = "Skills" + info[3];
SqlCommand cmd = new SqlCommand("use palladium; insert into #category(skill, basepercent, lvlpercent, special) values ( #skillname , #base , #lvl , #special);");
cmd.Parameters.AddWithValue("#category", table);
cmd.Parameters.AddWithValue("#skillname", info[4]);
cmd.Parameters.AddWithValue("#base", info[5]);
cmd.Parameters.AddWithValue("#lvl", info[6]);
cmd.Parameters.AddWithValue("#special", info[7]);
general.dbsend(cmd, connection);
You can't have a table name as a variable in a static query. You can in a dynamic query, by formatting the table name in the query rather than supplying it as a parameter.
SqlCommand cmd = new SqlCommand("use palladium; insert into ["+table.Replace("]","]]")+"](skill, basepercent, lvlpercent, special) values ( #skillname , #base , #lvl , #special);");
The replace bit guards against SQL Injection.

Use values from int[] in SQL statement in C#

I have the following SQL statement that I want to run:
string sql = #"DECLARE #a udt;" +
"INSERT INTO #a (id) VALUES #params;";
I have the following array of integers:
int[] array1 = {10,20,30,40,50,60};
It is important to not that the above array size is dynamic in my program.
I would like to add the values from the array to the #params in the SQL statement so that the SQL being executed in the SqlCommand looks like this:
sql = #"DECLARE #a udt;" +
"INSERT INTO #a (id) VALUES (10),(20),(30),(40),(50),(60);" +
"EXEC sp #a;";
I have tried the following amongst others and keep getting exceptions:
SqlConnection con = new SqlConnection("connectionString");
SqlCommand cmnd = new SqlCommand(sql, con);
for (int i = 0; i < array1.Count; i++)
{
cmnd.Parameters.AddWithValue("#params" , array1[i]);
}
con.open().
cmnd.ExecuteNonQuery();
May somebody please explain what I am doing wrong. I also need to make sure to prevent SQL injection.
You are close with your solution, but you need to explicitly reference each array index parameter in the sql string. eg:
List<int> tests = new List<int>() { 10, 11, 12};
var command = new MySqlCommand();
List<string> parameterNames = new List<string>();
for (int i = 0; i < tests.Count; i++)
{
string parameterName = String.Format("#test{0}", i);
command.Parameters.AddWithValue(parameterName, tests[i]);
parameterNames.Add("(" + parameterName + ")");
}
string insertSql = #"insert into test(id) values {0}";
command.CommandText = String.Format(insertSql, String.Join(", ", parameterNames));
command.Connection = connection;
int result = command.ExecuteNonQuery();
The resulting sql command text will be:
"insert into test(id) values (#test0), (#test1), (#test2)"
Join the Int array to format according to #params parameter:
int[] intArr = { 10, 20, 30 };
string intStr = string.Join(",", intArr.Select(i => "(" + i.ToString() + ")"));
intStr is in the form (10),(20),(30) which you pass to sql. Create dynamic statement and execute it.
-- declared here for sample
declare #params varchar(50) = '(10),(20),(30)'
declare #sql varchar(max) =
'declare #a table (id int)' + ';' +
'insert into #a (id) values ' + #params + ';' +
'exec sp #a'
exec (#sql)
You could use generate dynamic SQL for your values clause. This will cause some performance overhead depending on how often this code is run due to poor plan cache reuse.
I would pass the array in as a delimited string to the procedure and use a SQL string splitter to convert the string into a table. This allows you to pass in a single parameter to the procedure and possibly bypass the UDT.
There are a variety of string splitters available with a web search. Any you find that use a loop would be slow, but would work. I personally prefer Jeff Moden's inline table valued function [DelimitedSplit8K] (requires free signup, but I think it is well worth it) which is probably the fastest TSQL string splitter.
Usage with any splitter would be similar to this:
DECLARE #a udt;
INSERT INTO #a([id])
SELECT [Item]
FROM [dbo].[DelimitedSplit8K](#params)
This would convert {10,20,30,40,50,60} to a table with one value per row.
Another usage is for joining:
SELECT
[col1],
[col2],
[ItemNumber] -- position in array
FROM [dbo].[MyTable] A
INNER JOIN [dbo].[DelimitedSplit8K](#params) B
ON A.[col3] = B.[Item] -- join on array value

OUTPUT Clause value with another Data in SQL Sever : C#

I am using below query to get OUTPUT Clause value with another data.
#PageIndex bigint=1,
#PageSize bigint=5,
#RecordCount bigint=0 OUTPUT,
Select ROW_NUMBER() OVER (ORDER BY EmpId ASC) AS RowNumber,
EmpId,
(Salutation + ' ' + FirstName + ' ' + LastName) as Name,
Specialties,
EmpPhoto
INTO #Results
from tblEmployee
Where EmpType='Doctor'
Select #RecordCount = COUNT(*) from #Results
Select *
from #Results
Where RowNumber BETWEEN(#PageIndex -1) * #PageSize + 1
AND(((#PageIndex -1) * #PageSize + 1) + #PageSize) - 1
DROP TABLE #Results
Now, I want #RecordCount value with another data of Select query. How to get it in C# ? I am using 3-tier architecture so I have created method with DataTable datatype. So, what changes I will need to do ?
// 1. Create the output parameter
var p = new SqlParameter("RecordCount", 0) { Direction = ParameterDirection.Output };
// 2. Make the SQL call
using (var con = new SqlConnection(connString))
using (var cmd = con.CreateCommand())
{
cmd.Parameters.Add(p);
using (var reader = cmd.ExecuteReader())
{
// Retrieve reader values from query
}
}
// 3. Retrieve the value of the output parameter
var recordCount = (long)p;
Another option would be to refactor your SQL and do
RETURN ##ROWCOUNT
Which can be retrieved in C# with the following (note that you are limited to an int type for the return value)
cmd.Parameters["#RETURN_VALUE"]
And as an aside, avoid using TempDb like that. It's unnecessary. And a better way to page is like this using built-in SQL paging
SELECT ...
FROM ...
WHERE ...
ORDER BY ...
OFFSET (#PageIndex - 1) * #PageSize ROWS
FETCH NEXT #PageSize ROWS ONLY
Since you haven't show your c# code - it is hard to say what changes should you made.
But basically - you can't use output keyword in a script. It should be output parameter of stored procedure - so you have to wrap your logic into stored procedure and call it from c# code.
In this case you should add parameter to your SqlCommand with ParameterDirection.Output. After executing command you can get returned value of your parameter from SqlParameter.Value property.

Execute stored procedure with array-like string separated by comma [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Help with a sql search query using a comma delimitted parameter
I want to write a stored procedure that performs a select on a table and need one input variable of type varchar(max).
I'd like to send a bunch of values separated by , as the input parameter, e.g.
'Jack','Jane','Joe'
and then get the rows that contain one of these names.
In SQL the code would be
Select * from Personnel where Name in ('Jack','Joe','Jane');
Now I want to have a variable in my C# app, say strNames and fill it like
string strNames = "'Jack','Joe','Jane'";
and send this variable to the SP and execute it. Something like
Select * from Personnel where Name in (''Jack','Joe','Jane'') -- this is wrong
But how can I tell SQL Server to run such command?
I need to make this happen and I know it's possible, please give me the clue.
First of all, the single names don't need to be quoted when you pass them to the stored procedure.
using (SqlCommand cmd = new SqlCommand("MyStoredProc", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#longFilter", "Jack,Jill,Joe");
using (SqlDataReader reader = cmd.ExecuteReader())
{
...
}
}
Then, in the stored procedure, you can use simple text functions and a temporary table as follows to split up the string at the commas and an an entry to the temporary table for each part of the string:
DECLARE #temp AS TABLE (Name NVARCHAR(255))
IF ISNULL(#longFilter, '') <> ''
BEGIN
DECLARE #s NVARCHAR(max)
WHILE LEN(#longFilter) > 0
BEGIN
IF CHARINDEX(',', #longFilter) > 0
BEGIN
SET #s = LTRIM(RTRIM(SUBSTRING(#longFilter, 1, CHARINDEX(',', #longFilter) - 1)))
SET #longFilter = SUBSTRING(#longFilter, CHARINDEX(',', #longFilter) + 1, LEN(#longFilter))
END ELSE
BEGIN
SET #s = LTRIM(RTRIM(#longFilter))
SET #longFilter= ''
END
-- This was missing until 20140522
INSERT INTO #temp (Name) VALUES (#s)
END
END
Later use the following SELECT to get a list of all people the name of which is in #temp or all of them if #temp doesn't contain any rows (unfiltered result):
SELECT * FROM Personnel WHERE Name IN (SELECT Name FROM #temp) OR (SELECT COUNT(*) FROM #temp) = 0
You could use Table Valued Parameters.
Basically, you could insert a list of values as a parameter in the procedure, and use them as a table, something along the lines of
Select * from Personnel
where Name in (select name from #NamesTable).
Now, the specifics
To use table valued parameters, the type of the parameter must be predefined in sql server, using
create type NamesTable as table (Name varchar(50))
You can then use the defined type as a parameter in the procedure
create procedure getPersonnelList
#NamesTable NamesTable readonly
as
begin
select * from personnel
where Name in (select Name from #NamesTable)
end
You can see that in action, in this SQL Fiddle
On the C# side of things you need to create the parameter. If you have the names in a collection, and build the string, you can just use that to generate the parameter, and if they are a comma-separated string, a quick string.Split could take care of that. Since I do not know your specifics, I'll assume you have a List<string> called names. You'll need to convert that to a table valued parameter to be sent to the procedure, using something like:
DataTable tvparameter = new DataTable();
tvparameter.Columns.Add("Name", typeof(string));
foreach (string name in names)
{
tvparameter.Rows.Add(name);
}
You can find more info on how to generate a TVP in C# code in the SO Question..
Now you just need to send that parameter to the procedure, and that's that. Here is a complete console program that executes the procedure and outputs the results.
List<string> names = new List<string> { "Joe", "Jane", "Jack" };
using (SqlConnection cnn = new SqlConnection("..."))
{
cnn.Open();
using (SqlCommand cmd = new SqlCommand("getPersonnelList", cnn))
{
cmd.CommandType = CommandType.StoredProcedure;
DataTable tvparameter = new DataTable();
tvparameter.Columns.Add("Name", typeof(string));
foreach (string name in names)
{
tvparameter.Rows.Add(name);
}
cmd.Parameters.AddWithValue("#NamesTable", tvparameter);
using (SqlDataReader dr = cmd.ExecuteReader())
{
while (dr.Read())
{
Console.WriteLine("{0} - {1}", dr["ID"], dr["Name"]);
}
}
}
}
I guess you need Split Function in Sql Server to break Comma-Separated Strings into Table. Please refer these links.
Split Function in Sql Server to break Comma-Separated Strings into Table
SQL User Defined Function to Parse a Delimited String
You can select the data from table using
Select * from
Personnel where
Name in (select items from dbo.Split ('Jack,Joe,Jane',','))
You could simply check if Name is contained in the string. Note the commas at the start of the end to ensure you match the full name
string strNames = ",Jack,Joe,Jane,";
The the SQL becomes
select * from Personnel where PATINDEX('%,' + Name + ',%', #strNames) > 0
See http://www.sqlfiddle.com/#!3/8ee5a/1

How to insert a record and return the newly created ID using a single SqlCommand?

I'm using an SqlCommand object to insert a record into a table with an autogenerated primary key. How can I write the command text so that I get the newly created ID when I use the ExecuteScalar() method?
INSERT INTO YourTable(val1, val2, val3 ...)
VALUES(#val1, #val2, #val3...);
SELECT SCOPE_IDENTITY();
Don't forget the semicolons at the end of each statement.
Add the following line to the end of the Sql Query...
SELECT SCOPE_IDENTITY()
And then use the ExecuteScalar method on the SqlCommand object...
var rowCount = command.ExecuteScalar()
insert into Yourtable()
values()
SELECT SCOPE_IDENTITY()
I just ran a test and verified that the semi-colons are optional using SQL Server 2005 SP2, and .Net 3.5
Add an output parameter to the command object and then set the value to the new ID in the stored procedure.
Stored Procedure:
#ID AS INT OUTPUT
[Insert Command]
SET #ID = SCOPE_IDENTITY()
.NET:
cmd.CommandText = "stored_procedure";
SqlParameter pID = new SqlParameter("ID", DBType.Int32, 4);
pID.Direction = ParameterDirection.Output;
cmd.ExecuteScalar();
int id = Convert.ToInt32(cmd.Parameters["ID"].Value.ToString());
Don't use ##IDENTITY, however simple it may seem. It can return incorrect values.
SELECT SCOPE_IDENTITY()
appears to be the obvious choice.
Although I like Dave Markle's answer, ( and I see you did too, since you marked it as your answer ), that method can fail if you have triggers on your database, that audit CUD operations, and your audit table has an IDENTITY column. It would return the value of the Audit table's identity, not the table you just inserted into, since the audit table actualy happen after.
In that case, a more generic method can be used that will work in both cases, regardless of any auditing. Its a bit more wordy, but you get what you pay for.
example:
#"DECLARE #tmp AS TABLE ( id int )
INSERT INTO case
(
caseID,
partID,
serialNumber,
hardware,
software,
firmware
)
OUTPUT Inserted.ID into #tmp
VALUES
(
#caseID,
#partItemID,
#serialNumber,
#hardware,
#software,
#firmware
)
Select ID from #tmp" )
Immediately after your insert stmt, use
SELECT CAST(scope_identity() AS bigint) ---- incase you have a return result as int64
This will return the column created id/identity.
If your id is a Guid, then I found this solution to be best:
INSERT INTO YourTable (val1, val2, val3)
OUTPUT inserted.id
VALUES (#val1, #val2, #val3)
Thanks #Scott Ivey
Full demo:
internal static Guid InsertNote(Note note)
{
Guid id;
using (
var connection =
new SqlConnection(ConfigurationManager.ConnectionStrings["dbconn"].ConnectionString))
{
connection.Open();
using (
var command =
new SqlCommand(
"INSERT INTO Notes ([Title],[Text]) " +
"OUTPUT inserted.id " +
$"VALUES ('{title}','{text}');", connection))
{
command.CommandType = CommandType.Text;
var reader = command.ExecuteReader();
reader.Read();
id = reader.GetGuid(reader.GetOrdinal("id"));
}
connection.Close();
}
return id;
}
I would recommend using a Stored Procedure, but this is for unit testing our repository.
Straight out of the Whirlpool:
If you're using MS SQL you can use "SELECT ##IDENTITY as Value" after your insert to get the last ID generated
and:
##IDENTITY and SCOPE_IDENTITY return the last identity value generated in any table in the current session. However, SCOPE_IDENTITY returns the value only within the current scope; ##IDENTITY is not limited to a specific scope.
Edit: As pointed out in the comments, you should always use SCOPE_IDENTITY, not ##IDENTITY.

Categories