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
Related
All the code that I have has worked with its intended purpose a few weeks ago, although I think I have deleted something by accident and now it doesn't work right. The stored procedure is suppose to return multiple rows of data, with each row having columns TopicID, TopicName and PercentComplete.
In the JSONController / API, the data is supposed to be concatenated into a singular string where each column is separated with # while each row is separated with |.
Example:
"4#Ideology#50|5#Morality#100|6#Religion#0"
What I notice now is that the stored procedure only returns the last row, so in this example case "6#Religion#0".
Am I missing code, or am I doing something wrong? Also in the JSONController/API, how do I concatenate depending on the number of rows that is returned from the stored procedure?
Stored procedure code:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[getTopicsForSubject]
#SubjectID int,
#StudentID int
AS
BEGIN
CREATE TABLE #SubjectTopics
(
TopicID int,
Topic varchar(1000),
PercentComplete int
)
INSERT INTO #SubjectTopics
SELECT TopicID, topic, 0
FROM topic
WHERE SubjectID = #SubjectID
UPDATE #SubjectTopics
SET PercentComplete = ISNULL((SELECT PercentComplete FROM StudentTopic
WHERE topicid = #SubjectTopics.TopicID
AND StudentID = #StudentID), 0)
SELECT * FROM #SubjectTopics
RETURN
DROP TABLE #SubjectTopics
END
API / JSON Controller code:
private static string ExecuteSPGetSubjectsData(string queryString, string subjectID, string studentID)
{
string json = "";
string connectionString = ConfigurationManager.AppSettings["dbconn"].ToString();
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
// 1. create a command object identifying the stored procedure
SqlCommand cmd = new SqlCommand(queryString, conn);
// 2. set the command object so it knows to execute a stored procedure
cmd.CommandType = CommandType.StoredProcedure;
// 3. add parameter to command, which will be passed to the stored procedure
cmd.Parameters.Add(new SqlParameter("#SubjectID", subjectID));
cmd.Parameters.Add(new SqlParameter("#StudentID", studentID));
// execute the command
using (SqlDataReader rdr = cmd.ExecuteReader())
{
// iterate over the results, printing each to console
while (rdr.Read())
{
json = (string)rdr[0].ToString() + "#" + (string)rdr[1].ToString() + "#" + (string)rdr[2].ToString() + "|";
}
}
}
return json;
}
You will need to append (+=, instead of just =) each record's values to your "json" variable - currently, your line of code is replacing the "json" variable value as it loops through each record (which explains why the final result has the values from the last record)
while (rdr.Read())
{
json += (string)rdr[0].ToString() + "#" + (string)rdr[1].ToString() + "#" + (string)rdr[2].ToString() + "|";
}
I would like to do the same thing as oracle sqldeveloper do with parametered queries (but from c#, not java).
Let's say there's an arbitrary, user supplied query, eg
select * from dual where 1 = :parameter
My task is to parse safely similar strings, identify the parameters, ask them from user and execute the query.
Which is the right / safe approach? I guess, there's some oracle client api to do this. Or is the right way using some pl/sql stuff (eg. from DBMS_SQL)? I couldn't find such a thing yet...
Update / clarification: see the example code below:
// user enters the query string with parameters somehow:
string sql = AskUserForSelectString();
// now the value of sql is:
// "select column0 from tablename where column1 = :param1 and column2 = :param2 ;"
// this is my original question: HOW TO DO THIS?
List<string> param_names = OracleParseQueryAndGiveMyParameters(sql);
// param_names is now a list of ":param1",":param2"
// ask user again for parameter values:
var param_values = new List<string>();
foreach (var param_name in param_names)
{
string param_value = AskUserForParameterValue(param_name);
param_values.Add(param_value);
}
// give the parameter values for the query in safe way:
using (var cmd = new SqlCommand(sql, myDbConnection))
{
for (int i=0; i< param_names.Count; i++)
cmd.Parameters.AddWithValue(param_names[i], param_values[i]);
var result = cmd.ExecuteReader();
// process result...
}
The key point is that I don't know the parameters in advance. This is exactly what SqlDeveloper can do.
(That isn't an issue if EF expects # before the parameter name instead of colon, that can be worked out easily.)
You can do it like this:
var sql = "INSERT INTO myTable (myField1, myField2) " +
"VALUES (#someValue, #someOtherValue);";
using (var cmd = new SqlCommand(sql, myDbConnection))
{
cmd.Parameters.AddWithValue("#someValue", someVariable);
cmd.Parameters.AddWithValue("#someOtherValue", someTextBox.Text);
cmd.ExecuteNonQuery();
}
What you absolutly must NOT do is:
var sql = "INSERT INTO myTable (myField1, myField2) " +
"VALUES ('" + someVariable + "', '" + someTextBox.Text + "');";
var cmd = new SqlCommand(sql, myDbConnection);
cmd.ExecuteNonQuery();
The problem with the second example is that it opens your code to an SQL Injection attack.
One (hacky but accurate?!) way with the original ":parameter" bind variable syntax is to call out to C and use Oracle OCI functions to do the parsing for you.
Prepare the statement with OCIStmtPrepare2() and then call
OCIStmtGetBindInfo() to get the variable names.
I am calling a SQL Server stored procedure from my C# code:
using (SqlConnection conn = new SqlConnection(connstring))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand("InsertQuerySPROC", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
var STableParameter = cmd.Parameters.AddWithValue("#QueryTable", QueryTable);
var NDistanceParameter = cmd.Parameters.AddWithValue("#NDistanceThreshold", NDistanceThreshold);
var RDistanceParameter = cmd.Parameters.AddWithValue(#"RDistanceThreshold", RDistanceThreshold);
STableParameter .SqlDbType = SqlDbType.Structured;
NDistanceParameter.SqlDbType = SqlDbType.Int;
RDistanceParameter.SqlDbType = SqlDbType.Int;
// Execute the query
SqlDataReader QueryReader = cmd.ExecuteReader();
My stored proc is fairly standard but does a join with QueryTable (hence the need for for using a stored proc).
Now: I want to add a list of strings, List<string>, to the parameter set. For example, my stored proc query goes like this:
SELECT feature
FROM table1 t1
INNER JOIN #QueryTable t2 ON t1.fid = t2.fid
WHERE title IN <LIST_OF_STRINGS_GOES_HERE>
However, the list of strings is dynamic and a few hundred long.
Is there a way to pass a list of strings List<string> to the stored proc??? Or is there a better way to do this?
Many thanks,
Brett
If you're using SQL Server 2008, there's a new featured called a User Defined Table Type. Here is an example of how to use it:
Create your User Defined Table Type:
CREATE TYPE [dbo].[StringList] AS TABLE(
[Item] [NVARCHAR](MAX) NULL
);
Next you need to use it properly in your stored procedure:
CREATE PROCEDURE [dbo].[sp_UseStringList]
#list StringList READONLY
AS
BEGIN
-- Just return the items we passed in
SELECT l.Item FROM #list l;
END
Finally here's some sql to use it in c#:
using (var con = new SqlConnection(connstring))
{
con.Open();
using (SqlCommand cmd = new SqlCommand("exec sp_UseStringList #list", con))
{
using (var table = new DataTable()) {
table.Columns.Add("Item", typeof(string));
for (int i = 0; i < 10; i++)
table.Rows.Add("Item " + i.ToString());
var pList = new SqlParameter("#list", SqlDbType.Structured);
pList.TypeName = "dbo.StringList";
pList.Value = table;
cmd.Parameters.Add(pList);
using (var dr = cmd.ExecuteReader())
{
while (dr.Read())
Console.WriteLine(dr["Item"].ToString());
}
}
}
}
To execute this from SSMS
DECLARE #list AS StringList
INSERT INTO #list VALUES ('Apple')
INSERT INTO #list VALUES ('Banana')
INSERT INTO #list VALUES ('Orange')
-- Alternatively, you can populate #list with an INSERT-SELECT
INSERT INTO #list
SELECT Name FROM Fruits
EXEC sp_UseStringList #list
The typical pattern in this situation is to pass the elements in a comma delimited list, and then in SQL split that out into a table you can use. Most people usually create a specified function for doing this like:
INSERT INTO <SomeTempTable>
SELECT item FROM dbo.SplitCommaString(#myParameter)
And then you can use it in other queries.
No, arrays/lists can't be passed to SQL Server directly.
The following options are available:
Passing a comma-delimited list and then having a function in SQL split the list. The comma delimited list will most likely be passed as an Nvarchar()
Pass xml and have a function in SQL Server parse the XML for each value in the list
Use the new defined User Defined table type (SQL 2008)
Dynamically build the SQL and pass in the raw list as "1,2,3,4" and build the SQL statement. This is prone to SQL injection attacks, but it will work.
Yep, make Stored proc parameter as VARCHAR(...)
And then pass comma separated values to a stored procedure.
If you are using Sql Server 2008 you can leverage TVP (Table Value Parameters): SQL 2008 TVP and LINQ if structure of QueryTable more complex than array of strings otherwise it would be an overkill because requires table type to be created within SQl Server
Make a datatable with one column instead of List and add strings to the table. You can pass this datatable as structured type and perform another join with title field of your table.
If you prefer splitting a CSV list in SQL, there's a different way to do it using Common Table Expressions (CTEs). See Efficient way to string split using CTE.
The only way I'm aware of is building CSV list and then passing it as string. Then, on SP side, just split it and do whatever you need.
CREATE TYPE [dbo].[StringList1] AS TABLE(
[Item] [NVARCHAR](MAX) NULL,
[counts][nvarchar](20) NULL);
create a TYPE as table and name it as"StringList1"
create PROCEDURE [dbo].[sp_UseStringList1]
#list StringList1 READONLY
AS
BEGIN
-- Just return the items we passed in
SELECT l.item,l.counts FROM #list l;
SELECT l.item,l.counts into tempTable FROM #list l;
End
The create a procedure as above and name it as "UserStringList1"
s
String strConnection = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString.ToString();
SqlConnection con = new SqlConnection(strConnection);
con.Open();
var table = new DataTable();
table.Columns.Add("Item", typeof(string));
table.Columns.Add("count", typeof(string));
for (int i = 0; i < 10; i++)
{
table.Rows.Add(i.ToString(), (i+i).ToString());
}
SqlCommand cmd = new SqlCommand("exec sp_UseStringList1 #list", con);
var pList = new SqlParameter("#list", SqlDbType.Structured);
pList.TypeName = "dbo.StringList1";
pList.Value = table;
cmd.Parameters.Add(pList);
string result = string.Empty;
string counts = string.Empty;
var dr = cmd.ExecuteReader();
while (dr.Read())
{
result += dr["Item"].ToString();
counts += dr["counts"].ToString();
}
in the c#,Try this
I have the following, I could make it work as I want to but I think i'm doing it the wrong way, could you please explain how this could be done in a more efficient way ? While also looping on Categories and doing the same as with Districts within the same Insert() Method.
Thanks in advance.
#region Methods
public int Insert(List<District> Districts, List<Category> Categories)
{
StringBuilder sqlString = new StringBuilder("INSERT INTO Stores (name, image) VALUES (#Name, #Image);");
using (SqlConnection sqlConnection = new
SqlConnection(ConfigurationManager.ConnectionStrings["OahuDB"].ConnectionString))
{
SqlCommand sqlCommand = new SqlCommand(sqlString.ToString(), sqlConnection);
sqlCommand.Parameters.AddWithValue("#Name", this.Name);
sqlCommand.Parameters.AddWithValue("#Image", this.Image);
sqlConnection.Open();
int x = (int)sqlCommand.ExecuteScalar();
sqlString.Clear();
sqlCommand.Parameters.Clear();
foreach (District item in Districts)
{
sqlString.AppendLine("INSERT INTO districts_has_stores (district_id, store_id) VALUES (#DistrictID, #StoreID);");
sqlCommand.CommandText = sqlString.ToString();
sqlCommand.Parameters.AddWithValue("#DistrictID", item.ID);
sqlCommand.ExecuteNonQuery();
}
return x;
}
}
EDIT
Is is wrong to achieve the above by doing the following ?
sqlString.Clear();
sqlCommand.Parameters.Clear();
sqlString.AppendLine("INSERT INTO districts_has_stores (district_id, store_id) VALUES (#DistrictID, #StoreID);");
sqlCommand.CommandText = sqlString.ToString();
sqlCommand.Parameters.AddWithValue("#StoreID", x);
foreach (District item in Districts)
{
sqlCommand.Parameters.AddWithValue("#DistrictID", item.ID);
sqlCommand.ExecuteNonQuery();
}
sqlString.Clear();
sqlCommand.Parameters.Clear();
sqlString.AppendLine("INSERT INTO categories_has_stores (category_id, store_id) VALUES (#CategoryID, #StoreID);");
sqlCommand.CommandText = sqlString.ToString();
sqlCommand.Parameters.AddWithValue("#StoreID", x);
foreach (Category item in Categories)
{
sqlCommand.Parameters.AddWithValue("#CategoryID", item.ID);
sqlCommand.ExecuteNonQuery();
}
The first obvious thing is to move the invariant part of the sqlCommand out of the loop
sqlCommand.Parameters.Clear();
sqlString.Clear();
sqlString.AppendLine("INSERT INTO districts_has_stores (district_id, store_id) VALUES (#DistrictID, #StoreID);");
sqlCommand.CommandText = sqlString.ToString();
sqlCommand.Parameters.AddWithValue("#DistrictID", 0); // as dummy value
sqlCommand.Parameters.AddWithValue("#StoreID", x); // invariant
foreach (District item in Districts)
{
sqlCommand.Parameters["#DistrictID"].Value = item.ID;
sqlCommand.ExecuteNonQuery();
}
But this doesn't answer your fundamental problem. How to avoid hitting the database multiple times.
You could build a query with multiple inserts like this
sqlString.Clear();
sqlString.Append("INSERT INTO districts_has_stores (district_id, store_id) VALUES (");
foreach(District item in Districts)
{
sqlString.Append(item.ID.ToString);
sqlString.Append(", ")
sqlString.Append(x.ToString());
sqlString.Append("),");
}
sqlString.Length--;
sqlCommand.CommandText = sqlString.ToString()
But string concatenation is really a bad practice and I present this solution just as an example and I don't want to suggest this kind of approach.
The last possibility are Table-Valued Parameters (Only from SqlServer 2008).
First you need to create a Sql Type for the table you will pass in
CREATE TYPE dbo.DistrictsType AS TABLE
( DistrictID int, StoreID int )
and a StoredProcedure that will insert the data from the datatable passed in
CREATE PROCEDURE usp_InsertDistricts
(#tvpNewDistricts dbo.DistrictsType READONLY)
AS
BEGIN
INSERT INTO dbo.Districts (DistrictID, StoreID)
SELECT dt.DistrictID, dt.StoreID FROM #tvpNewDistricts AS dt;
END
then, back to your code you pass the district into the storedprocedure
(Probably you need to convert your List in a DataTable)
DataTable dtDistricts = ConvertListToDataTable(Districts);
SqlCommand insertCommand = new SqlCommand("usp_InsertDistricts", sqlConnection);
SqlParameter p1 = insertCommand.Parameters.AddWithValue("#tvpNewDistricts", dtDistricts);
p1.SqlDbType = SqlDbType.Structured;
p1.TypeName = "dbo.DistrictsType";
insertCommand.ExecuteNonQuery();
Well, if you look back at the link above, you will find other ways to pass your data in a single step to the database backend.... (Scroll to the end and you will find also a method that doesn't require a stored procedure on the database)
Assuming Stores has an identity column, in SQL Server, create a table type and a table-valued parameter to take advantage of it:
CREATE TYPE dbo.DistrictsTVP AS TABLE
(
DistrictID INT -- PRIMARY KEY? I hope so.
);
GO
CREATE PROCEDURE dbo.InsertStoreAndDistricts
#Name NVARCHAR(255),
#Image <some data type???>,
#Districts dbo.DistrictsTVP READONLY
AS
BEGIN
SET NOCOUNT ON;
DECLARE #StoreID INT;
INSERT dbo.Stores(name, [image]) SELECT #Name, #Image;
SET #StoreID = SCOPE_IDENTITY();
INSERT dbo.district_has_stores(district_id, store_id)
SELECT DistrictID, #StoreID
FROM #Districts;
END
GO
Then in C#, you can pass your List in directly without any looping:
using (...)
{
SqlCommand cmd = new SqlCommand("dbo.InsertStoreAndDistricts", sqlConnection);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter tvparam = cmd.Parameters.AddWithValue("#Districts", Districts);
tvparam.SqlDbType = SqlDbType.Structured;
// other params here - name and image
cmd.ExecuteNonQuery();
}
Recently in my project i used XML as a data type in my stored proc and did insert update and delete in just one shot instead of hitting the database multiple times .
Sample Stored proc
ALTER PROCEDURE [dbo].[insertStore]
#XMLDATA xml,
#name varchar(50),
#image datatype
AS
Begin
INSERT INTO Store
(name
,image
)
Select XMLDATA.item.value('#name[1]', 'varchar(10)') AS Name,
XMLDATA.item.value('#image[1]', 'yourData type') AS Image
FROM #XMLDATA.nodes('//Stores/InsertList/Store') AS XMLDATA(item)
END
Similarly you can write for update and delete .In C# u need to create the xml
public string GenerateXML(List<District> Districts)
var xml = new StringBuilder();
var insertxml = new StringBuilder();
xml.Append("<Stores>");
for (var i = 0; i < Districts.Count; i++)
{ var obj = Districts[i];
insertxml.Append("<Store");
insertxml.Append(" Name=\"" + obj.Name + "\" ");
insertxml.Append(" Image=\"" + obj.Image + "\" ");
insertxml.Append(" />");
}
xml.Append("<InsertList>");
xml.Append(insertxml.ToString());
xml.Append("</InsertList>");
SqlCommand cmd= new SqlCommand("insertStore",connectionString);
cmd.CommandType=CommandType.StoredProcedure;
SqlParameter param = new SqlParameter ();
param.ParameterName ="#XMLData";
param.value=xml;
paramter.Add(param);
cmd.ExecuteNonQuery();
Personally, I would create a stored procedure for the insert and pass in a Table-Valued param, which would allow you to do
INSERT tbl (f1, f2, ... fN)
SELECT * FROM #TVP
http://msdn.microsoft.com/en-us/library/bb510489.aspx
Unless you're using SQL 2005, then I would use an XML param in my stored proc and Serialize a collection to be inserted.
Think about your system design. Where is the data that you need to insert coming from? If it's already in the database, or another database, or some other kind of data store, you should be able to achieve a more bulk kind of transfer, simply inserting from one database to the other in a loop in stored procedure.
If the data is coming from a user, or some incompatible data store, like say an export from some third party program, then you basically have to realize that to get it into the database will involve quite of few round-trips to the database. You can use some tables, or XML or such , but those are actually closer to doing a bulk insert using other methods.
The bottom line is that SQL databases are designed to do inserts one at a time. This is 99% of the time OK because you are never asking users using the UI to type in thousands of things at one time.
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.