How to use every word of a string in WHERE clause? - c#

I am facing a problem , I have a query in SQL Server 2014, The query result should be based on a WHERE clause that takes a string from a C# CheckedListBox.
I have the string in this form (the following values are for example only) :-
cat,dog,bird,duck
And inside the database the records look like this:-
dog cat-dog cat-dog-duck bird-dog-duck etc...
I have tried this :-
DECLARE #animals nvarchar(max) = 'dog,bird,cat'
select x,y,z WHERE CHARINDEX(animals, replace(#animals,',',' ')) > 0
The result would show rows with only ONE SINGLE VALUE like dog cat bird But it wouldn't show rows with values like dog-cat dog-cat-bird etc! it just shows rows with one single word from #animals string.
How can I select all rows where column animals contains either a word or more from #animals string.
Thanks in advance...

You should create a temp table for store all searching value or you should create a temp table from the comma separated variable for the example visit Query for convert CSV values into temp table. Then use inner join for filter records from your table like below.
declare #temp table (animal varchar(50))
insert into #temp values ('cat')
insert into #temp values ('dog')
insert into #temp values ('bird')
select * from SomeTable a
inner join #temp t on a.Column like '%' + t.animal + '%'
Make stored procedure for that query and call it from C#.

And inside the database the records look like this:-
dog cat-dog cat-dog-duck bird-dog-duck etc...
There is the source of your problems. Before reading anything else I wrote in my answer, you should read Is storing a delimited list in a database column really that bad?, where you will see a lot of reasons why the answer to this question is Absolutely yes!.
Once you're done with that, the solution to the problem should be as obvious to you as it is to me - Fix the database structure - meaning remove that column storing delimited data and replace it with a table referenced by a many-to-many relationship to your existing table, that will hold the animals data for you.
The first part of the solution is using a table valued parameter instead of sending a delimited string to the database.
There are plenty of examples on how to do this on stackoverflow - like here and there.
Once you've done that, you can use a hack with like as a workaround, in case you can't change the database structure:
SELECT <ColumnsList>
FROM <TableName> As T
JOIN #TVP As TVP
ON '-' + T.Animals +'-' LIKE '%-' + TPV.Animal +'-%'
Note I've added the delimiter to both ends of both columns.
If you can change the structure you will have a query like this:
SELECT <ColumnsList>
FROM <TableName> As T
JOIN TableToAnimals AS TTA
ON T.Id = TTA.TableId
JOIN Aniamls AS A
ON TTA.AnimalId = A.Id
JOIN #TVP As TVP
ON A.Name = TVP.Animal

You should take a look at Table valued parameters, it will let you send in a table of values as a parameter from C# to SQL. You make a table with "animals" and then make a sub-select in your Stored Proc.
See example code from link below, shows how to pass the parameter:
// Assumes connection is an open SqlConnection object.
using (connection)
{
// Create a DataTable with the modified rows.
DataTable addedCategories = CategoriesDataTable.GetChanges(DataRowState.Added);
// Configure the SqlCommand and SqlParameter.
SqlCommand insertCommand = new SqlCommand("usp_InsertCategories", connection);
insertCommand.CommandType = CommandType.StoredProcedure;
SqlParameter tvpParam = insertCommand.Parameters.AddWithValue("#tvpNewCategories", addedCategories);
tvpParam.SqlDbType = SqlDbType.Structured;
// Execute the command.
insertCommand.ExecuteNonQuery();
}
https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/table-valued-parameters

For anyone who has the same problem. I have found the solution for this on SQL Server. Use the Full Text Search and your problem is easily solved. It's awesome.
Check the following link :-
https://learn.microsoft.com/en-us/sql/relational-databases/search/get-started-with-full-text-search?view=sql-server-2017

use like this:
var query = string.Empty;
var index=0;
foreach(var animal in animals) {
if (query.Length>0) {
query +=" and ";
}
var paramName = "#animalName" + index++;
query +="(animals like " + paramName + " or animals like '%' + " + paramName + " or animals like " + paramName + " + '%' or animals like '%' + " + paramName + " + '%')";
SqlParameter thisParam = new SqlParameter(paramName, animal);
command.Parameters.Add(thisParam);
}
command.CommandText = "select * from tableName WHERE " + query;

Related

How Insert on table IF some value is in database

There is a syntax problem on Asp.Net when I try to run an insert on a table if I have a vlaue in another table that I look for.
I tried different queries and datareaders but that generates a problem, one of the 2 datareaders needs to be closed.
con.Open();
String insertRegInfo = "INSERT INTO Login (NumEmp,Nombre,Apellido,Codigo) values (#NumEmp, #Nombre,#Apellido,#Codigo) SELECT NumEmp from Empleados WHERE NumEmp = " + TxtNumEmp.Text +"" ;
MySqlCommand command = new MySqlCommand(insertRegInfo, con);
LblConfirm.Text = "Confirmado";
command.Parameters.AddWithValue("#NumEmp", TxtNumEmp.Text);
command.Parameters.AddWithValue("#Nombre", TxtNombre.Text);
command.Parameters.AddWithValue("#Apellido", TxtApellido.Text);
command.Parameters.AddWithValue("#Codigo", TxtCodigo.Text);
command.ExecuteNonQuery();
con.Close();
I expect to insert data into the table if any value is true.
Let me start saying that this is a terrible coding practice:
String insertRegInfo = "INSERT INTO Login (NumEmp,Nombre,Apellido,Codigo) values (#NumEmp, #Nombre,#Apellido,#Codigo) SELECT NumEmp from Empleados WHERE NumEmp = " + TxtNumEmp.Text +"" ;
Better is:
String insertRegInfo = "INSERT INTO Login (NumEmp,Nombre,Apellido,Codigo)
SELECT NumEmp,#Nombre,#Apellido,#Codigo from Empleados WHERE NumEmp = #NumEmp" ;
You should use parameters instead and better even Store Procedures.
However, to answer your question. All you need to do is match the number of columns of your SQL Command.
INSERT INTO Login (NumEmp,Nombre,Apellido,Codigo)
SELECT NumEmp, #Nombre,#Apellido,#Codigo from Empleados WHERE…
Note that I removed the values section from the insert. That is not required

Searching an int column on the basis of a string value

I have a View View_Booking in sql server 2014:
bkID bkSlot bkStatus
---- ------ --------
2 Lunch 1
4 Lunch 1
6 Dinner 0
7 Lunch 1
While in c# I have used a gridview and casted bkStatus into string like:
<asp:Label ID="lblStatus" Text='<%# (Eval("bkStatus")+"" == "1") ? "Booked" : "Pending" %>'
... ></asp:Label>
bkID bkSlot bkStatus
---- ------ --------
2 Lunch Booked
4 Lunch Booked
6 Dinner Pending
7 Lunch Booked
Now I'm searching into View using this query:
SELECT * FROM View_Booking
WHERE CAST(bkID AS NVARCHAR(MAX)) LIKE '%" + keyword + "%'
OR bkSlot LIKE '%"+keyword+"%'
OR bkStatus LIKE << ? >>
But don't know how to search for bkStatus which is passed as string from c# while it's a int in sql?
Some recommendations
The query you have provided need to be optimized:
First, using CAST(bkID AS NVARCHAR(MAX)) will affect the performance of the query, because it will not use any index, also casting to NVARCHAR(MAX) will decrease the performance.
bkStatus is a numeric column so you have to use = operator and compare with numeric values (0 or 1 or ...), also the text values provided are defined in the asp tag not in the database, so they are used in the application level not the data level.
if you are using CAST(bkID AS NVARCHAR(MAX)) to search for the bkid column that contains a specific digit (ex: search for 1 -> result 1,10,11,...), then try Casting to a specific size (ex: CAST(bkID as NVARCHAR(10))
It is recommended to use parameterized queries for a better performance and to prevent Sql injection attacks. look at #un-lucky answer
You can use a dictionary Object to store the ID values related to the keywords
Example
Note: The use of CAST and Like will not used any index, this example is based on your requirements (i tried to combine the recommendations i provided with others recommendations)
var dicStatus = new Dictionary<int, string> {
{ 0, "Pending" },
{ 1, "Booked" },
{ 2, "Cancelled" }
// ...
};
string querySql = " SELECT * FROM View_Booking" +
" WHERE CAST(bkID AS NVARCHAR(10)) LIKE #bkID" +
" OR bkSlot LIKE #bkSlot" +
" OR bkStatus = #status";
using (SqlConnection dbConn = new SqlConnection(connectionString))
{
dbConn.Open();
using (SqlCommand sqlCommand = new SqlCommand(querySql, dbConn))
{
sqlCommand.Parameters.Add("#bkID", SqlDbType.VarChar).value ="%" + keyword + "%";
sqlCommand.Parameters.Add("#bkSlot", SqlDbType.VarChar).value ="%" + keyword + "%";
sqlCommand.Parameters.Add("#status", SqlDbType.Int).value = dicStatus.FirstOrDefault(x => x.Value == keyword).Key;
sqlCommand.ExecuteNonQuery();
}
}
Also if BkID is an integer column it is better to use
sqlCommand.Parameters.Add("#bkID", SqlDbType.Int).value = (Int)keyword ;
References & Helpful Links
Like operator for integer
Performance hit using CAST in T-SQL
How much do CAST statements affect performance?
SQL Server: Index columns used in like?
C# Dictionary get item by index
Getting query to work with parameter and "like"
So you need a search box in which user can search by using bkID,bkSlot or bkStatus, If the search text is Booked or Pending we have to add the filter for bkStatus which will be an integer field in the database. right? Few more thing that I have to mention here is the usage of using as well as the parameterization for queries for a smarter and safer way of execution. So I would like to suggest to build and execute the query like the following:
int statusCode = -1;
if(keyword.ToLower() == "booked")
statusCode = 1;
else if(keyword.ToLower() == "pending")
statusCode = 0;
string querySql = " SELECT * FROM View_Booking" +
" WHERE CAST(bkID AS NVARCHAR(MAX)) LIKE #bkID" +
" OR bkSlot LIKE #bkSlot" +
" OR bkStatus = #status";
using (SqlConnection dbConn = new SqlConnection("connectionString here"))
{
dbConn.Open();
using (SqlCommand sqlCommand = new SqlCommand(querySql, dbConn))
{
sqlCommand.Parameters.Add("#bkID", SqlDbType.VarChar).value ="%" + keyword + "%";
sqlCommand.Parameters.Add("#bkSlot", SqlDbType.VarChar).value ="%" + keyword + "%";
sqlCommand.Parameters.Add("#status", SqlDbType.int).value = statusCode;
sqlCommand.ExecuteNonQuery();
}
}
Please note the following:
If you want to include the bkStatus filter for book, Pend etc.. then you have to change the condition accordingly by using .Contains() or .StartsWith() instead for .ToLower()
statusCode is initialized with -1 to avoid bkStatus based filter for all other values
You can use declare function to create a temporary table that has a list of bkStatus.
It will be easier for you to create a query by using bkstatus as a foreign key. After that, you don't have to use cast or like function anymore. It will be a little bit inefficient.
You can try this code below :
declare #bkstatus table (number int primary key , bkstatus varchar(10) )
insert into #bkstatus (number , bkstatus)
values ( 0 , 'Pending'), (1 , 'Booked')
and then using this query :
SELECT * FROM View_Booking v
INNER JOIN #bkstatus b on v.bkstatus = b.number
WHERE b.bkstatus = #keyword
Just another option using CHOOSE() to decode the bkStatus and TRY_CONVERT() to test bkID.
Example
Declare #KeyWord varchar(50) = 'Pending';
Select *
From View_Booking
Where bkID = try_convert(int,#KeyWord)
or bkSlot like '%'+#KeyWord+'%'
or choose(bkStatus+1,'Pending','Booked')=#KeyWord
Returns
bkID bkSlot bkStatus
6 Dinner 0
If keyword will be status name and not status id, I would create BookingStatus table, have bkStatus and bkStatusTitle columns there and join it to the View_Booking. You could easily do LIKE on bkStatusTitle then.
SELECT * FROM View_Booking
WHERE CAST(bkID AS NVARCHAR(16)) LIKE '%' + #keyword + '%'
OR bkSlot LIKE '%' + #keyword + '%'
OR bkStatusTitle LIKE '%' + #keyword + '%'
If keyword will be a string representation of bkStatus, I would just see if the values are same.
As a side note, it's a bad idea to build your SQL queries concatenating user input into it like '%' + keyword + '%'. This is open to SQL injection attacks. It's best to use SQL parameters to pass user input to SQL queries. Using '%' + #keyword + '%' in the SQL bit and in C# something like example below would be much safer.
sqlCommand.Parameters.Add("#keyword", SqlDbType.VarChar, 1000);
sqlCommand.Parameters["#keyword"].Value = searchText;
Parameterized queries also give you a benefit of same query text for multiple requests, which in turn allows SQL Server to cache SQL execution plans and reuse them, giving slightly better performance.
Your bkStatus is integer. In the view you translate the integer value into a user meaningful string. Up to that point it's all ok. Now, for the user to search all you need to do is reverse your translations from strings to integer and search for integers.
Keeping things simple
searchStatusKey = yourVariableHodingTheString == "Booked" ? 1 : 0;
However, to avoid tedious bugs and painless upgrades of code in multiple places (say because they decided to add another status there) I would recommend a translation table here. Something like HashMap (associative array).
var statusMap = new Dictionary<int, string> {
{ 0, "Pending" },
{ 1, "Booked" },
/* can always add more pairs in the future as needed */
};
Now, assuming your parameter is in a variable called searchStatus
searchStatusKey = statusMap.FirstOrDefault(x => x.Value == searchStatus).Key;
Now Provide as bkStatus parameter in the where part the searchStatusKey value and you're done.
select * from View_Booking where bkStatus = << ? >>
I will just focus on this part of your question (Is it the question itself?):
But don't know how to search for bkStatus which is passed as string from c# while it's a int in sql?
One way of dealing with that in SQL is with the help of the CASE clause. In your specific case you could (doesn`t mean should) do something like:
SELECT * FROM View_Booking
WHERE CAST(bkID AS NVARCHAR(MAX)) LIKE '%" + keyword + "%'
OR bkSlot LIKE '%"+keyword+"%'
OR bkStatus = CASE '%"+keyword+"%' WHEN 'Booked' THEN CAST(1 AS INT) WHEN 'Pending' THEN CAST(0 AS INT) WHEN ... THEN ... ELSE ... END'
But I suggest the use of parameters as indicated in #un-lucky's answer. There's a whole lot more we could discuss in terms of best practices here, so I suggest you to take a look at the following articles:
Lookup tables: You stated that bkStatus is of type INT soy I assume you could have more options than Booked or Pending, for example: Reserved or Cancelled. In that case your actual code may become increasingly untidy with every option you add.
Best practices for using ADO.NET: You did not specify how do you access the database from your front end. Even though this article has been around for years, most of its content is still current. I assume this may be helpful.
Building Better Entity Framework: In case you are using Entity Framework to access your database.
Hope it helps.
Make an enum for BookingStatus and make a function that accepts string and returns the enum value. See the below code.
public enum BookingStatus {
[Description("Pending")]
Pending = 0,
[Description("Booked")]
Booked = 1
}
Now the function is as below,
public static T GetValueFromDescription<T>(string p_description)
{
var type = typeof(T);
if (!type.IsEnum) throw new InvalidOperationException();
foreach (var field in type.GetFields())
{
var attribute = Attribute.GetCustomAttribute(field,
typeof(DescriptionAttribute)) as DescriptionAttribute;
if (attribute != null)
{
if (attribute.Description == p_description)
return (T)field.GetValue(null);
}
else
{
if (field.Name == p_description)
return (T)field.GetValue(null);
}
}
throw new ArgumentException("Not found.", "description");
// or return default(T);
}
Now in the parameter in sql query, call this function with parameter as "Booked" or "Pending" and it will return enum BookingStatus.Booked. You can easily extract int value from that.
(int)BookingStatus.Booked // will give 1
It looks as if you are trying to search freely amongst several columns. This is quite a common problem, and the real solution can be found at www.Sommarskog.se on dynamic search conditions.
Your solution looks as if it is vulnerable to SQL Injection. May I suggest that you implement something similar to the stored procedure search_orders_3?

Insert data into SQL database using Where clause

How can you add data to a specific row in a database, using the where clause.
Here is what I tried:
"Insert into table1 (name, address) values ('" + textbox1.Text+ "', '"+textbox2.Text+"')
where (name ='"+textbox1.Text+"')"
Firstly, never ever concatenate strings for database input. It leaves you wide open to SQL Injection attacks. See this MSDN article for more information
Secondly, you don't do inserts with a WHERE clause. You could do an UPDATE with a WHERE
Examples:
INSERT INTO TABLE (col1, col2) VALUES (val1, val2)
UPDATE TABLE SET col1 = val1, col2 = val2 WHERE col3 = somevalue
for example in your case, you would want to write this for an insert (including parameters)
sql = "INSERT INTO table1 (name, address) VALUES (#textbox1,#textbox2)";
SqlCommand query = new SqlCommand("connection string", sql);
query.Parameters.AddWithValue(#textbox1, textbox1.Text);
query.Parameters.AddWithValue(#textbox2, textbox2.Text);
If you know the database field type, instead of using AddWithValue, instead use this syntax:
query.Parameters.Add(#parametername, SqlDBType.Type,size).Value = somevalue;
where SqlDBType.Type is the database field type (eg VarChar, Int, VarBinary etc) and size is the value of the field. If my DB field was VarChar(500), then my parameter setup would be
query.Parameters.Add(#parametername, SqlDBType.VarChar, 500).Value = somevalue;
you can replace the sql string with the following if you want to update rather than insert. Note, update records using an identifier - it would be a bad practice to use name as your WHERE clause.
sql = "UPDATE table1 SET name = #textbox1, address = #textbox2 WHERE xyz";
Parameters prevent users from putting unexpected values into boxes allowing for unauthorised code to be run. Using a concatenated string as you currently are could enable an attacker to compromise your entire database
you can't insert with a 'where' clause inseert is for adding new records. If your updating then use:
Update table1 set
name = '" + textbox1.Text + "',
address ='" + textbox2.Text+ "'
where (name ='"+textbox1.Text+"')
or insert should be:
"Insert into table1 (name, address) values ('" + textbox1.Text+
"','"+textbox2.Text+"')"
however make sure everything is validated against sql injection. or parameterize the above.
You don't need a where clause for inserting a record into SQL Server database using INSERT statement. You should change your code as below to make it work:
"Insert into table1 (name, address) values ('" + textbox1.Text+ "', '"+textbox2.Text+"')
Two possible forms of a SQL insert statement are as below:
INSERT INTO table_name (column1,column2,column3,...)
VALUES (value1,value2,value3,...);
INSERT INTO table_name
VALUES (value1,value2,value3,...);
You can learn more about INSERT SQL statement here:
http://www.w3schools.com/sql/sql_insert.asp

How to copy a MySql database schema using C#?

I'm trying to use C# & MySql in order to copy an empty table (recreate the Schema really). The structure looks like this:
> TableTemplate (schema)
+ Tables
> FirstTable (table)
> second table (table)
> ...
> SomeOtherTable
+ Tables
> ...
What I would like is to copy the TableTemplate into a new Schema with the user name.
The first obvious path to oblivion was trying CREATE TABLE #UserName LIKE TableTemplate, swiftly learning that sql parameters are supposed to be used for values and not table names (all hail jon skeet, again: How to pass a table as parameter to MySqlCommand?).
So that leaves us with manual validation of the user names in order to build the table names (robert's a prime example).
Next, it seems that even CREATE TABLE UserID LIKE TableTemplate; won't work (even from MySQL Workbench), since TableTemplate isn't a table.
So It's down to writing a loop that will create a table LIKE each table in TableTemplate, after creating a UserID Schema (after manual validation of that string), or trying other options like dumping the database and creating a new one, as seen in these questions:
C# and mysqldump
Slow performance using mysqldump from C#
But I would prefer avoid running a process, dumping the database, and creating it from there every time I add a user.
Any suggestions would be highly appreciated.
I think mysqldump would be better. but if you want to do in one process. try this.
SELECT
CONCAT ("CREATE TABLE SomeOtherTable.",
TABLE_NAME ," AS SELECT * FROM TableTemplate.", TABLE_NAME
) as creation_sql
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'TableTemplate';
the output will be like
CREATE TABLE SomeOtherTable.tbl_name AS SELECT * FROM TableTemplate.tbl_name;
then iterate result and execute CREATE TABLE ....
Ended up using something like this, in a method where aName is passed for the table name:
using (MySqlCommand cmd = new MySqlCommand(string.Format("CREATE DATABASE {0} ;", aName), connection))
{
cmd.ExecuteNonQuery(); // Create the database with the given user name
// Building the sql query that will return a "create table" per table in some_db template DB.
cmd.CommandText = (string.Format("SELECT CONCAT (\"CREATE TABLE {0}.\", TABLE_NAME ,\" "
+ "LIKE some_other_db.\", TABLE_NAME ) as creation_sql "
+ "FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'some_db';"
, aName));
try // Building the inner tables "create table" sql strings
{
using (MySqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
createInnerTablesList.Add(reader.GetString(0));
}
}
catch (MySqlException mysql_ex) { ... } // handle errors
foreach (var sql_insert_query in createInnerTablesList)
{
try // Insert the tables into the user database
{
cmd.CommandText = sql_insert_query;
cmd.ExecuteNonQuery();
}
catch (Exception e) { ... } // handle errors
}
}
The reasons for using LIKE vs AS like Jungsu suggested is that even though the AS will create the tables, it will not keep any of the constraints and keys defined (primary key, etc).
Using the LIKE will replicate them with the constraints.
I'm still not too happy about this, since I feel I'm missing something though ...

Passing parameter to SQL select statement IN clause acts weird.

I've got the following query that returns 2 records (in DataSet's query builder)
SELECT EmpID, Name, id
FROM Users
WHERE (CAST(id AS Varchar(20)) IN ('5688','5689'))
Now if I do the same query passing the parameter instead from code behind: String param = "'5688','5689'"; it returns null.
WHERE (CAST(id AS Varchar(20)) IN (#param))
I tried taking off the very first and last ', but that did not make a diffrence.
!!!id is a unique PK!!!
Anyone's got a clue?
The solution I found is quite simple, this works like a charm and there's no need for sps or other functions;
SQL:
SELECT whatever
FROM whatever
WHERE (PATINDEX('%''' + CAST(id AS Varchar(20)) + '''%', #param) > 0)
C#:
String param = "'''1234'',''4567'''";
dataTable1 = tableAdapter1.getYourValues(param);
A variable is not allowed in the IN clause.
You are expecting the values as a comma delimited string you could use the split function (user defined and non-standard) to join them with the original tables:
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=326300&SiteID=1
For more information you can visit this
('5688','5689') is an array of values.
Defining String param = "'5688','5689'"; and using it as (#param) makes ('5688','5689') a string. Which wont work.
Bibhas is correct. For me this worked:
string param="'1234','4567'"; we can't use param as SQL Parameter(#param).
command = new SqlCommand("SELECT * FROM table WHERE number IN (" + param + ")", connection);
command.ExcecuteReader();

Categories

Resources