I have a c# windows application where I get data from SQL database.
I need to write an sql query with where clause having multiple “OR” conditions dynamically.
Select * from table
where
(Name = #name and Account = #account)
OR
(Name = #name and Account = #account)
OR
(Name = #name and Account = #account)
OR
(Name = #name and Account = #account)
OR
……..
Here number of OR statement can vary based on the number of rows in the data table.
How can write a sql query that can use OR statements dynamically?
E.g.
var names = new[] {"name1", "name2", "name3"};
var accounts = new[] {"account1", "account2", "account3"};
var conditions = new List<string>();
var command = new SqlCommand();
for (var i = 0; i < names.Length; i++)
{
conditions.Add($"(Name = #Name{i} AND Account = #Account{i})");
command.Parameters.Add($"#Name{i}", SqlDbType.VarChar, 50).Value = names[i];
command.Parameters.Add($"#Account{i}", SqlDbType.VarChar, 50).Value = accounts[i];
}
command.CommandText = $"SELECT * FROM MyTable WHERE {string.Join(" OR ", conditions)}";
This still uses parameters so it still avoids the possibility of SQL injection but it also allows you to build the query dynamically.
Related
With the help from the sylvan.data.csv package i'm now able to apply the scheme from the sql table to the csv. Yet the following problem arises where i want to check if a row from the csv exists in the sql database. If it does, it needs to be updated and if it does not only the non existing rows need to be imported. But with a bulkcopy this is not possible.
I've got the following code:
static void LoadTableCsv(SqlConnection conn, string tableName, string csvFile)
{
// read the column schema of the target table
var cmd = conn.CreateCommand();
conn.Open();
cmd.CommandText = $"select top 0 * from {tableName}"; // beware of sql injection
var reader = cmd.ExecuteReader();
var colSchema = reader.GetColumnSchema();
reader.Close();
// apply the column schema to the csv reader.
var csvSchema = new CsvSchema(colSchema);
var csvOpts = new CsvDataReaderOptions { Schema = csvSchema };
using var csv = CsvDataReader.Create(csvFile, csvOpts);
// Initialize SqlCommand for checking if the record already exists.
using var checkCommand = new SqlCommand("SELECT COUNT(*) FROM {tablename} WHERE TicketID = #value", conn);
checkCommand.Parameters.Add("#value", SqlDbType.Int, 10, "TicketID");
using var bulkCopy = new SqlBulkCopy(conn);
bulkCopy.DestinationTableName = tableName;
bulkCopy.EnableStreaming = true;
// Iterate through the records in the CSV file.
while (csv.Read())
{
// Set the value of the "#value" parameter
checkCommand.Parameters["#value"].Value = csv["TicketID"].ToString();
// Execute the check command to see if the record already exists.
var checkResult = (int)checkCommand.ExecuteScalar();
if (checkResult == 0)
{
// The record does not exist, write it to the SQL database using SqlBulkCopy.
bulkCopy.WriteToServer(new[] { csv });
}
else
{
// The record already exists, update it using an UPDATE statement.
using var updateCommand = new SqlCommand("UPDATE {tablename} SET Column1 = #col1, Column2 = #col2, Column3 = #col3, Column4 = #col4, Column5 = #col5, Column6 = #col6 WHERE TicketID = #value", conn);
// Add parameters for each column you want to update, using the names and types of the columns in the target table.
updateCommand.Parameters.Add("#col1", SqlDbType.Int, 10, "TicketID");
updateCommand.Parameters.Add("#col2", SqlDbType.NVarChar, 50, "TicketTitle");
updateCommand.Parameters.Add("#col3", SqlDbType.NVarChar, 50, "TicketStatus");
updateCommand.Parameters.Add("#col4", SqlDbType.NVarChar, 50, "CustomerName");
updateCommand.Parameters.Add("#col5", SqlDbType.NVarChar, 50, "TechnicianFullName");
updateCommand.Parameters.Add("#col6", SqlDbType.DateTime, 50, "TicketResolvedDate");
updateCommand.Parameters.Add("#value", SqlDbType.Int, 10, "TicketID");
// Set the values of the parameters to the values in the current row of the CSV file.
updateCommand.Parameters["#col1"].Value = int.Parse(csv["TicketID"].ToString());
updateCommand.Parameters["#col2"].Value = csv["TicketTitle"].ToString();
updateCommand.Parameters["#col3"].Value = csv["TicketStatus"].ToString();
updateCommand.Parameters["#col4"].Value = csv["CustomerName"].ToString();
updateCommand.Parameters["#col5"].Value = csv["TechnicianFullName"].ToString();
updateCommand.Parameters["#col6"].Value = DateTime.Parse(csv["TicketResolvedDate"].ToString());
updateCommand.Parameters["#value"].Value = int.Parse(csv["TicketID"].ToString());
// Execute the update command.
updateCommand.ExecuteNonQuery();
}
}
conn.Close();
}
But this gives me an error cause the bulkcopy can't read only one datarow.
Siggermannen's comment was the correct suggestion. You should bulk load all of the data into a temp table, then use SQL command(s) to merge the data from the temp table into the destination table. Ideally, you'd do this with a T-Sql Merge Statement. You could also use separate update and insert statements. This requires knowledge of the table columns to create the commands to merge the data. You can do this dynamically, by reading querying the INFORMATION_SCHEMA for the tables to determine the columns and keys, and using that to dynamically construct the merge statements. Or, if you know the schema at compile time, you can hard-code the statements which will be significantly easier to develop and test.
using Sylvan.Data.Csv;
using System.Data.SqlClient;
static void LoadTableCsv(SqlConnection conn, string tableName, string csvFile)
{
// read the column schema of the target table
var cmd = conn.CreateCommand();
cmd.CommandText = $"select top 0 * from {tableName}"; // beware of sql injection
var reader = cmd.ExecuteReader();
var colSchema = reader.GetColumnSchema();
reader.Close();
// create a temp table to load the data into
// using the destination table as a template
cmd.CommandText = $"select top 0 * into #load from {tableName}";
cmd.ExecuteNonQuery();
// apply the column schema to the csv reader.
var csvSchema = new CsvSchema(colSchema);
var csvOpts = new CsvDataReaderOptions { Schema = csvSchema };
using var csv = CsvDataReader.Create(csvFile, csvOpts);
// push *all* data into the temp table
using var bulkCopy = new SqlBulkCopy(conn);
bulkCopy.DestinationTableName = "#load";
bulkCopy.EnableStreaming = true;
bulkCopy.WriteToServer(csv);
// use sql commands to "MERGE" the data into the destination table
cmd.CommandText = $"""
insert into {tableName}
select * from #load l
where not exists (select * from {tableName} d where d.Id = l.Id)
""";
cmd.ExecuteNonQuery();
}
Doing insert/update statements in a loop is what you're trying to avoid. This produces "chatty" communication with the database where performance becomes dominated by overhead of each operation. Instead, you want to keep your operations as "chunky" as possble, which SqlBulkCopy and MERGE will provide.
I have facing a problem that SQL IN operator did not work with multiple value of scalar variables in C#, but query works in SQL Server 2012.
This is working:
SELECT WorkStatus
FROM viewWorkRequest
WHERE WorkStatus IN ('Open', 'Closed')
But it's not working in C#
string All = "'Open','Closed'";
string sql = "SELECT WorkStatus FROM viewWorkRequest WHERE WorkStatus IN (#WorkStatus)"
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
cmd.Parameters.AddWithValue("#WorkStatus", All);
cmd.ExecuteNonQuery();
}
Thanks in advance
Why would you think that the fact that the first SQL works would have any bearing on the second SQL, which doesn't contain an IN clause?
Each parameter can only contain a single value and each value in an IN clause is distinct, so you cannot pass multiple values for an IN clause with a single parameter. If you don't know how many values you will have then you have to build your SQL in such a way as to allow for a variable number of parameters, e.g.
var values = new[] {"Open", "Closed"};
var sql = $"SELECT * FROM viewWorkRequest WHERE WorkStatus IN ({string.Join(", ", Enumerable.Range(0, values.Length).Select(n => "#WorkStatus" + n))})";
var command = new SqlCommand(sql, connection);
for (var i = 0; i < values.Length; i++)
{
command.Parameters.Add("#WorkStatus" + i, SqlDbType.VarChar, 50).Value = values[i];
}
Attempting to set the column as a parameter in FromRawSql. Query always returns null, I understand this is because the SQL is compiled before parameter values are set. How can I pass the column name into the query as a parameter?
What I'm trying:
var param = new SqlParameter[] {
new SqlParameter("#col", dataDiscriminator),
new SqlParameter("#value", itemDiscValue)
};
var thisq = context.Devices.FromSqlRaw("SELECT * FROM Devices WHERE #col = #value", param);
var thisDevice = thisq.SingleOrDefault();
Will produce the SQL:
DECLARE #col nvarchar(4) = N'Name';
DECLARE #value nvarchar(26) = N'Registration Template';
SELECT * FROM Devices WHERE #prop = #value
you can not use parameter for table or column name. Try this
var param = new SqlParameter[] {
new SqlParameter("#value", itemDiscValue)
};
var thisq = context.Devices.FromSqlRaw($"SELECT * from Devices
WHERE [{dataDiscriminator}] = #value", param).ToList();
var thisDevice = thisq.SingleOrDefault();
I don' t know where dataDiscriminator data from but always remember about the sql script injections.
I want to reuse a parameterized query in a loop.
(This query is a simple example, I don't think I could make the loop inside sql and just return the needed rows)
Instead of
private String sql = "SELECT v FROM t WHERE VAL_1 = #param_1";
for (int n=1;n<10;n++)
{
MySqlCommand m = new MySqlCommand(sql);
m.Parameters.AddWithValue("#param_1", n);
res = Convert.ToInt32(m.ExecuteScalar());
( ... )
}
I'd like to move the setup of the query outside the loop; something like
private String sql = "SELECT v FROM t WHERE VAL_1 = #param_1";
MySqlCommand m = new MySqlCommand(sql);
m.Parameters.Add("#param_1"); // does not exist
for (int n=1;n<10;n++)
{
m.Parameters.Set("#param_1", n); // does not exist
res = Convert.ToInt32(m.ExecuteScalar());
( ... )
}
So the server does not have to parse the same sql for each ilteration in loop.
Is that possible?
You can add a parameter with
m.Parameters.Add("#param_1", MySqlDbType.Int32);
and later in the loop assign a value with
m.Parameters["#param_1"].Value = n;
If you just need to run query for list of parms without do diffrent things on each result, You can create a string with a loop like that:
String where_str= VAL_1 = #param_1" OR VAL_1 = #param_2" OR VAL_1 = #param_3"...
String sql = "SELECT v FROM t WHERE " + where_str;
and then exec the query it will give the same result.
If you need to saparate results so you can make it with prepaerd statement. Also, I recommend you to read about stored procedure it may be the best soultion for you in some cases.
example for prepaerd statement: (more info in the link)
private static void SqlCommandPrepareEx(string connectionString)
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
SqlCommand command = new SqlCommand(null, connection);
// Create and prepare an SQL statement.
command.CommandText =
"INSERT INTO Region (RegionID, RegionDescription) " +
"VALUES (#id, #desc)";
SqlParameter idParam = new SqlParameter("#id", SqlDbType.Int, 0);
SqlParameter descParam =
new SqlParameter("#desc", SqlDbType.Text, 100);
idParam.Value = 20;
descParam.Value = "First Region";
command.Parameters.Add(idParam);
command.Parameters.Add(descParam);
// Call Prepare after setting the Commandtext and Parameters.
command.Prepare();
command.ExecuteNonQuery();
// Change parameter values and call ExecuteNonQuery.
command.Parameters[0].Value = 21;
command.Parameters[1].Value = "Second Region";
command.ExecuteNonQuery();
}
}
Yes, this should be possible! Have a look for SQL Prepared Statements!
You can just use:
cmd = new MySqlCommand("SELECT * FROM yourTable WHERE condition=#val1", MySqlConn.conn);
In the loop add the parameters and prepare the command
cmd.Parameters.AddWithValue("#val1", value);
cmd.Prepare();
after the loop execute your query with
cmd.ExecuteNonQuery();
Yep, you can do all of those things but unless that's just an example you'd want to use IN with all the values or a join to a bulk loaded temp table if there are a large number of them. The reason is that each round trip to the DB has a significant overhead that you can reduce from n to 1 with either of those techniques.
I have asp.net site which contains 13 textboxes where a user can enter in certain information to search through a database. A gridview populates the data. The data is only for viewing purposes.
My issue is, every row is always returned. If a user types data into only one field, then only the rows containing that data should be returned. Instead, every row is returned no matter what and I can't seem to figure out what is wrong with my SQL Statement.
Here is the entire code:
SqlConnection mySqlConnection = new SqlConnection(strings.settings.connectionString);
SqlCommand mycommand = new SqlCommand("SELECT SOPID, CONTACT, SHIPTONAME, KNOWN_EMAIL, ADDR1, ADDR2, CITY, STATE, ZIPCODE, PHONE1, CUSTPO, CID, AID, SEATS FROM dbo.LOOKUP_TEST_TBL WHERE CONTACT = #CONTACT OR SHIPTONAME = #SHIPTONAME OR KNOWN_EMAIL = #KNOWN_EMAIL OR ADDR1 = #ADDR1 OR ADDR2 = #ADDR2 OR CITY = #CITY OR STATE = #STATE OR ZIPCODE = #ZIPCODE OR PHONE1 = #PHONE1 OR CUSTPO = #CUSTPO OR CID = #CID OR AID = #AID OR SEATS = #SEATS", mySqlConnection);
//AccessCommand.Parameters.AddWithValue("#ORDERID", SqlDbType.Char).Value = txtOrderID.Text.ToUpper();
mycommand.Parameters.AddWithValue("#CONTACT", SqlDbType.Char).Value = txtContact.Text.ToUpper();
mycommand.Parameters.AddWithValue("#SHIPTONAME", SqlDbType.Char).Value = txtShipToName.Text.ToUpper();
mycommand.Parameters.AddWithValue("#KNOWN_EMAIL", SqlDbType.Char).Value = txtEmail.Text.ToUpper();
mycommand.Parameters.AddWithValue("#ADDR1", SqlDbType.Char).Value = txtAddress1.Text.ToUpper();
mycommand.Parameters.AddWithValue("#ADDR2", SqlDbType.Char).Value = txtAddress2.Text.ToUpper();
mycommand.Parameters.AddWithValue("#CITY", SqlDbType.Char).Value = txtCity.Text.ToUpper();
mycommand.Parameters.AddWithValue("#STATE", SqlDbType.Char).Value = txtState.Text.ToUpper();
mycommand.Parameters.AddWithValue("#ZIPCODE", SqlDbType.Char).Value = txtZip.Text.ToUpper();
mycommand.Parameters.AddWithValue("#PHONE1", SqlDbType.Char).Value = txtPhone.Text.ToUpper();
mycommand.Parameters.AddWithValue("#CUSTPO", SqlDbType.Char).Value = txtCustomerPO.Text.ToUpper();
mycommand.Parameters.AddWithValue("#CID", SqlDbType.Char).Value = txtCustomerID.Text.ToUpper();
mycommand.Parameters.AddWithValue("#AID", SqlDbType.Char).Value = txtAddressID.Text.ToUpper();
mycommand.Parameters.AddWithValue("#SEATS", SqlDbType.Char).Value = txtSeats.Text.ToUpper();
mySqlConnection.Open();
SqlDataReader reader = mycommand.ExecuteReader();
if (reader.HasRows == false)
{
throw new Exception();
}
GridView1.DataSource = reader;
GridView1.DataBind();
}
Any tips or advice or a point in the right direction would be fantastic! Thanks everyone!
your query shoul look like
SELECT *
FROM dbo.LOOKUP_TEST_TBL
WHERE (CONTACT = #CONTACT and CONTACT is not null) or
(SHIPTONAME = #SHIPTONAME and SHIPTONAME is not null).....
just exclude the nullable records
The problem is almost certainly the query -- it will return any rows that have empty columns in any of those fields, which the user didn't provide.
I would try constructing the query using only the columns that the user has specified.
Get the non-empty parameters:
Dictionary<string, string> values = new Dictionary<string,string> {
{ "#CONTACT", txtContact.Text.ToUpper() },
{ "#ADDR1", txtAddress1.Text.ToUpper() },
// etc
};
var enteredParams = values.Where(kvp => !string.IsNullOrEmpty(kvp.Value));
And construct the query:
string sql = string.Format("SELECT ... WHERE {0}",
string.Join(" OR ",
enteredParams.Select(kvp =>
kvp.Key.TrimStart(new char[] { '#' }) + " = " + kvp.Key
)
);
And finally construct the parameters:
foreach (var kvp in enteredParams)
{
mycommand.Parameters.AddWithValue(kvp.Key, kvp.Value);
}
(Unchecked for exact syntax, but I think you get the idea.)
You OR everything in your WHERE clause together, so if even ONE of the things matches it'll be included in the results. Did you maybe mean to AND them together instead?